基于构造函数的继承
继承是面向对象编程中的重要组成之一。本文梳理 js 中实现继承的方式,说清楚原型链的生与死。
js 在 es6 中添加了 class,在此之前都是通过原型链来实现继承的。class 使用比较清晰,所以今儿先整理基于构造函数的继承。
js 中是直接通过构造函数实例化来生成的对象的,首先定义一个基构造函数:
function Base(name) {
this.name = name;
}
Base.prototype.getName = function () {
return this.name;
};
唯一需要注意的就是不要使用箭头函数,否则 this 是 undefined。
此时若有一个派生类 Derived,若要完好的继承 Base 需要继承两样东西:构造函数中的属性和方法和原型上的属性和方法
首先是继承构造函数中实例化的内容:
function Derived(age, name) {
Base.call(this, name); // apply也可以
this.age = age;
}
然后是通过修改 prototype 属性来继承 Base prototype 上的内容:
Derived.prototype = new Base();
Derived.prototype.getAge = function () {
return this.age;
};
到这里通过 Derived 实例化的对象已经可以正常访问 name、age、 getName 和 getAge(Derived 扩展了自己的原型添加了 getAge 方法)
但是如果此时查看实例化的对象的 constructor 属性发现指向其实是不正确的
const a = new Derived(18, "xdx");
a.constructor === Derived; // false
a instanceof Derived; // false
所以需要修正一下派生构造函数的原型中的 constructor 属性
Derived.prototype.constructor = Derived;
const b = new Derived(19, "xdx");
b.constructor === Derived; // true
b instanceof Derived; // true
至此就通过所有对象都隐含的属性__proto__
形成了一条原型链,当实例化的对象在查询属性时就会沿着这条链一层层往上查询到顶层的__proto__
的值为 Object.prototype,此时若 Object.prototype 都没有想要查询的值,则会返回 undefined
b.__proto__.__proto__.__proto__ === Object.prototype; // true
这里简单解释一下为什么顶层是 Object.prototype。js 中所有对象若没有指明构造函数的情况下,都是通过 Object 构造函数进行实例化的,包括函数的 prototype 属性的值也是,所以 Base 函数的 prototype 的值默认情况下就是一个空对象且__proto__
属性指向 Object.prototype
这时候肯定会有好奇宝宝问那 Object.prototype 也是一个对象呀,那Object.prototype.__proto__
是啥呢,原型链怎么到这就断了呢。答案是
Object.prototype.__proto__ === null; // true
是的,为 null 且不可修改,其他对象你可能可以通过修改__proto__
来修改原型链,但是到了 Object.prototype 就必须终止了。