ES5继承分别有以下几种
- 原型链继承
通过将对象的__proto__指向被继承构造函数的protopyte,使得新的对象在寻找属性和方法的时候能够通过__proto__向上查找原型链,用于定义实例间共享的属性或方法。这种方案最大问题就是子类无法通过父类创建私有属性。
// 示例: function Parent() { this.name = ['写代码像蔡徐抻'] } Parent.prototype.getName = function() { return this.name } function Child() {} Child.prototype = new Parent() Child.prototype.constructor = Child // 测试 const child1 = new Child() const child2 = new Child() child1.name[0] = 'foo' console.log(child1.name) // ['foo'] console.log(child2.name) // ['foo'] (预期是['写代码像蔡徐抻'], 对child1.name的修改引起了所有child实例的变化)
- 借用构造函数继承
通过在新对象的构造函数中使用call或者apply改变this指向,运行被继承的构造函数,由此得到实例对象的自身属性。但是这种办法只能继承父类构造函数中声明的实例属性,并没有继承父类原型的属性和方法。
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { Parent.call(this, 'zhangsan') // 执行父类构造方法并绑定子类的this, 使得父类中的属性能够赋到子类的this上 } //测试 const child1 = new Child() const child2 = new Child() child1.name[0] = 'foo' console.log(child1.name) // ['foo'] console.log(child2.name) // ['zhangsan'] child2.getName() // 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法
- 组合继承(原型链继承+借用构造函数继承)
通过结合原型链继承和借用构造函数继承的优点,通过此方式,将实例的自身属性通过借用构造函数实现,将共有的属性或方法通过原型链继承实现,能让实例拥有自身的属性与方法,同时能够抽取公用的属性方法的实现,挂到prototype原型对象上分享使用。每次创建子类实例都执行了两次构造函数(Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅。
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { // 构造函数继承 Parent.call(this, 'zhangsan') } //原型链继承 Child.prototype = new Parent() Child.prototype.constructor = Child //测试 const child1 = new Child() const child2 = new Child() child1.name[0] = 'foo' console.log(child1.name) // ['foo'] console.log(child2.name) // ['zhangsan'] child2.getName() // ['zhangsan']
- 寄生继承
为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { // 构造函数继承 Parent.call(this, 'zhangsan') } //原型链继承 // Child.prototype = new Parent() Child.prototype = Parent.prototype //将`指向父类实例`改为`指向父类原型` Child.prototype.constructor = Child //测试 const child1 = new Child() const child2 = new Child() child1.name[0] = 'foo' console.log(child1.name) // ['foo'] console.log(child2.name) // ['zhangsan'] child2.getName() // ['zhangsan']
- 寄生组合式继承
寄生继承存在一个问题,由于子类原型和父类原型指向同一个对象,我们对子类原型的操作会影响到父类原型,例如给Child.prototype增加一个getName()方法,那么会导致Parent.prototype也增加或被覆盖一个getName()方法,为了解决这个问题,我们给Parent.prototype做一个浅拷贝
function Parent(name) { this.name = [name] } Parent.prototype.getName = function() { return this.name } function Child() { // 构造函数继承 Parent.call(this, 'zhangsan') } //原型链继承 // Child.prototype = new Parent() Child.prototype = Object.create(Parent.prototype) //将`指向父类实例`改为`指向父类原型` Child.prototype.constructor = Child //测试 const child = new Child() const parent = new Parent() child.getName() // ['zhangsan'] parent.getName() // 报错, 找不到getName()
到这里我们就完成了ES5环境下的继承的实现,这种继承方式称为寄生组合式继承,是目前最成熟的继承方式,babel对ES6继承的转化也是使用了寄生组合式继承
ES6继承
class Point() { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // RefreenceError super(x, y); this.color = color; // 正确 } }
两种继承方式的区别
ES5的继承的实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this,最终子类的this就使用这个被修饰后的继承自父类的this值。
1.class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
const bar = new Bar(); // it's ok function Bar() { this.bar = 42; } const foo = new Foo(); // ReferenceError: Foo is not defined class Foo { constructor() { this.foo = 42; } }
2.class 声明内部会启用严格模式。// 引用一个未声明的变量 function Bar() { baz = 42; // it's ok } const bar = new Bar(); class Foo { constructor() { fol = 42; // ReferenceError: fol is not defined } } const foo = new Foo();
3.class 的所有方法(包括静态方法和实例方法)都是不可枚举的。// 引用一个未声明的变量 function Bar() { this.bar = 42; } Bar.answer = function() { return 42; }; Bar.prototype.print = function() { console.log(this.bar); }; const barKeys = Object.keys(Bar); // ['answer'] const barProtoKeys = Object.keys(Bar.prototype); // ['print'] class Foo { constructor() { this.foo = 42; } static answer() { return 42; } print() { console.log(this.foo); } } const fooKeys = Object.keys(Foo); // [] const fooProtoKeys = Object.keys(Foo.prototype); // []
4.class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。function Bar() { this.bar = 42; } Bar.prototype.print = function() { console.log(this.bar); }; const bar = new Bar(); const barPrint = new bar.print(); // it's ok class Foo { constructor() { this.foo = 42; } print() { console.log(this.foo); } } const foo = new Foo(); const fooPrint = new foo.print(); // TypeError: foo.print is not a constructor
5.必须使用 new 调用 class。function Bar() { this.bar = 42; } const bar = Bar(); // it's ok class Foo { constructor() { this.foo = 42; } } const foo = Foo(); // TypeError: Class constructor Foo cannot be invoked without 'new'
6.class 内部无法重写类名。function Bar() { Bar = 'Baz'; // it's ok this.bar = 42; } const bar = new Bar(); // Bar: 'Baz' // bar: Bar {bar: 42} class Foo { constructor() { this.foo = 42; Foo = 'Fol'; // TypeError: Assignment to constant variable } } const foo = new Foo(); Foo = 'Fol'; // it's ok
参考文章: