Javascript继承

109 阅读2分钟

Javascript继承

在 JavaScript 中,继承是一种通过一个对象(子类)来获取另一个对象(父类)属性和方法的方式。JavaScript 中的继承主要有两种方式:原型继承和构造函数继承。

原型继承

function Parent (name) {
  this.arr = [1, 2, 3];
  this.name = name;
}

Parent.prototype.getName = function () {
  console.log(this.arr);
}

function Child () {}

Child.prototype = new Parent('Tom'); // 改变Child的原型

var child1 = new Child();
var child2 = new Child();
child1.arr.push(4); // 改变引用属性值
child1.getName(); // [ 1, 2, 3, 4 ]
child2.getName(); // [ 1, 2, 3, 4 ]

通过改变Child的prototype指向,使得Child继承了Parent的实例属性。但是:

  • 引用类型的属性被所有实例共享,即一改接改。
  • 创建Child的实例时,不能向Parent传参,因为在修改Chil原型时Parent的属性值就已经确定。

构造函数继承(经典继承)

function Parent(name) {
  this.arr = [1, 2, 3, 4];
  this.name = name;
}
Parent.prototype.sayName = function() {
  console.log(this.name);
}

function Child(name) {
  Parent.call(this, name); // 在Child内调用构造函数Parent
}

const child1 = new Child('Tom');
const child2 = new Child('Sam');
child1.arr.push(5);
console.log(child2); // { arr: [ 1, 2, 3, 4, 5 ], name: 'Tom' }
console.log(child1); // { arr: [ 1, 2, 3, 4 ], name: 'Sam' }
child1.sayName(); // sayName is undefined

Child构造函数内调用Parent构造函数,将Parent的实例属性指向Child实例(需要了解new关键字原理和构造函数执行原理)。

构造函数原理

    function Person(name, age) {
      // 1.函数体最前端隐式创建空对象this={}
      this.name = name;
      // this = {
      //		name : name
      // }
      this.age = age;
      // this = {
      //	 name : name,
      //	 age : age
      // }
      // 2.将name属性添加进去
      return this;
      // 3.隐式抛出this,可以自行return其它结果
    }

new执行原理

function myNew(Func, ...args) {
    // 创建原型为Func.prototype的对象
    let obj = Object.create(Func.prototype);
    // 改变构造函数this指向obj,将属性都挂载到obj上
    const result= Func.apply(obj, args);
    // 构造函数可自定义返回结果,但必须是对象
    return typeof result === 'object' ? result : obj;
}

解决了原型继承的缺点

  • 避免了引用类型的属性被所有实例共享
  • 可以在 Child 中向 Parent 传参

但是并不能继承Parent的方法,方法都在Child构造函数中定义。

组合继承

原型链继承和经典继承双剑合璧,结合了两种方法的优点。

function Parent (name) {
  this.name = name;
  this.arr = [1, 2, 3];
}

Parent.prototype.getArr = function () {
  console.log(this.arr)
}

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

const child1 = new Child('Tom', 25);
const child2 = new Child('Sam', 22);
child1.arr.push(4);
child1.getArr(); // [ 1, 2, 3, 4 ]
child2.getArr(); // [ 1, 2, 3 ]
console.log(child1.name); // 'Tom'
console.log(child2.name); // 'Sam'

圣杯模式

const inherit = (function () {
    const F = function() {};
    return function (target, origin) {
        F.prototype = origin.prototype;
        target.prototype = new F();
        target.prototype.constructor = target;
    }
}());

利用闭包私有化标量F,通过F引用被继承者的原型,通过实例化生成新的对象作为继承者的原型,并将原型的构造函数指向继承者本身。

类继承

通过es6提供class类实现的继承。

class Parent {
  constructor(name) {
    this.arr = [1, 2, 3];
    this.name = name;
  }
  getArr() {
    console.log(this.arr);
  }
}
class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  sayName() {
    console.log(this.age);
  }
}
const child1 = new Child('Tom', 25);
const child2 = new Child('Sam', 22);
child1.arr.push(4);
child1.getArr(); // [ 1, 2, 3, 4 ]
child2.getArr(); // [ 1, 2, 3 ]
console.log(child1.name); // 'Tom'
console.log(child2.name); // 'Sam'