1. 原型链继承
父类实例对象中有proto,是对象,叫原型,子类构造函数中有prototype属性,也是对象,也叫原型,由于原型中的方法是可以互相访问的。因此就是让子类构造函数的原型,指向父类实例对象的原型,就实现了原型链继承。
function Parent() {
this.name = 'Tom';
this.data = [1, 2, 3];
}
function Child() {
this.age = 18;
}
Child.prototype = new Parent();
let child = new Child();
console.log(child);
// 打印台输出
/*
Child
age: 18
__proto__: Parent
data: (3) [1, 2, 3]
name: "Tom"
__proto__: Object
*/
缺点
let child1 = new Child();
let child2 = new Child();
child1.data.push(4);
console.log(child1.data);
console.log(child2.data);
// 打印台输出
/*
[1, 2, 3, 4]
[1, 2, 3, 4]
*/
可以看到,只改变了 child1 的data 数组的属性,但是child2 实例对象也跟着改变了。这是由于 两个实例使用的是同一个原型对象。它们的内存是共享的,因此当一个对象发生变化时,另一个也会随着发生变化,这是原型链继承的一个很大缺点。
2. 构造函数继承(call,apply)
通过在子类里调用 call 或者 apply 方法,让子类构造函数直接指向父类构造函数。
function Parent() {
this.name = 'Tom';
this.data = [1, 2, 3];
}
Parent.prototype.say = function() {
console.log(123);
}
function Child() {
Parent.call(this);
this.age = 18;
}
let child1 = new Child();
let child2 = new Child();
child1.data.push(4);
console.log(child1);
console.log(child2);
// 控制台打印输出
/*
child1:
Child {name: "Tom", data: Array(4), age: 18}
age: 18
data: (4) [1, 2, 3, 4]
name: "Tom"
__proto__: Object
child2:
Child {name: "Tom", data: Array(3), age: 18}
age: 18
data: (3) [1, 2, 3]
name: "Tom"
__proto__: Object
*/
从打印台输出,可以看出 Child 通过 call 方法,直接将 Parent 中的属性拿到自己身上,这样就实现了属性的继承。同时我们改变实例化传 child1 的 data 属性的值,child2 中的属性值并没有随着 child1 变化,也解决了原型链继承内存共享的问题。
缺点
Parent 的 原型上的say方法并没有被继承,这是因为 Child 实例化对象产生的 child1 原型还是指向自身。没有有继承 Parent 的原型链。
3. 原型式继承(Object.create)
Object.create这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
const parent = {
name: 'tom',
age: 18,
num: [1, 2, 3],
getName: function() {
return this.name;
}
}
const c1 = Object.create(parent)
console.log(c1.name); // tom
c1.num.push(4)
console.log(c1.num); // [ 1, 2, 3, 4 ]
const c2 = Object.create(parent)
console.log(c2.num); // [ 1, 2, 3, 4 ]
console.log(c2.getName()); // tom
由上面的结果可以看出,Object.create 实现对象的继承,与浅拷贝相似,引用类型的数据同样也发生了共享,但是可以实现方法的继承。
4. 寄生组合式继承
前面使用原型链继承的方式,存在着调用父类构造函数的浪费。
接下来的方式在使用Object.create 的基础上,减少构造函数的调用,以达到最优继承。
function clone(parent, child) {
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
}
function Parent() {
this.name = 'tom',
this.num = [1, 2, 3]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this)
}
clone(Parent, Child)
const c1 = new Child()
const c2 = new Child()
c1.num.push(4)
console.log(c1.num); // [ 1, 2, 3, 4 ]
console.log(c2.num); // [ 1, 2, 3 ]
console.log(c1.getName()); // tom
Object.create 实现方法继承,不会共享的原因是因为函数在调用过程中,谁调用函数,函数的this就会指向谁所以不存在通用的结果。
5. ES6 的 extends 实现继承 (类继承)
class Parent {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
const child = new Child('tom', 18)
console.log(child); // { name: 'tom', age: 18 }
ES6 extends 继承方式也是采用 寄生组合式 的继承方式。