Skip to content
本页目录

继承相关

一、 ES5 JS 如何实现继承

1、使用 call

在子类中调用父类 call 一下

js
function Parent1() {
	this.name = 'parent1';
	this.arr = function() {
		return;
	};
}
function Child1() {
	Parent1.call(this);
	this.type = 'child1';
}
console.log(new Child1());
// Child1 {name: "parent1", type: "child1"}

这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类原型对象中一旦存在方法那么子类无法继承。

2、借助原型链实现

js
function Parent2() {
	this.name = 'parent2';
	this.arr = [1, 2, 3];
}
function Child2() {
	this.type = 'child2';
}
Child2.prototype = new Parent2();

父类的方法和属性都能够访问,但实际上有一个潜在的不足。比如:

js
var s1 = new Child2();
var s2 = new Child2();
s1.arr.push(4);
console.log(s1.arr, s2.arr);

//  (4) [1, 2, 3, 4] (4) [1, 2, 3, 4]

明明只改变了 s1 的 arr 属性,为什么 s2 也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象。

3、将前两种组合

js
function Parent3() {
	this.name = 'parent3';
	this.arr = [1, 2, 3];
}
function Child3() {
	Parent3.call(this);
	this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.arr.push(4);
console.log(s3.arr, s4.arr);

这样的话,之前的问题都得以解决。但是这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是多余的操作。那么如何解决这个问题?

4、组合继承优化

js
function Parent4() {
	this.name = 'parent4';
	this.arr = [1, 2, 3];
}
function Child4() {
	Parent4.call(this);
	this.type = 'child4';
}
Child4.prototype = Parent4.prototype;

这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问。但是我们发现子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4 才对。

5、寄生组合继承(最优)

js
function Parent5() {
	this.name = 'parent5';
	this.arr = [1, 2, 3];
}
function Child5() {
	Parent5.call(this);
	this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;

以上来源:三元

二、ES5/ES6 的继承除了写法以外还有什么区别?

1、class 声明会提升

  • class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
js
const bar = new Bar(); // it's ok
function Bar() {
	this.bar = 42;
}

const foo = new Foo(); // ReferenceError
class Foo {
	constructor() {
		this.foo = 42;
	}
}

2、调用方法

  • ES6 必须使用 new 调用 class。
js
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'

3、this 生成顺序不同

  • ES5 和 ES6 子类 this 生成顺序不同。ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例,ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象。
js
function MyES5Array() {
	Array.call(this, arguments);
}

// it's useless
const arrayES5 = new MyES5Array(3); // arrayES5: MyES5Array {}

class MyES6Array extends Array {}

// it's ok
const arrayES6 = new MyES6Array(3); // arrayES6: MyES6Array(3) []

4、通过 proto 向上寻址

  • 子类可以直接通过 proto 寻址到父类。
js
class Super {}
class Sub extends Super {}

const sub = new Sub();

Sub.__proto__ === Super;
  • 而通过 ES5 的方式,Sub.proto === Function.prototype
js
function Super() {}
function Sub() {}

Sub.prototype = new Super();
Sub.prototype.constructor = Sub;

var sub = new Sub();

Sub.__proto__ === Function.prototype;

5、class 声明内部会启用严格模式。

js
// 引用一个未声明的变量
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();

MIT Licensed