Skip to content

重学JS-继承 #9

Open
Open
@jappp

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。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions