继上篇文章深入剖析prototype、constructor、_proto_原理后,接下来整理、理解JS中的继承就水到渠成,一气呵成!
话不多说,上个文章中说过,原型链最主要就是为了解决JS在面向对象中继承功能的实现。所以在js继承中第一种要介绍的方式当然是从原型链继承。过程中会插入我对该种继承产生过的一些疑惑并作出解答,希望这对你的理解能带来帮助
function Father () {
this.name = 'father';
}
Father.prototype.getName = function () {
console.log(this.name);
return this.name;
};
function Son () {
this.age = 12;
}
//name属性在son中找不到,则会自动从Son的prototype(即father对象)中寻找
Son.prototype = new Father(); // 原型链继承
let son1 = new Son();
在理解这种继承的时候我产生过一个疑惑:既然是原型链继承,为什么不把Son.prototype = Father.prototype?后来仔细想想,这种做法会丧失掉继承中非常重要的特性:多态 。如果不知道继承多态特性的可以去查查。并且子类在原型链做做增删改都会直接影响到父类。这些缺陷都太过恶劣。
然而上面的这种继承方式也不是完美的,他同样存在缺点:
先不讲如何改进原型链继承,接下来看常见的第二种继承方式:
function Father () {
this.name = 'father';
}
Father.prototype.getName = function () {
console.log(this.name);
return this.name;
};
function Son () {
Father.call(this); // 构造继承
this.age = 12;
}
let son1 = new Son();
这种继承方式仅仅是利用Father构造方法,传入son作为Father构造函数中的this。他们的原型链并未发生任何关系。
先做个小结:仔细看不难知道,构造继承与原型链继承分别是继承私有和公有两种属性的方式,那么结合形成互补才是两者的改进之道。
由此引出第三种继承:
function Father () {
this.name = 'father';
}
Father.prototype.getName = function () {
console.log(this.name);
return this.name;
};
function Son () {
Father.call(this); // ②
this.age = 12;
}
//name属性在son中找不到,则会自动从Son的prototype(即father对象)中寻找
Son.prototype = new Father(); // 原型链继承 ③
Son.prototype.constructor = Son; // ①
let son1 = new Son();
代码几乎是将前面第一、二中结合起来,但唯一不同的是:在代码①处:Son.prototype,constructor = Son
产生过疑惑:
- 为什么增加代码①处:Son.prototype.constructor = Son?
- 因为Son.prototype = new Father(),Son.prototype指向的是father对象,而father对象本应该是Father()构造函数,增加①代码后岂不是将father的构造函数改为指向Son()?
细细想来:
疑惑一:因为son的prototype发生了改变:son._proto_原本的指向Son()构造函数下的prototype,而这个prototype中的construtor指向Son(),而此时执行Son.prototype = new Father()之后,son._proto_指向了new Father()对象,这时候son丢失了原本的construtor指向Son()的关系【这个关系是有必要继续维持的,例如用到instance of之类的方法就会产生意想不到的结果】
疑惑二:Son.prototype.construtor = Son仅仅是在father对象中新增了construtor属性,而非改变Father()下protytpe下的construtor:通过调试模式下你就可以发现construtor真真实实实在father对象中产生。
然而这种方式也有美中不足的地方,在继承的过程中Father()被执行了两次【②③】,而②的作用仅仅是为了原型链的继承,所以执行Father()函数中的其他逻辑【如this.name = name】是没有作用并且浪费资源的。但我们前面又讲了不能直接Son.prototype = Father.prototype,所以该怎么优化呢?
我们先了解什么是原型式继承?
原型式继承是牛逼之父道格拉斯·克罗克福德在 2006年写了一篇文章,题为 Prototypal Inheritance in JavaScript (JavaScript 中的原型式继承)提出的。我的理解就是最纯粹的原型继承!
function object(o){
function f(){}
f.prototype = o;
return new f();
}
创建一个对象,将他的prototype直接指向o对象,是否跟前面个说的son的prototype直接直接father的prototype,中间做的个f对象,解决了直接赋值prototype引起的问题尴尬。这种方式解决在原型链继承中 Son.prototype = new Father();中new Father()中所产生的额外资源消耗,因为这一步仅仅是为了继承Father中的prototype。因为其中产生的空白f()函数已经是简洁到不能再简洁了。
在原型继承发生第二种增强继承
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
function Father () {
this.name = 'father';
}
function createSon() {
var tempObj= object(Father);
tempObj.age = 12;
}
let son= createSon();
有必要说明一下,开发中函数编程有一种函数编程写法就叫:函数增强(装饰器),就是将原函数增加功能的一种写法。基于原型,把增强功能写在createSon函数中!这种写法在js继承中也叫寄生继承。
结合到组合继承当中:
function Father () {
this.name = 'father';
}
Father.prototype.getName = function () {
console.log(this.name);
return this.name;
};
function Son () {
Father.call(this); // ②
this.age = 12;
}
function object(o){
function f(){}
f.prototype = o;
return new f();
}
function createSon(sonFn, fatherFn) {
var tempObj= object(Father);
tempObj.height = 176;
tempObj.constructor = sonFn;
}
let son = craeteSon(Son, Father);
这种方式弥补了组合继承中多次调用了Father()函数产生了莫须有的消耗【new Father()替换了new F();F()内部无其他操作逻辑】
这种方式也是目前最常用的继承方式。
在es6中新增了继承语法,并且增加了extends来统一实现extends实现继承。有条件的话推荐该种继承方式!!!
好了文章写到这。
目前正在处于离职->新工作的更替状态,也就是舒舒服服的度个小长假,10天!哈哈哈,实在太舒服。希望大家都不要在996的状态中迷失自我,只有有适当的业余时间,才能叫生活,才能做自己喜欢做的事情。
happy endind…
下一篇文章打算解释探究new关键字的含义,想看的留意一下!