JS: 继承
继承
记录一下 javascript 的各种继承方式,个人用得比较多的还是原型链继承和 ES6 的 extends。
原型链继承
// 原型模式
function Parents() {
this.name = 'Parents';
}
Parents.prototype.getName = function() {
return this.name;
}
// 原型链继承
function Child() {}
Child.prototype = new Parents();
Child.prototype.constructor = Child;
let son = new Child();
son.getName(); // Parents;
缺点:
- 在创建 Child 的实例时,无法向 Parents 传参
- 父类里面的引用类型被共享,个例修改导致所有实例都被修改
function Parents() {
this.name = ['dad', 'mom'];
}
function Child() {}
Child.prototype = new Parents();
Child.prototype.constructor = Child;
// 无法向 Parents 传参
let son = new Child();
let daughter = new Child();
// 父类里面的引用类型被共享
son.name == daughter.name; // true
// 个例修改导致所有实例都被修改
son.name[0] = 'father';
daughter.name[0]; // "father"
借用构造函数
为了解决上面的问题 ,经典继承方式被设计出来:
// 构造函数模式
function Parents(childName) {
this.childName = childName;
this.name = ['dad', 'mom'];
this.getName = function() {
return this.name;
}
}
// 借用构造函数继承
function Child(name) {
Parents.call(this, name);
}
// 可以向 Parents 传参
let son = new Child('son');
son.childName; // "son"
// 父类里面的引用类型不会被共享
let daughter = new Child('daughter');
son.name[0] = 'father';
daughter.name[0]; // "dad"
缺点:方法都在构造函数里定义,每次创建 Child 实例,都要重新创建一次方法,函数无法得到复用。
组合继承
为了弥补上面的缺点,于是乎组合继承诞生了。
// 组合模式
function Parents(childName) {
this.childName = childName;
this.name = ['dad', 'mom'];
}
Parents.prototype.getName = function() {
return this.name;
}
// 组合继承
function Child(name) {
Parents.call(this, name);
}
Child.prototype = new Parents();
Child.prototype.constructor = Child;
let son = new Child('son');
son.name[0] = 'father';
let daughter = new Child('daughter');
daufhter.name[0]; // "dad"
因为原型链能保持不变,所以instanceOf
和isProtypeOf()
也能识别基于组合继承创建的对象。
原型式继承
function obj(o) {
function F() {}
F.prototype = 0;
return new F();
}
这种方式就是在 obj 函数内部,创建一个临时性的构造函数 F(),然后将传入的对象作为这个构造函数的原型,最后返回这个临时类的实例。
其实就是对传入的对象进行一次浅复制,类似于 ES5 的 Object.create() 方法。
Object.create(proto, [propertiesObject]) 方法创建一个新对象,使用现有的对象来提供新创建的对象的
__proto__
。proto: 新创建对象的原型对象。
propertiesObject: 可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不 是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties() 的第二个参数。
寄生式继承
function createObj (o) {
var clone = Object.create(o);
clone.sayHi = function () {
console.log('hi');
}
return clone;
}
这种方式就是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回增强过的对象。本质上还是对传入的对象进行一次浅复制。
寄生组合式继承
这种方式得先创建一个用于封装继承过程的函数。
function inheritPrototype(child, Parents) {
let prototype = Object.create(Parents.prototype);
prototype.constructor = child;
child.prototype = prototype
}
然后用上面的函数实现继承:
function Parents(childName) {
this.childName = childName;
this.name = ['dad', 'mom'];
}
Parents.prototype.getName = function() {
return this.name;
}
function Child(name) {
Parents.call(this, name);
}
inheritPrototype(Child, Parents);
其实和组合继承差不多,只是封装了一个 inheritPrototype 函数使得在继承的时候只在 Child 里调用一次 Parents 构造函数。
ES6 的 extends
ES6 引入了 class,可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。但需要注意的是,ES6 的 class 只是语法糖,本质上还是 ES5 的通过修改原型链实现继承的方法,具体可以去babel 试用来看看 babel 对于 class 的转换。
class Parents {
// 构造函数
constructor(name) {
this.name = name;
}
// 静态方法
static getName() {
return this.name;
}
// 方法
printName() {
console.log(this.name);
}
}
class Child extends Parents {
constructor(name, childName) {
// 调用父类的constructor(name)
super(name);
this.childName = childName;
}
printName() {
super.printName();
console.log(this.childName);
}
}
let c = new Child('mom', 'son');
c.printName();// mom son
ES5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
大多数浏览器的 ES5 实现之中,每一个对象都有
__proto__
属性,指向对应的构造函数的prototype
属性。Class 作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。(1)子类的
__proto__
属性,表示构造函数的继承,总是指向父类。(2)子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
参考
《javascript高级程序设计》(三)