参考资料《JavaScript高级程序设计》
本文介绍四种实现继承的方式
- 原型链继承
- 组合继承
- 寄生式组合继承
- es6 class继承
原型链继承
实现一个简单的原型链继承
- 虚拟一个父类构造函数Parent,设置name属性和getName方法
- 虚拟一个子类构造函数Person继承父类的属性和方法
- 虚拟一个子类构造函数的实例,通过prototype访问父类Person的getName方法
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function () {
return this.name;
};
function Child() {}
Child.prototype = new Parent("hanmeimei");
const person = new Child();
console.log(person.getName());
这种方式存在的缺陷:
- 子类实例化的时候不能自由传参,只能在设置子类构造函数的原型的时候传参一次
Child.prototype = new Parent("hanmeimei");
- 父类的属性值是引用类型,则所有子类实例会共享,如
// ...省去其他代码
Child.prototype = new Parent(["hanmeimei"]);
// ...省去其他代码
const p1 = new Child();
const p2 = new Child();
p1.name.push("lilei");
console.log(p2.getName()); // 输出[ 'hanmeimei', 'lilei' ]
组合继承:借用call/apply实现继承+原型链
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function () {
return this.name;
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
创建实例验证效果
const p1 = new Child(["hanmeimei"]);
const p2 = new Child(["lilei"]);
p1.name.push("lilei");
console.log(p1.getName()); // 输出[ 'hanmeimei', 'lilei' ]
console.log(p2.getName()); // 输出[ 'lilei' ]
优点:
- 可以传参
- 引用类型属性不会共享
缺陷:
组合继承的方式比较完善,但仍然有瑕疵,就是两次调用了父类构造函数Child.prototype = new Parent()
和Parent.call(this, name)
,导致实例和Child.prototype上都创建了一个name属性。
可以通过getOwnPropertyNames
查看
console.log(Object.getOwnPropertyNames(Child.prototype)); // 输出:[ 'name' ]
console.log(Object.getOwnPropertyNames(p1)); // 输出:[ 'name' ]
《JavaScript高级程序设计》上还介绍了一种寄生式组合继承方法,可以解决这个缺陷
寄生式组合继承
关键是借助Object.create
创建一个新对象,用来制定这个新对象的原型,参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
也可以自己模拟一个Object.create
function createObj(obj) {
const F = function () {};
F.prototype = obj;
return new F();
}
利用这个改写组合继承子类构造函数继承写法
// Child.prototype = new Parent(); 原来的写法
Child.prototype = createObj(Parent.prototype); //新写法
最终的完整代码如下:
function createObj(obj) {
const F = function () {};
F.prototype = obj;
return new F();
}
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function () {
return this.name;
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = createObj(Parent.prototype);
写一段测试代码验证:
const p1 = new Child(["hanmeimei"]);
console.log(p1.getName()); // 输出:[ 'lilei' ]
console.log(Object.getOwnPropertyNames(Child.prototype)); // 输出:[]
console.log(Object.getOwnPropertyNames(p1)); // 输出:[ 'name' ]
既有组合继承的优点,也去掉了Child.prototype上的name属性
ES6 class继承
参考文档:http://caibaojian.com/es6/class.html
实现继承最简单的方式莫过于使用es6语法class
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Child extends Person {
constructor(name) {
super(name);
}
}
创建实例验证效果
const p1 = new Child(["hanmeimei"]);
const p2 = new Child(["lilei"]);
p1.name.push("lilei");
console.log(p1.getName()); // 输出[ 'hanmeimei', 'lilei' ]
console.log(p2.getName()); // 输出[ 'lilei' ]
ES6的类class,完全可以看作构造函数的另一种写法。
typeof Point // "function"
Point === Point.prototype.constructor // true