继承 方法

99 阅读3分钟

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 继承方式也是采用 寄生组合式 的继承方式。