3. 继承
对于继承,其实我是拒绝的!!看的时候懂了,用的时候懵逼。。。表示go die。。这次按照自己的理解把继承的几种方式记录下来,再忘记的话我就可以直接回家养老了。
3.1 原型链
主要思想: 利用prototype属性重写原型对象。使一个引用类型集成另一个引用类型的属性和方法。
function Supertype(){
this.name = 'supertype';
}
Supertype.prototype.getName = function(){
console.log(this.name);
}
function Subtype(){
this.subname = 'subtype';
}
Subtype.prototype = new Supertype();
Subtype.prototype.getSubName = function(){
console.log(this.subname);
}
var instance = new Subtype();
console.log(instance.getName()); // supertype
console.log(instance.getSubName()); // subtype
以上可以看到,instance 继承了 subtype 和 supertype 的方法和属性。
思考题来啦~
1. 我把这两个语句换一下位置,会出现什么情况?
Subtype.prototype.getSubName = function(){
console.log(this.subname);
}
Subtype.prototype = new Supertype();
2. 如果我通过这种方式添加 getSubName
方法会出现什么情况?
Subtype.prototype = {
getSubName: function(){
console.log(this.subname);
}
}
3. 试着自己画一个以上例子的原型链~
答案来啦:
1. 没错,更换位置后会报错~这是因为在重写 subtype 的原型之前, Subtype.prototype
指向默认原型,那么 getSubName
自然是定义在默认原型上。接下来我们重写了原型,Subtype.prototype
指向了 Supertype
的实例 ,而在其上我们并没有定义 getSubName()
,所以在调用该方法的时候就会报错啦。
2. 在我们使用字面量的方式赋值的时候,Subtype.prototype
就不在是按照我们设想的是指向Supertype
了,也就是说它会切断原型链。那么instance
可以说和Supertype
完全无关~
3.
实践中很少单独使用原型链。主要是因为原型链继承存在两个缺点: 1. 数据共享。原型中的引用类型值被其中一个实例改变的时候,其他所有实例的该属性值也会随之改变。 2. 没有办法在不影响其他实例的情况下,给超类型的构造函数传参。
function Supertype(){
this.color = ['red', 'blur', 'green']; // 引用类型值哦~
}
function Subtype(){
}
Subtype.prototype = new Supertype(); // 重写subtype的原型~
var instance1 = new Subtype();
var instance2 = new Subtype();
instance1.color.push('yellow');
console.log(instance1.color); // ['red', 'blur', 'green', 'yellow']
console.log(instance2.color); // ['red', 'blur', 'green', 'yellow']
再来思考一下,超类型的color属性被所有实例共享,如果我们通过 Subtype
实现对超类型的构造函数传参,能做到不影响哪个其他实例嘛?在来想一下,为什么color是引用类型值的时候出现这种情况,是字符串可以吗?自己去试一下哦~
3.2 借用构造函数
主要思想: 在Subtype
函数的内部调用Supertype
构造函数
function Supertype(){
this.color = ['red', 'blue', 'green'];
}
function Subtype(){
Supertype.call(this);
}
var instance1 = new Subtype();
var instance2 = new Subtype();
instance1.color.push('yellow');
console.log(instanc1.color); // "red", "blue", "green", "yellow"
console.log(instance2.color); // "red", "blue", "green"
实际上,我们这次是在Subtype
函数内部调用了 Supertype
,从而实现每次初始化 Subtype
函数,实例都会拥有一个自己的color。
借用构造函数继承虽然解决了原型链继承的问题: 参数传递和数据共享。但它实际上只是一个伪对象继承。
缺点: 函数无法实现复用。
3.3 组合继承
又叫做伪经典继承。它将原型链继承和借生构造函数结合起来。通过借生构造函数进行属性的继承,而通过原型链实现方法的复用。
function Supertype(name){
this.name = name;
this.color = ['red', 'blue', 'yellow'];
}
function Subtype(name){
Supertype.call(this, name);
}
Subtype.prototype = new Supertype();
Subtype.prototype.getColor = function(){
console.log(this.color);
}
哈~我们终于实现了传参,属性不共享,方法复用~ 在实践中,组合继承是最常用的继承方法。但它也有不足,想想看能不能想到呢?
它的缺点就是无论什么情况,它都会调用两次超类型构造函数。一次是在重写 Subtype
原型的时候,一次是在调用 Subtype
构造函数的时候。
3.4 原型式继承
主要思想: 基于已经存在的对象创建新对象
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var baseObj = {
name: 'colors',
color: ['red', 'blue'],
}
var instance1 = object(baseObj);
var instance2 = object(baseObj);
instance1.color.push('green');
console.log(instance1.color); // "red", "blue", "green"
console.log(instance2.color); // "red", "blue", "green"
好啦,这就是原型式继承。看到这里有没有想到 ES5 里的一个方法呢——Object.create()
。就是这个方法规范化了原型式继承。
当我们在只是简单地想要构造两个比较相似的对象的时候,使用原型式继承还是很方便的。
实际上,原型式继承只是创建了两个baseObj的副本。如果把这个过程看做是复制的话,那么它执行的只是简单的浅复制~毕竟引用类型值依旧是共享的。
3.5 寄生式继承
主要思想:创建一个用于封装继承过程的函数,并在函数内部对对象进行增强。思路有些类似工厂模式和寄生构造函数模式
function createObj(o) {
var clone = object(o); // 创建一个新对象
clone.sayHi = function(){
console.log('hi');
};
return clone;
}
var instance1 = createObj(baseObj);
instance1.sayHi(); // hi
通过以上方式,可以使返回的新对象不仅有baseObj的属性和方法,自己也拥有方法。但是类似于构造函数模式,它无法实现方法复用。
3.6 寄生组合式继承
我们经常用的继承方式是组合继承,但它也有缺点,就是会调用两次超类型构造函数。现在寄生组合式继承弥补了该缺点。
function inheritPrototype(subtype, supertype){
var ob = object(supertype);
ob.constructor = subtype;
subtype.prototype = ob;
}
function Supertype(name){
this.name = name;
this.color = ['red', 'blue'];
}
Supertype.prototype.getName = function(){
console.log(this.name);
}
function Subtype(){
Supertype.call(this, name);
}
inheritPrototype(Subtype, Supertype); // 与组合继承的不同点
// Subtype.prototype = new Supertype(); // 组合继承中的语句
Subtype.prototype.sayHi = function(){
console.log('hi');
};
看到了嘛?不同点就在于组合继承中,subtype指向Supertype的实例,在这个过程中调用了一次构造函数。而在寄生组合式继承中,使用 object() 方法创建了 Supertype 的一个副本,通过该副本实现继承。没有调用Supertype构造函数~
有兴趣的话可以把几种继承方式的原型链都画一下。反正我是在纸上画了。。电脑画图好慢。。。