继承相关
一、 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();
russ