Skip to content

重学JS-继承 #9

Description

@jappp

一、原型链继承

基本思想:重写原型对象,将父类实例赋值给子类原型对象。
原来存在于父类实例中的所有属性和方法,现在也存在于子类原型中了。

function Parent () {
  this.property = false;
}
Parent.prototype.getSuperValue = function () {
  return this.property;
}

function Child () {
  this.subProperty = true;
}

Child.prototype = new Parent();
Child.prototype.getSubValue = function () {
  return this.subProperty;
}

var child1 = new Child();
console.log(child1.getSuperValue()) // false
console.log(child1.getSubValue()) // true

原型链: child1.proto -> Child.prototype Child.prototype.proto -> Parent.prototype

缺点:

  1. 子类原型的属性会被所有子类实例共享,所以引用类型属性的变动会影响所有子类实例
  2. 创建子类实例时无法向父类的构造函数传参

二、借用构造函数继承

基本思想:在子类构造函数的内部调用父类构造函数

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {
    Parent.call(this);
}

var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]

缺点:

  1. 方法都在构造函数中定义,无法复用
  2. 不能继承原型属性和方法

三、组合继承

基本思想:用原型链实现原型属性和方法的继承,借用构造函数实现对实例属性的继承

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

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

function Child (name, age) {
    // 创建子类实例时,第二次调用父类构造函数
    Parent.call(this, name);
    this.age = age;
}

// 第一次调用父类构造函数
Child.prototype = new Parent();
// 重新赋值子类原型之后,需要重写constructor指向自身
Child.prototype.constructor = Child;

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

缺点:会调用两次父类构造函数,给子类实例和子类原型重复写入属性和方法。

四、原型式继承

基本思想:将传入对象作为构造函数实例指向的原型,本质是对传入对象进行了一次浅拷贝。
返回构造函数的实例instance。instance.proto === F.prototype === obj;
ES5通过object.reate()规范了原型式继承

function object(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

var person = {
  name: "nike",
  friends: ["a", "b"],
}

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push("c");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'linda';
yetAnotherPerson.friends.push("d");
console.log(person.friends); // ["a", "b", "c", "d"]

缺点:引用类型的值会被子类实例改变,无法传递参数。

五、寄生式继承

基本思想:类似工厂模式,在原型式继承基础上增强对象,返回构造函数

function createAnother(original) { 
    // 通过调用 object() 函数创建一个新对象 
    var clone = object(original); 
    clone.sayHi = function(){ 
        // 以某种方式来增强对象 
        alert("hi"); 
    }; 
    return clone; // 返回这个对象
}

缺点:同原型式继承,只是为实例增加属性和方法。

六、寄生组合式继承

基本思路:主要是省略掉子类原型赋值,不必为了指定子类原型而调用父类构造函数,我们所需要的的只是父类原型的副本。
本质:使用寄生式继承来继承父类的原型,再将结果指定给子类的原型

function inheritPrototype(child, parent) {
    // 创建父类原型对象的副本
    var prototype = Object.create(parent.prototype);
    // 弥补因重写原型而丢失的constructor
    prototype.constructor = child;
    // 新创建的对象赋值给子类原型
    child.prototype = prototype;
}

// 父类初始化实例属性和原型属性
function Parent() {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = function(){
  alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function Child(name, age){
  Parent.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(Child, Parent);

// 新增子类原型属性
Child.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new Child("xyc", 23);
var instance2 = new Child("lxy", 23);

原型链: instance.proto -> Child.prototype Child.prototype.proto -> Parent.prototype

相比之前的组合继承,只调用了一次父类构造函数,避免了重复属性和方法的创建。

七、ES6继承

通过class创建父类,子类通过extends继承父类
extends 原型链接 B.prototype.proto = A.prototype;
super()子类调用父类构造方法,继承父类属性

class A {
    constructor(name) {
        this.name = name;
    }
}

class B extends A {
    constructor() {
        // 执行A构造函数,
       // super()内部的this指向的是B, 即A.prototype.constructor.call(this)
        super();
    }
}

ES5继承和ES6继承的区别:

ES5的继承是先new创建子类实例对象,然后再将父类方法添加到this上
ES6的继承不同点在于是先创建父类的实例对象this,然后再用子类的构造函数修改this。所以子类构造函数使用this之前必须先调用super()方法创建this。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions