继承
第一部分讲的都是如何创建一个类型,如果要创建多个类型,而且类型中属性方法相似度非常大,那么久可以直接用继承的方法来创建其它类 。
① 原型链
1、原型链定义+实现
继承:是面向对象语言中一个非常棒的概念。一般的面向对象语言中都支持两种继承:接口继承+实现继承。
但是!!由于js中函数没有签名,所以只支持实现继承。
其实现继承主要依靠原型链来实现的。
下面是一个继承的例子:
先用组合式构造一个类:Super
function Super(){
this.value = true;
}
Super.prototype.getvalue = function(){
return this.value;
};
再用构造函数创建第二个新类型:Sub
function Sub(){
this.subvalue = false;
}
//注意,接下来的方法直接用new Super构造
Sub.prototype = new Super();
Sub.prototype.getsubvalue = function(){ //再创建一个新方法
return this.subvalue;
};
///实例化
var instanse = new Sub();
instanse.getsubvalue(); // "true"
注意Sub.prototype = new Super(); 正常此处应该写Sub.prototype的方法,但是此处直接将Sub.prototype指向了Super对象。
这时就形成了一个原型链。
instanse(实例)----->Sub(类型)----->Sub.prototype(原型)----->Super(原型)----->Super.prototype(原型)
在这个链中,后面的都算是Sub的原型,其中的东西都是可共享的。
Sub相当于可以使用除了自己后加入的方法getsubvalue,和自己的属性subvalue以外,还可以使用Super中的value和getvalue方法。
当实例中调用一个方法时,会一级一级向上找。
2、默认的原型链
实际上instanse完整的原型链为:
instanse(实例)----->Sub----->Sub.prototype----->Super----->Super.prototype----->Object----->Object.prototype
别忘了所有的函数都有一个终极的默认原型Object的实例,Object当然自己也有一个Object.prototype。
这就是为什么所有对象都有toString(),valueOf()的方法,不管有没有意义,都可以调用,因为这些都是终极原型Object.prototype中的方法.......必须得继承
3、确定原型与实例的方法
两种方法:
instanceof:
A 是实例of 类型
friend instanceof Person; //true
friend instanceof Object; //true
isPrototypeOf:
原型 . 是原型of ( 实例)
Person.prototype.isPrototypeOf(friend); //true
Person.prototype.isPrototypeOf(Object); //true
4、子级添加方法
子类添加新方法,OK 。直接用子类的prototype中写就可以
当子类中创建了一个与父级中同名的方法(即重写超类型的方法),OK。
此时就会屏蔽掉超类型中原方法,注意不会对超类型产生影响,仅在子类的实例化中会执行新方法。
5、缺点
两点:
1、引用类型共享问题
instanse(实例)----->Sub(类型)----->Sub.prototype(原型)----->Super(原型)----->Super.prototype(原型)
就这个原型链中,我们可以看出Super的构造函数也变成了原型之一,这就导致了如果Super构造函数中有属性值为引用类型(比如数组型),就会被共享,当实例中将其改变后,所有的都会被改。
之前上一部分讲创建一个类型时,出现过这种问题的是纯纯的原型模式。为了避免这个问题我们才引入的构造原型组合式。
但是此处因为继承,构造函数的也变成了原型模式。
2、不能向上级传参
只能从父级向下引用
解决办法——组合继承
② 组合继承
1、实现
组合继承的思路:因为之前构造函数被继承的缘故,导致被共享,那么在新的类型中,我们不把构造函数直接继承过来,而是变成调用父级的方式。我们可以运用call,或者apply来现场调用。
///首先还是创建父级Super
function Super(name){
this.name = name;
this.color = ["red" , "blue"];
}
Super.prototype.say = function(){
alert(this.name);
};
///接下来创建新的类Sub
function Sub(name,age){
Super.call(this , name); //要继承的name与color,用call函数在此处调用Super的构造函数
this.age = age; // 自己新创建的属性age
}
Sub.prototype = new Super(); //方法直接继承
Sub.prototype.sayage = function(){ // 自己的新方法
alert(this.age);
};
//测试一下
var a = new Sub("jack",20); // 创建Sub实例
a.color.push("green"); //将引用类型做更改
alert(a.color); //red,blue,green
a.say(); //jack
a.sayage(); //20
var b = new Sub("tom",33); // 创建Sub实例
alert(b.color); //red,blue
b.say(); //tom
b.sayage(); //33
2、缺点
无论什么情况下,都会调用两次超类型的构造函数(就是父级的构造函数)
Super.call(this , name); //要继承的name与color,用call函数在此处调用Super的构造函数
Sub.prototype = new Super(); //方法直接继承
顺序是先new Super(); 这时实际上已经有了一套name,color,位置在超类型中,被继承
只不过后来的call又实现了一套子类中自己的name,color,将超类型中的屏蔽了。
解决办法——寄生组合式
③原形式继承+寄生式继承+寄生组合式继承
原型式:可以在没有预先定义构造函数的情况下实现继承,本质是先浅复制,再改造
寄生式:与原形式类似,也是先创建一个对象,然后再改造
寄生组合式:集寄生式和组合式,是实现引用类型最理想的继承方式。,只调用一次超类型的构造函数