前言
为了更好的阅读体验你可以查看我的github原文
最近又要面试了,继承这块掌握的不扎实,今天写出来好好研究研究。
1. 原型继承
核心思想
Child.prototype = new Parent();
子类的原型指向父类的一个实例,即子类的 protytype 指向父类实例的__proto__
,所以子类实例就继承了父类原型上的属性和方法。
举例
function Foo(name = "foo") {
this.name = name;
this.array = [];
}
Foo.prototype.name = "tom";
function Child() {}
Child.prototype = new Foo();
const child = new Child("child1");
child.array.push(1);
const child2 = new Child("child2");
child2.array.push(2);
console.log(child.constructor.array == Foo.prototype.array); //true
console.log("child", child2.array); //[1,2]
console.log(child2.name); //foo
缺点
我们参考上例,
- 每个 child 实例中的 array 属性所指向的都是
Foo.prototype.array
,因此对于引用类型的存储都指向同一块内存,child1 和 child2 的 array 是共享的,并不是独立的。 - 直接将子类的原型指向父类的实例,子类实例无法初始化父类构造函数上的属性。参考上例,每个 child 实例都不能给父类上的 name 属性赋值。
2.构造函数继承
核心思想
function Child() {
Foo.apply(this, arguments);
}
Child.prototype = new Parent();
在子类的构造函数中,通过改变父类构造函数的执行上下文,相当于做了代理。
举例
function Foo(name = "foo") {
this.name = name;
this.array = [];
}
Foo.prototype.age = 18;
function Child() {
Foo.apply(this, arguments);
}
const child = new Child("child");
child.array.push(1);
console.log(child.name); // child
console.log(child.age); // undefined prototype上的属性没有继承
const child2 = new Child("child2");
child2.array.push(2);
console.log(child2.array); // [2] 独立的实例
缺点
- prototype 上的属性和方法没有继承。适合没有在父类原型链上自定义方法和属性的继承。
3. 组合继承
核心思想
function Child() {
Foo.apply(this, arguments);
}
Child.prototype = new Parent();
其实就是把上面两种思想结合起来。
举例
function Foo(name = "foo") {
this.name = name;
this.array = [];
}
Foo.prototype.sayName = function () {
console.log(this.name);
};
function Child() {
Foo.apply(this, arguments);
}
Child.prototype = new Foo();
const child = new Child("child");
child.array.push(1);
const child2 = new Child("child2");
child2.array.push(2);
console.log(child2.array); // [2] 没有共享,实例里面的属性都是独立的
child2.sayName(); // child2 可以继承父类原型上的方法与属性
缺点
调用了两次构造函数。可能你会奇怪,为什么两种组合起来,反而两者的缺陷都解决了。这是因为在访问实例的时候,先找实例自身的属性,然后才回顺着原型链一级一级找原型上的属性,直至找不打返回 undefined。
4.寄生组合
核心思想
需要浅拷贝父类对象,来解决不能访问父类原型属性的问题。
function Child() {
Foo.apply(this, arguments);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Parent;
实例
function Foo(name = "foo") {
this.name = name;
this.array = [];
}
Foo.prototype.sayName = function () {
console.log(this.name);
};
function Child() {
Foo.apply(this, arguments);
}
Child.prototype = Object.create(Foo.prototype);
Child.prototype.constructor = Child; //注意这一句,如果不加,子类实例的constructor指向Foo
const child = new Child("child");
child.array.push(1);
console.log(child.name);
const child2 = new Child("child2");
child2.array.push(2);
console.log(child2.array); // [2] 没有共享,实例里面的属性都是独立的
child2.sayName(); // child2 可以继承父类原型上的方法与属性
缺点
几乎没有,但要非找个缺点,需要浅拷贝父类的原型,浪费内存。。
5. extends 继承
开发时首选,不多说了。现代人谁还拿 prototype 写继承?
class Foo {
constructor(name = "foo") {
this.name = name;
this.array = [];
}
sayName() {
console.log(this.name);
}
}
class Child extends Foo {
constructor(name) {
super(name);
}
}
const child = new Child("child");
child.array.push(1);
console.log(child.name); // child
const child2 = new Child("child2");
child2.array.push(2);
console.log(child2.array); // [2] 没有共享,实例里面的属性都是独立的
child2.sayName(); // child2 可以继承父类原型上的方法与属性
缺点
没有缺点,要是有非说不适配浏览器啥的,那就是工程化不及格。
网友评论