JS继承方式

81 阅读3分钟

一、原型链继承

function Person () {
    // 属性
    this.colors = ["red", "blue", "green"];
}

// 原型方法
Person.prototype.sayHello = function () {
    console.log("hello word!");
}

function Student () { }

// 继承了 Parent
Student.prototype = new Person();

// 创建实例对象 child1
var student1 = new Student();
student1.sayHello();  // hello word!

student1.colors.push("yellow");
console.log(student1.colors);  // ["red", "blue", "green", "yellow"]

// 创建实例对象 child2
var student2 = new Student();
console.log(student2.colors);  // ["red", "blue", "green", "yellow"]

优点:

  • 子类可以访问父类原型上的方法
  • 简单,易于实现

缺点:

  • 父类的属性子被所有实例共享;
  • 创建子类实例时,不能向父类构造函数中传参数

二、借用构造函数(伪造对象或经典继承)

使用 apply()call() 方法在新创建的对象上执行构造函数

function Person (name) {
    this.name = name;
}

Person.prototype.sayName = function () {
    console.log(this.name);
}

function Student (name) {
    Person.call(this, name);
}

var student1 = new Student("Pitt");
console.log(student1.name);  // Pitt
student1.sayName();  // 报错

优点:

  • 子类构造函数可以向父类构造函数中传递参数
  • 可以实现多继承(call / apply 多个父类对象)

缺点:

  • 子类无法访问父类原型上的方法

三、组合继承(伪经典继承)

将原型链和借用构造函数的技术组合到一块,既避免了两者的缺陷,又发挥了它们的优点。是JavaScript中最常用的继承模式。

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

Person.prototype.sayName = function () {
    console.log(this.name);
}

function Student (name, age) {

    // 继承属性
    Person.call(this, name);  //第二次调用 Person()

    this.age = age;
}

// 继承方法
Student.prototype = new Person();  // 第一次调用 Person()
Student.prototype.constructor = Student;
Student.prototype.sayAge = function () {
    console.log(this.age);
};

var student1 = new Student("Pitt", 18);
student1.colors.push("black");
console.log(student1.colors);  //["red", "blue", "green", "black"]
student1.sayName();  // Pitt
student1.sayAge();  // 18

var student2 = new Student("Bob", 20);
console.log(student2.colors);  //["red", "blue", "green"]
student2.sayName();  // Bob
student2.sayAge();  // 20

优点:

  • 可以继承父类原型上的属性,可以传参,可复用。
  • 每个新实例引入的构造函数属性是私有的。

缺点:

  • 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

四、寄生组合继承

前面说过,组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。我们再来看下组合继承的例子。

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

Person.prototype.sayName = function () {
    console.log(this.name);
}

function Student (name, age) {
    Person.call(this, name);  //第二次调用 Person()

    this.age = age;
}

Student.prototype = new Person();  // 第一次调用 Person()
Student.prototype.constructor = Student;
Student.prototype.sayAge = function () {
    console.log(this.age);
};

在第一次调用 Parent 构造函数时,Child.prototype 会得到两个属性:namecolors;它们都是 Parent 的实例属性,只不过现在位于 Child 的原型中。当调用 Child 构造函数时,又会调用一次 Parent 构造函数,这一次又在新对象上创建了实例属性 namecolors 。于是,这两个属性就屏蔽了原型中的两个同名属性。

下面来看下寄生组合式继承:

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

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

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

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.sayAge = function () {
    console.log(this.age);
}

趋近完美的继承方式。

五、ES6继承

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

class Person {
    constructor(name) {
        this.name = name;
    }
    
    // 方法写在constructor后面
    sayName() {
        console.log(this.name);
    }
}

// 通过extends关键字实现继承
class Student extends Person {
    constructor(name, age) {
        // 子类必须在constructor方法中调用super方法
        super(name);
        this.age = age;
    }
    
    sayAge() {
        console.log(this.age);
    }
}

let student1 = new Student("Pitt", 18);
student1.sayName();  // Pitt
student1.sayAge();  // 18