《JavaScript高级程序设计(第3版)》第6章 面向对象的程序设计总结二

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010011236/article/details/86099351

6.3、继承

ES只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

6.3.1、原型链

利用原型让一个引用类型继承另一个引用类型的属性和方法。

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

原型链基本概念:让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。

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;
};

var instance = new SubType();
alert(instance.getSubValue());    // true
alert(instance.getSuperValue());    // true

上面代码的图解如下所示:

调用 instance.getSuperValue() 会经历三个搜索步骤:1)搜索实例;2)搜索 SubType.prototype;3)搜索 SuperType.prototype,最后一步才会找到该方法。

1、别忘记默认的原型

所有类型默认都继承了 Object,而这个继承也是通过原型链实现的(所有函数的默认类型都是 Object 的实例)。具体如下图所示:

2、确定原型和实例的关系

可以通过两种方式来确定原型和实例之间的关系:

第一种:使用 instanceof 操作符,

// 由于原型链的关系,可以说 instance 是
//  Object、SuperType、SubType中任何一个类型的实例
alert(instance instanceof Object);  // true
alert(instance instanceof SuperType);   // true
alert(instance instanceof SubType); // true

第二种:使用 isPrototypeOf() 方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此 isPrototypeOf() 方法也会返回 true。

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

3、慎重地定义方法

注意:给原型添加方法的代码一定要放在替换原型的语句之后。

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

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

// 继承 SuperType
SubType.prototype = new SuperType();

// 添加新方法
SubType.prototype.getSubValue = function() {
    return this.subprototype;
};

// 重写超类型中的方法
// 会屏蔽原来的那个方法
SubType.prototype.getSuperValue = function() {
    return false;
};

var instance = new SubType();
alert(instance.getSubValue());    // false
alert(instance.getSuperValue());  // false

注意:在通过原型链实现继承时,不能使用对象字面量创建原型方法。

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

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

// 继承 SuperType
SubType.prototype = new SuperType();

// 使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubValue : function() {
        return this.subprototype;
    },

    someOtherMethod : function() {
        return false;
    }
};

var instance = new SubType();
alert(instance.getSuperValue()); // is not a function

4、原型链的问题

问题一:来自包含引用类型值的原型。

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

function SubType() {
}

// 继承了SuperType
// 继承之后相当于 SubType.prototype.colors = ['red','blue','green'];
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"

var superType = new SuperType();
superType.colors.push('aa');
alert("superType colors are : " + superType.colors);    // "red,blue,green,aa"

问题二:在创建子类型的实例时,不能向超类型的构造函数中传递参数。

6.3.2、借用构造函数

为解决原型中包含引用类型值所带来的问题,可以使用 借用构造函数 的技术来解决。即在子类型构造函数的内部调用超类型构造函数。

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"

1、传递参数

借用构造函数:可以在子类型构造函数中向超类型构造函数传递参数。

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

function SubType() {
    // 继承了 SuperType,同时还传递了参数
    // 调用超类型构造函数
    SuperType.call(this, 'Nicholas');

    // 相同的属性名会覆盖超类的同名属性
    // this.name = 'Greg';

    // 实例属性
    this.age = 29;
}

var instance = new SubType();
alert(instance.name);   // 'Nicholas'
alert(instance.age);    // 29

2、借用构造函数的问题

无法避免构造函数模式中存在的问题——方法都在构造函数中定义,函数无法复用。

6.3.3、组合继承

组合继承(又称为 伪经典继承):将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green', 'blue'];
}
SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    // 继承超类 SuperType
    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,green,blue,black"
instance1.sayName();        // 'Nicholas'
instance1.sayAge();         // 29

var instance2 = new SubType('Greg', 30);
alert(instance2.colors);    // "red,green,blue"
instance2.sayName();        // 'Greg'
instance2.sayAge();         // 30

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

6.3.4、原型式继承

借助原型可以基于已用的对象创建新对象,同时还不必因此创建自定义类型。

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

var person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');  // "Shelby,Court,Van,Rob"
alert(anotherPerson.name);          // 'Greg'

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(yetAnotherPerson.name);   // 'Nicholas'

alert(person.friends);  // "Shelby,Court,Van,Rob,Barbie"

ES5通过新增 Object.create() 方法规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create() 与 object() 方法的行为相同。

var person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = Object.create(person);
alert(yetAnotherPerson.name);   // 'Nicholas'
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

alert(person.friends);  // "Shelby,Court,Van,Rob,Barbie"

Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性

var person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = Object.create(person, {
    name: {
        value: 'Greg'
    }
});

alert(anotherPerson.name);  // 'Greg'

如果只想让一个对象与另一个对象保持类似的情况下,原型继承是完全可以胜任的。包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

6.3.5、寄生式继承

寄生式继承:即创建一个仅用于封装过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

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

    return clone;   // 返回这个对象
}

var person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();  // 'Hi'

6.3.6、寄生组合式继承

组合继承最大的问题就是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
// SuperType的原型
SuperType.prototype.sayName = function() {
    alert(this.name);
};

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

    this.age = age;
}

// 第一次调用 SuperType()
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    alert(this.age);
};

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。

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);
};

参考文献

[1]《JavaScript高级程序设计(第3版)》

猜你喜欢

转载自blog.csdn.net/u010011236/article/details/86099351