JS继承

62 阅读4分钟

对象

ECMA-62将对象定义为一组属性的无序集合

继承

ECMA-62把原型链定义为ECMAScript的主要主要继承方式。

其基本思想是通过原型继承多个引用类型的属性和方法

原型链继承

实现原型链设计如下代码模式:

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function (){
    return this.property;
}
function SubType(){
    this.subproperty = false;
}

SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true

原型链继承的问题:

1.原型中包含的引用值,会在所有实例间共享

在使用原型实现继承时,原型实际变成了另一个类型的实例

2.子类型在实例化时不能给父类型的构造函数传参

function Parent(name) {  
    this.name = name;  
}  
  
function Child(name) {  
    // 在这里,我们无法将参数传递给 Parent 的构造函数  
    Parent.call(this, name); // 这是一种解决方案,但这不是原型链继承的一部分了。  
}  
Child.prototype = new Parent(); // 我们无法给 Parent 的构造函数传参  
let child = new Child('John');  
console.log(child.name); // undefined,因为我们无法在 Child 的构造函数中给 Parent 的构造函数传递参数。

盗用构造函数(对象伪装)

为了解决原型包含引用值导致的继承问题

基本思路:

在子类构造函数中调用父类构造函数

function SuperType(name) {
    this.colors = ["red", "blue", "green"];
    this.name = name;
}
function SubType() {
    SuperType.call(this,"zhangsan") // 还可以传递参数
}

var instance1 = new SubType();

instance1.colors.push("black");

alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType();

alert(instance2.colors) //"red,blue,green"

解决了原型链继承引用值共享 与 子类实例化后无法给父类构造函数传参的问题

不足:

必须在构造函数中定义方法,因此函数不能复用,每个方法都需要在每个实例上重新创建一遍

组合继承

综合 原型链和盗用构造函数,将两者优点集中起来. 基本思路:

使用原型链继承原型上的属性和方法,通过构造函数继承实例属性

function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function (){
    console.log(this.name);
}
function SubType(name,age) {
    // 继承属性
    SuperType.call(this,name) // 还可以传递参数
    this.age = age;
}

SubType.prototype = new SuperType(); // 继承方法
SubType.prototype.asyAge = function (){
    console.log(this.age)
}
let instance1 = new SubType("zhangsan",29);

原型式继承

原型式继承适合:你有一个对象,想在它的基础上再创建一个新对象.

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

对于基本类型的属性(比如字符串、数字等),当我们在一个对象上设置这个属性的值时,JavaScript 会在该对象自身上创建一个新的属性,如果对象自身没有这个属性的话,而不会去修改原型链上的同名属性。

在ECMA5中添加Object.create()方法 这个方法接受两个参数:

1.作为新对象原型的对象 2.给新对象定义额外属性的对象

原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。 但要记住属性包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的

寄生式继承

思路:创建一个实现继承的函数以某种方式增强对象,然后返回这个对象

function createAnother(original){
    let clone = object(original); // 通过调用函数创建一个新对象
    clone.sayHi = function (){  // 以某种方式增强这个对象
        console.log('hi');
    };
    return clone;
}

不足:给对象添加函数导致函数难以复用,与构造函数雷士

寄生式组合继承

基本思路:

不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本

组合继承存在效率问题 父类构造函数始终会被调用两次

一次是在创建子类原型时调用 一次是在子类构造函数中调用

本质上,子类的原型最终是要包含超类对象的所有实例属性 子类构造函数只要在执行时重写自己的原型就行了

实现寄生是组合继承的核心逻辑

function inheritPrototype(subType,superType){
    let prototype = object(superType.prototype);
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; // 赋值对象
}
function SuperType(name) {  
    this.name = name;  
    this.colors = ["red", "blue", "green"];  
}  
  
SuperType.prototype.sayName = function() {  
    console.log(this.name);  
};  
  
function SubType(name, age) {  
    SuperType.call(this, name); // 借用构造函数继承属性  
    this.age = age;  
}  
  
// 创建一个新的对象,该对象的原型是 SuperType 的原型  
let prototype = Object.create(SuperType.prototype);   
prototype.constructor = SubType; // 增强对象,使其构造函数指向 SubType  
  
// 将新创建的对象赋值给 SubType 的原型  
SubType.prototype = prototype;   
  
let instance = new SubType("Bob", 23);  
instance.colors.push("black");  
console.log(instance.name);  // Bob  
console.log(instance.age);   // 23  
console.log(instance.colors); // ["red", "blue", "green", "black"]  
instance.sayName(); // "Bob" —— 成功访问到了父类原型中定义的方法