现在的位置: 首页 > 综合 > 正文

JS中类式继承的实现

2019年08月24日 ⁄ 综合 ⁄ 共 4141字 ⁄ 字号 评论关闭

在开始摆弄代码之前,应该搞清楚使用继承的目的和能带来什么好处。一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化类之间的耦合。而要做到这两者都兼顾是很难的,我们需要根据具体的条件和环境下决定我们应该采取什么方法。根据我们对面向对象语言中继承的了解,继承会带类直接的强耦合,但js由于其特有的灵活性,可以设计出强耦合和弱耦合,高效率和低效率的代码。而具体用什么,看情况。

下面提供js实现继承的三种方法:类式继承,原型继承,掺元类。这里先简述类式继承,后两种在往后的随便中简述,请多多关注、指导,谢谢。

类式继承。

js类式继承的实现依靠原型链来实现的。什么是原型链?js中对象有个属性prototy,这个属性返回对象类型的引用,用于提供对象的类的一组基本功能。

貌似对prototype有印象,对了,我们经常这样用代码。

1
2
3
4
5
6
7
8
9
var Person
=
function(){   
    this.name
=
"kaitiren";
};
Person.prototype
= {
    //可以在这里提供Person的基本功能
    getName
:
function(){
        return this.name;
    }
}

我们把类的基本功能放在prototype属性里,表示Person这个对象的引用有XXX功能。

在理解原型后,需要理解下什么是原型链。在访问对象的某个成员(属性或方法)时,如果这个成员未见于当前对象,那么js会在prototype属性所指的那个对象中查找它,如果还没有找到,就继续到下一级的prototype所指的对象中查找,直至找到。如果没有找到就会返回undifined。

那么原型链给我们什么提示呢?很容易联想到,原型链意味着让一个类继承另一个类,只需将子类的prototype设置为指向父类的一个实例即可。这就把父类的成员绑定到子类上了,因为在子类上查找不到某个成员时会往父类中查找。(以上这两段用词不严谨,只在用通俗易懂的言语描述)

下面我们需要个Chinese类,需要继承Person类的name和getName成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Chinese
=
function(name,
nation){
        //继承,需要调用父类的构造函数,可以用call调用,this指向Chinese
    //使Person在此作用域上,才可以调用Person的成员
    Person.call(this,name);
    this.nation
= nation;
};
Chinese.prototype
= Person.prototype;
//这里不可和以前一样,因为覆盖掉了prototype属性
//Chinese.prototype
= {
// 
getNation : function(){
//     
return this.nation;
// 
}
//};
//以后的方法都需要这样加
Chinese.prototype.getNation
=
function(){
        return this.nation;
};

继承关系就建立了,我们这样调用它

1
2
var c
=
new Chinese("kaitiren","China");
alert(c.getName());//

kaitiren

于是类式继承就这样完成了。难道真的完成了嘛,用firebug在alert那里设断点,会发现原来的Person.prototype被修改了,添加了getNation方法。

1

这是因为在上面的代码Chinese.prototype = Person.prototype; 这是引用类型,修改Chinese同时也修改了Person。这本身就是不能容忍的,且使类之间形成强耦合性,这不是我们要的效果。

我们可以另起一个对象或实例化一个实例来弱化耦合性。

1
2
3
4
5
6
//第一种
//Chinese.prototype
= new Person();
//第二种
//var
F = function(){};
//F.prototype
= Person.prototype;
//Chinese.prototype
= F.prototype;

这两种方法有什么区别呢。在第二种中添加了一个空函数F,这样做可以避免创建父类的一个实例,因为有可能父类会比较庞大,而且父类的构造函数会有一些副作用,或者说会执行大量的计算任务。所以力荐第二种方法。

到此,完了嘛,还没有!在对象的属性prototype下面有个属性constructor,它保存了对构造特定对象实例的函数的引用。根据这个说法Chiese.prototype.constructor应该等于Chinese,实际上不是。

回忆之前在设置Chiese的原型链时,我们把Person.prototype 覆盖掉了Chiese.prototype。所以此时的Chiese.prototype.constructor是Person。我们还需要添加以下代码

1
2
3
4
//对这里的if条件不需要细究,知道Chinese.prototype.constructor
= Chinese就行
if(Chinese.prototype.constructor
== Object.prototype.constructor){
    Chinese.prototype.constructor
= Chinese;
}

整理全部代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var Person
=
function(name){
    this.name
= name;
};
Person.prototype
= {
    getName
:
function(){
        return this.name;
    }
};
 
var Chinese
=
function(name,
nation){
    Person.call(this,name);
    this.nation
= nation;
};
var F
=
function(){};
F.prototype
= Person.prototype;
Chinese.prototype
= F.prototype;
if(Chinese.prototype.constructor
== Object.prototype.constructor){
    Chinese.prototype.constructor
= Chinese;
}
Chinese.prototype.getNation
=
function(){
        return this.nation;
};
 
var c
=
new Chinese("kaitiren","China");
alert(c.getName());

如果可以把继承的代码放在一个函数里,方便代码复用,最后整理代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function extend(subClass,superClass){
    var F
=
function(){};
    F.prototype
= superClass.prototype;
    subClass.prototype
=
new F();
    subClass.prototype.constructor
= subClass;
    subClass.superclass
= superClass.prototype;
//加多了个属性指向父类本身以便调用父类函数
    if(superClass.prototype.constructor
== Object.prototype.constructor){
        superClass.prototype.constructor
= superClass;
    }
}
 
var Person
=
function(name){
    this.name
= name;
};
Person.prototype
= {
    getName
:
function(){
        return this.name;
    }
};
 
var Chinese
=
function(name,
nation){
    Person.call(this,name);
    this.nation
= nation;
};
extend(Chinese,
Person);
Chinese.prototype.getNation
=
function(){
        return this.nation;
};
 
var c
=
new Chinese("kaitiren","China");
alert(c.getName());

发表后修改:

在一楼的评论下,我对那个extend函数又有新的看法。之前在讨论如何设置原型链时提出了两种方法

1
2
3
4
5
6
//第一种
//Chinese.prototype
= new Person();
//第二种
//var
F = function(){};
//F.prototype
= Person.prototype;
//Chinese.prototype
= F.prototype;

虽然第二种减少了调用父类的构造函数这条路,但在设计Chinese类时用了Person.call(this,name);这里也相当于调用了父类的构造函数。

然而用第一种方法的话可以减少在Chinese中再写Person.call(this,name);,这部分代码在子类中往往会遗忘。不妨把这种功能代码放在了extend里。就只写

Chinese.prototype = new Person();也达到同样的目的:耦合不强。

但遗忘的一点是,Chinese.prototype = new Person();这样写对嘛。答案是不对!很明显 new Person()需要传一个name参数的。我们不可能在extend函数里做这部分工作,只好在Chinese类里调用父类的构造函数了。这样也符合面向对象的思路。

所以,还是力荐用第二种方法。

抱歉!评论已关闭.