本文共 3707 字,大约阅读时间需要 12 分钟。
原型链是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;};var instance = new SubType();alert(instance.getSuperValue()); // 找到的结果是正确的 在这种情况下,SubType 类型继承了 SuperType 类型的所有属性和方法。需要注意的是,这种实现方式没有使用 SubType 的默认原型,而是手动替换了一个 SuperType 实例作为其原型。这样一来,每个 SubType 实例都共享同一个 SuperType 实例的属性和方法。
在原型链的查找过程中,当一个对象属性或方法不存在时,引擎会依次检查:
Object 原型)时停止所有引用类型都默认继承了 Object,这种继承同样是通过原型链实现的。要确保在重写或添加方法时,原型链不会被破坏。例如,替换原型对象后,子类型的方法和属性应该放在替换之后进行定义:
function SuperType() { this.property = true;}SuperType.prototype.getSuperValue = function() { return this.property;};function SubType() { this.subproperty = false;}SubType.prototype = new SuperType(); // 替换子类型的原型// 方法重写SubType.prototype.getSuperValue = function() { return false;};// 添加新方法SubType.prototype.getSubValue = function() { return this.subproperty;}; 然而,要小心处理原型对象的替换。如果在替换原型后使用对象字面量添加新方法,会导致原型链断裂,子类型实例就和超类型失去了关系:
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; } // 其他额外方法};var instance = new SubType();alert(instance.getSuperValue()); // Error! 这是因为 SubType.prototype 现在是一个字面量对象,而不是 SuperType 实例。为了避免原型链断裂,原型对象的替换必须谨慎进行。
在原型链模式中存在以下问题:
为了解决这些问题,可以使用借用构造函数的方式,让子类型的构造函数内部调用超类型的构造函数:
function SuperType(name) { this.name = name; this.colors = ["red", "green", "yellow"];}function SubType(name, age) { // 借用超类型的构造函数 SuperType.call(this, name); this.age = age;}var instance = new SubType("Alice", 23);alert(instance.name); // "Alice" 这种方式的优点是:
然而,借用构造函数的缺点是:
最常用的继承模式是组合继承,它结合了原型链和借用构造函数的优点:
function SuperType(name) { this.name = name; this.colors = ["red", "green", "yellow"];}SuperType.prototype.sayName = function() { alert(this.name);};function SubType(name, age) { // 借用超类型的构造函数 SuperType.call(this, name); this.age = age; // 原型链方式继承方法 SubType.prototype = new SuperType();}SubType.prototype.sayAge = function() { alert(this.age);};var instance1 = new SubType("Alice", 23);instance1.colors.push("black"); // ["red", "green", "yellow", "black"] 这样,子类型实例既有自己的实例属性,又可以使用超类型原型链中的共享属性和方法。这也是为什么许多现代JavaScript框架会选择组合继承作为默认的继承方式。
此外,ECMAScript 5引入了原型式继承的规范化方法 Object.create(),可以通过指定原型对象和属性对象来实现:
var person = { name: "Alice", friends: ["Shellby", "Court", "Van"]};var anotherPerson = Object.create(person);anotherPerson.name = "Rob";anotherPerson.friends.push("Room"); Object.create() 最大的优势是实现完全的原型式继承,而没有依赖构造函数。
最后,寄生式继承则是一种更高级的技术,它与工厂模式类似,通过创建一个函数来封装继承过程:
function createAnother(original) { var clone = Object(original); clone.sayHi = function() { alert("HI"); }; return clone;}var person = { name: "Alice", friends: ["Shelby"] };var anotherPerson = createAnother(person);anotherPerson.sayHi(); // "HI" 这种模式适合处理不直接涉及构造函数的情况。最终,通过结合原型链和借用构造函数的优点,寄生组合式继承成为了JavaScript中最优的继承方式。它通过原型链实现共享属性和方法,通过借用构造函数实现实例特有的属性,同时避免构造函数的重复调用。
原型链和组合继承模式是JavaScript中最常用的继承方式。这些模式在实际开发中通过instanceof和isPrototypeOf()等方法进行验证。选择恰当的继承模式对于确保代码优化和维护具有重要意义。
转载地址:http://vayoz.baihongyu.com/