js中共有6种继承
一、原型链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function SuperType(){
this.prototype=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property
}
function SubType(){
this.subPrototy=false;
}
//继承了SuperType
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
var instance = new SubType();
console,log(instance.getSuperValue);//true
console.log(instance.getSubValue);//false
|
以上代码我们可以看到定义两个构造函数,里面有属性,原型里面有方法,我们让SubType的原型等于SuperType的实例,通过替换了SubType的默认原型对象实现继承,我们可以看到SubType的实例成功调用SuperType里的方法,之后我们又为其手动添加了一个新的方法也是可以调用的。
注意:继承原型后使用字面量添加方法,会使继承无效,因为已经替换了原型,我们在上一篇中介绍原型时已经讲过。
缺点:(1)继承后的原型对应的构造函数它所有的实例都会共享原型中的方法。 (2)继承的子类和父类,子类是不能向父类传参数的。
二、借用构造函数
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
|
function SuperType (){
this.colors=['red','blue'];
}
function SubType(){
//用call或者apply将调用对象换成SuperType实现对SuperType函数的继承
//this指向当前对象
SuperType.call(this);
}
var o=new SubType();
o.colors.push('black');
console.log(o);//red,blue,black
var o1=new SubType();
console.log(o1)//red,blue
//利用call()或apply()在子类函数中调用父类函数实现继承,我们可以看到SubType的实例并不共享属性;而且子类也是可以向父类传参的
function SuperType (name){
this.name=name;
}
function SubType(){
SuperType.call(this,'lili');
this.age=12;
}
var o=new SubType();
console.log(o.name)//lili
console.log(o.age)//12
|
我们可以看到子类传入参数后,调用父类函数直接赋值给属性name。为了确保父类的属性不会覆盖子类,在调用父类函数后再添加新属性。 缺点:方法都在构造函数中定义,无法函数复用,父类的原型中的方法对于子类也是不可见的。
三、组合继承(原型链和借用构造函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function SuperType(){
this.name=name;
this.colors=\['red','blue'\];
}
SuperType.prototype.sayName=function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age=age;
}
SubType.prototype=new SuperType();
SubType.prototype.sayAge=function(){
alert(this.age);
}
var o=new SubType('lili','23');
o.colors.push('black');
alert(o.color);//red,blue,black
alert(o.name);//lili
alert(o.age);//23
var o1=new SubType('hi','21');
alert(o1.name);//hi
alert(o1.age);//21
|
我们可以看到父类函数里有两个属性,原型里有一个方法,用原型链继承原型里的方法,这样他们的方法共享,借用构造函数的方法继承了父类函数里的属性,属性不共享,这样每个实例都有自己的属性,和使用相同的方法了。 组合继承避免了原型链和借用构造函数的缺陷,是JavaScript中最常用的继承模式。
四、原型式继承
《JavaScript语言精粹》作者克罗克福德提出了一个方式来实现继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: \["Shelby", "Court", "Van"\]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
console.log(anotherPerson.name);//Greg
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
console.log(yetAnotherPerson.name);//Linda
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
alert(person.name);//Nicholas,名字不会被修改
|
我们可以看到object函数中创建了一个临时性的构造函数,将传入的参数等于构造函数的原型,最后返回这个构造函数的实例; 这种原型式继承要求必须要有一个对象作为另一个对象使用的基础。所以我们要先定义一个person对象,之后我们通过函数定义两个实例,并将参数person传入,两个实例成员都有对象person的属性,也可以修改。 ES5中推出了Object.create()规范了原型式继承,方法接受两个参数,第一个就是你准备的参数,第二个(可选)就是为一个新对象定义额外属性的对象。
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
|
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create()存在兼容性问题,因此不推荐使用; 解决兼容性问题封装一个自定义函数:
function create(obj){
if(Object.create){
return Object.create(obj);
}else{
function F() {
}
F.prototype = obj;
return new F();
}
}
|
五、寄生式继承
寄生式继承也是克罗克福德提出的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function createAnother(original){
//object函数不是必须的,任何能返回对象的函数都可以
var clone=object(original);
clone.sayHi=function(){
alert('hi');
};
return clone;
}
var person={
name :'lili';
friends:['jack','van'];
};
var anotherPerson=createAnother(person);
anotherPerson.sayHi();//hi
|
寄生式继承的思路跟工厂模式差不多,封装一个函数,函数接受一个参数,参数就是将要作为新对象基础的对象,然后把这对象传给object函数,返回值给clone,再为clone对象添加一个方法,最后返回clone对象。我们可以看到person返回了一个新对象- anotherPerson,新对象不仅有person 的所有属性和方法,还有自己的sayHi方法 缺点:还是函数不能复用的问题;
六、寄生组合式继承
我们之前说过Js的继承用的最多的是第三种组合继承,但是他也有缺点,他会调用两次超类型构造函数(父类函数),第一次调用会创建实例属性name和colors,第二次调用又会如此,这样就覆盖了原来的属性,所以寄生组合式继承是对第一次调用函数的优化,只需创建一个超类型函数的副本即可,这样子类就只调用一次超类型构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function inheritPrototype(subType,SuperType){
var prototype=Object(SuperType. prototype);//赋值一份父类的原型
subType.prototype=prototype;//将赋值的原型指向子类的原型
preototype.constructor=subType;//赋值的原型里的constructor失效需要重新指向
}
//所以原来的组合继承可以这样改
function SuperType(){
this.name=name;
this.colors=\['red','blue'\];
}
SuperType.prototype.sayName=function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age=age;
}
//SubType.prototype=new SuperType();//第一次调用父类函数
//替换成这个
inheritPrototype(subType,SuperType);
SubType.prototype.sayAge=function(){
alert(this.age);
}
|