JS 6.3 继承

6.3 继承

JS不支持接口继承,只支持实现继承,因为函数没有签名

原型链

原型链是继承的主要方法

每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针;

让原型对象等同于另一个类型的实例,这样原型对象将包含一个指向另一个原型的指针,相应地,另一个原型也包含着一个指向另一个构造函数的指针,如此层层递进,就构成了实例与原型的链条;

所有对象都继承object所以都拥有toString(),valueOf()这样的默认方法

instanceof和isPrototypeOf()可以判断原型与实例之间的关系

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

使用字面量添加新方法就会重写原型链

function SuperType() {
    this.property = true;
}

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

function SubType() {
    this.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubValue: function () {
        return this.subproperty;
    },
    someOtherMethod: function () {
        return false;
    }
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!

原型链的问题

包含引用类型值的原型

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

function SubType() {
}

//继承了SuperType
SubType.prototype = new SuperType();
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,black"

在创建子类型的实例时,不能向超类型的构造函数中传递参数;

实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数;

基于这两点原因,实践中很少会单独使用原型链;

借用构造函数

也叫伪造对象或经典继承;

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

function SubType() {
//继承了SuperType
    SuperType.call(this);
}

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"

通过call()或apply()在新创建的实例的环境中调用了SuperType构造函数,这样每个SubType实例都有自己的colors副本了;

借用构造函数的问题

回顾6.2构造函数的问题,这里也一样,要么无法函数复用要么全是全局函数;

在超类型的原型中定义的方法对子类型是不可见的,所有类型都只能用构造函数模式;

基于这两点原因,实践中很少会单独使用借助构造函数;

组合继承

也叫伪经典继承;

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

SuperType.prototype.sayName = function () {
    alert(this.name);
};

function SubType(name, age) {
//继承属性
    SuperType.call(this, name);
    this.age = age;
}

//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript 中最常用的继承模式;

而且,instanceof 和isPrototypeOf()也能够用于识别基于组合继承创建的对象;

但是,无论什么情况下,都会调用两次超类型构造函数:一次在创建子类型原型的时候,另一次在子类型构造函数内部;

原型式继承

举例

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
alert(anotherPerson.name); //"Greg"

包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样;

寄生式继承

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式;

示范继承模式时使用的object()函数不是必需的;

任何能够返回新对象的函数都适用于此模式;

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

寄生组合式继承

举例

function object(o) {
    function F() {
    }

    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType) {
    var 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 () {
    alert(this.name);
};

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
    alert(this.age);
};

拷贝一个超类型,增加constructor属性,赋值给子类型;

这样就避免了像组合继承一样调用两次超类型构造函数;

同时还能使用instanceof和isPrototypeOf();

是实现基于类型继承的最有效方式;