js基础之继承

191 阅读3分钟

js基础之继承

搬运自个人博客

《JS高级程序设计》中说JS的继承有原型链、构造函数、组合式、原型式、寄生式、寄生组合式继承,加上ES6的class继承,一共有7种方式。前6种最关键的是搞清楚构造函数、原型对象、原型链的关系,其他模式都是这些的组合。

构造函数

构造函数也是函数,跟普通函数的没什么区别,但是可以用new()方法创建实例

e.g.

function Animal(name, legCount) {
  this.name = name;
  this.legCount = legCount;
}
const cat = new Animal('Tom', 4);

原型对象

每个函数都有一个prototype属性(对象没有这个属性),指向一个原型对象,即Animal.prototype( 这玩意是个对象,名字就叫Animal.prototye )

Animal.prototype默认有个constructor属性,这个对象的constructor属性指向该对象对应的构造函数,即Animal()。

console.log(Animal.prototype);
// 结果:
{
  constructor: ƒ Animal(name ,leg),
  __proto__: Object  
}

注意:Animal()函数内部定义了name、age属性,但是Animal.prototype对象是没有这些属性的,只有默认的constructor属性,除非在原型上定义其他属性:

Animal.prototype.speak = 'miao'
console.log(Animal.prototype);
// 结果:
{
  constructor: ƒ Animal(name ,leg),
  speak: 'miao',
  __proto__: Object
}

这时创建一个Animal实例:

const dog = new Animal('light', 4);

dog对象有一个[[prototype]]属性,指向该实例对象的原型对象,[[prototype]]可以用__proto__访问,即dog.__proto__ = Animal.prototype

class

没有class之前,生成实例都是用构造函数, es6中引入了class概念,可以像C++等直接用class关键字来定义类 e.g.

class Animal {
  species = 'bird'; // 这跟定义在this上(写在constructor内)是一样的效果
  constructor(name, age) {
    this.name = name; //  类内部的this指向该类的实例
    this.age = age;
    this.Height = 100;
  }
  speak() {
    return this.name + ', speak';
  }
  static voice() { // Animal类自身的静态方法
    return 'this is my voice';
  }
  get height() { // getter方法
    return this.Height * 3;
  }
  set height(val) { // setter方法
    this.Height += val;
  }
}
const cat = new Animal('Tom', 18);
console.log(cat, cat.speak(), Animal.voice());
// cat是: 
Animal {
  name: 'Tom',
  age: 18,
  Height: 100
}
cat.__proto__= {
  constructor:  f Aniaml(name, age),
  speak: f speak() {},
  height: 300,
  get height: f() {},
  set height: f(val) {},
  __proto__: Object
}

构造函数式继承

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.hand = 'hand';
}

function Child(name, age) {
  Person.call(this, name, age);
}
const tom = new Child('Tom', 10);
// tom是Child的实例,但是“借用了”Person的构造函数,生成的实例就包含了Person的属性
{
  name = 'Tom';
  age = 10;
  hand = 'hand';
}

原型链式继承

function Animal() {
  this.action = 'move';
  this.speak = 'speak';
}
Animal.prototype.hasLeg = 'true';

function Cat() {
  this.name = 'cat';
}
Cat.prototype = new Animal();
Cat.prototype.height = 50;

const tom = new Cat();

// tom
{
  name: 'cat',
  __proto__: Animal // 这里的Animal不是Animal构造函数,而是Cat的原型对象。因为Cat.prototype = new Animal();
}
// tom.__proto__指向tom的原型对象,就是Cat.prototype
{
  action: "move",
  height: 50,
  speak: "speak",
  __proto__: Object // 这里的Object是Animal.prototype
}
// tom.__proto__.__proto__指向tom原型对象的原型对象,就是Animal.prototype
{
  hasleg: 'true',
  constructor: ƒ Animal(),
  __proto__: Object // 这里的Object是原型Object
}

class继承

class的继承有专门的语法

class Animal {
  speak = 'bar';
  constructor() {
    this.name = 'Animal'; //  类内部的this指向该类的实例
    this.age = 15;
    this.Height = 100;
  }
}
class Dog extends Animal {
  constructor() {
    constructor(...args) {
      super(...args); // super(...args)相当于Animal.prototype.constructor.call(this, ...args)
      this.speak = 'bar';
    }
    dogSpeak() {
      return super.speak(); // 调用父类的speak()方法,相当于dogSpeak() = Animal.prototype.speak(),并且内部的this指向当前调用的实例
    }
  }
}

class的继承是将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。也就是只有先调用super,才能够使用this. 注意:super 当方法super()使用时,会继承父类的构造函数,只能在子类的构造函数中使用; 当对象super.XXX使用时,指向父类的原型对象Animal.prototype; 如果super作为对象,用在静态方法之中,这时super将指向父类; 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例

参考资料

《ECMAScript 6入门》-- 阮一峰