JS那些你看过就忘的知识点之继承

109 阅读2分钟

继承在JS里不可谓不重,但是知识点看了千万次,下次要说的时候还是说不出个所以然,虽然已经有很多前辈写了很多很好的文章,但是好记性不如烂笔头,还是做一个记录整理,加强记忆理解查询。第一次写技术文章,文章里有什么错误,还请大家不吝指出。

正文从这里开始~

JS中原型链是实现继承的主要方法。实现原型链继承的方式如下:

function ParentType() {
    this.parentProperty = 'parent';
    this.colors = ['red', 'green'];
}
ParentType.prototype.getParentValue = function() {
    return this.parentProperty;
}
function ChildType() {
    this.childProperty = 'child';
}
ChildType.prototype = new parentType();
ChildType.prototype.getChildValue = function() {
    return this.childProperty;
}
let parent = new ParentType();
let child1 = new ChildType();
let child2 = new ChildType();

child1.getParentValue(); // parent
console.log(child1.colors); // ['red', 'green']

child1.colors.push('black');
parent.colors; // ['red', 'green']
child2.colors; // ['red', 'green', 'black']

console.log(ChildType.prototype.constructor === ParentType); // true

画了原型链之后对于上面的代码理解会更加清晰。

从上面的例子可以看到,原型链继承有一个问题在于,如果原型中有引用类型的属性,实例会共享该属性。那该如何解决这个问题呢?构造函数!

function ParentType() {
    this.parentProperty = 'parent';
    this.colors = ['red', 'green'];
}
ParentType.prototype.getParentValue = function() {
    return this.parentProperty;
}
function ChildType() {
    ParentType.call(this);
    this.childProperty = 'child';
}
ChildType.prototype = new ParentType();
ChildType.prototype.constructor = ChildType;
ChildType.prototype.getChildValue = function() {
    return this.childProperty;
}
let child1 = new ChildType();
let child2 = new ChildType();

child1.colors.push('black');
child2.colors; // ['red', 'green']

console.log(ChildType.prototype.constructor === ChildType); // true

为什么构造函数可以解决这个问题呢,其实并不是原型上没有引用属性了,而是被实例上的属性覆盖了,我们再通过一张图来了解一下。

当我们删除实例上的属性后,实例仍然共享原型上的属性。

delete color1.colors;
delete color2.colors;
color1.push('black');
console.log(color2.colors); // ['red', 'green', 'black']

同时使用构造函数和原型链继承,也叫组合继承是我们最常使用的方法,但是这种方法还有一个小问题就是,它会调用两次超类型的构造函数。ChildType.prototype = new ParentType()ParentType.call(this)都会调用构造函数。那组合继承要如何改进呢?寄生组合式继承!

function inheritPrototype(parentType, childType) {
    let prototype = new Object(parentType.prototype);
    prototype.constructor = childType;
    childType.prototype = prototype;
}
function ParentType() {
    this.parentProperty = 'parent';
    this.colors = ['red', 'green'];
}
ParentType.prototype.getParentValue = function() {
    return this.parentProperty;
}
function ChildType() {
    ParentType.call(this);
    this.childProperty = 'child';
}
inheritPrototype(ParentType, ChildType);
ChildType.prototype.getChildValue = function() {
    return this.childProperty;
}

let child = new ChildType();

寄生组合式继承的原理是将ChildType的原型设置为ParentType的原型副本,因此ChildType实例的属性删除以后,原型链上不存在该属性,无法访问。

delete child.colors;
console.log(child.colors); // undefined

除了这几种继承方式之外,还有一些方法可以实现继承,因为不常使用没有列出来,大家可以自己查询了解。

写完文章画完图之后觉得自己理解的差不多了,于是在控制台测试了一下,看到下面的输出又开始陷入蒙圈状态。

console.log(Function.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true

后来想了一下,其实也是很好理解的,只是一开始陷入一个怪圈。我们可以理解为实例.__proto__ === 其构造函数.prototypeFunctionObject作为对象考虑,其构造函数都是Function,因此上面的代码自然成立。在控制台输入console.log(Function.toString())输出"function Function() { [native code] }"可以帮助我们更好的理解Function的构造函数为Function