String.prototype.replace = function () {
return 'hahaha';
};
但是,当某些浏览器或库的实现功能不足或性能滞后时,它可以发挥强大的作用。
它还有助于库的模块化、可扩展性和改进。如果你包含了某个人的库,并发现他们对某个方法的实现可以更好地优化,你可以插入他们的代码而不会篡改它,同时仍然有能力改进它或添加功能到它,并使所有在他们的库之外定义的对象受益(至少在你开始将它添加到原型时)。一个库甚至可以基于用户偏好交换实现(虽然这可能不是一个通常的好主意,因为它可能会干扰使用该方法的其他代码),或让他们动态定义他们想要使用的方法名称。
并且,即使在“类”内部,原型行为也会发挥作用,因为你可以利用直接存储在对象上的便利性(尽管在这种情况下它会增加内存,可能更好地创建一个新的类——但它仍然可以很方便)。
function Dog (type) {
if (type === 'poodle') {
this.bark = function () {
alert('(yapyapyap)');
};
}
}
Dog.prototype.bark = function () {
alert('(woof)');
};
var muffy = new Dog('poodle');
muffy.bark(); // '(yapyapyap)'
var rover = new Dog();
rover.bark(); // '(woof)'
function Creature () {}
Creature.prototype.respire = function () { return 'oooooh'; };
function createClass (o, f) {
f = f || function f () {}
f.prototype = (typeof o === 'function') ? o.prototype : o.constructor.prototype;
f.prototype.constructor = f;
return f;
}
var animals = ['Dog', 'Tiger', 'Lion', 'Frog', 'Kangaroo'];
animals.forEach(function (animal) {
window[animal] = createClass(Creature);
});
var rover = new Dog();
function createMixinClass (old, constructor, newMethods) {
if (typeof constructor === 'object') {
newMethods = constructor;
constructor = null;
}
var proto = old.prototype, constructor = constructor || function () {};
for (var m in proto) {
constructor.prototype[m] = proto[m];
}
for (var method in newMethods) {
if (!newMethods[method]) {
delete constructor.prototype[method];
}
else {
constructor.prototype[method] = newMethods[method];
}
}
return constructor;
}
var Cat = createMixinClass(Dog, {bark:null, meow: function () {alert('meow');}});
var kitty = new Cat();
这并不是一个“教学”示例,而是原型继承在实际应用中的例子。jQuery插件就是一种“真实世界”中的应用。$.fn
实际上是神奇的jQuery集合的原型,而jQuery插件会向其中添加方法,以便为任何jQuery集合添加功能。
在实际应用中有很多原型继承的例子。值得注意的是,jQuery使用它。每次进行jQuery选择时,都会使用委托原型来继承jQuery方法。它也被广泛应用于各种Web应用程序,包括Adobe的Creative Cloud平台,雅虎的许多产品等等...
事实上,JavaScript中的所有经典继承实现都实际上采用了原型继承来模拟经典继承--这只是为方便那些更熟悉经典继承而不是原型继承的程序员。原型继承非常灵活,可以轻松地使用原型继承来模仿经典继承的特性。反之则不然。
原型继承简单地意味着一个对象直接从另一个对象继承。下面是一个例子:
var switchProto = {
isOn: function isOn() {
return this.state;
},
toggle: function toggle() {
this.state = !this.state;
return this;
},
state: false
},
switch1 = Object.create(switchProto),
switch2 = Object.create(switchProto);
通常将Object.create()
调用放在工厂函数中,以使对象实例化更加方便。
经典继承存在许多问题,而原型继承则不存在这些问题,例如:
紧密耦合。继承是OO设计中最紧密的耦合形式。派生类对其祖先类有着深入的了解。
不灵活的层次结构(也称必要的重复)。单个父级层次结构很少能够描述所有可能的用例。最终,所有层次结构都对于新用例“错误”——这个问题需要代码重复。
多重继承很复杂。通常希望从多个父级继承。该过程非常复杂,其实现与单一继承的过程不一致,这使得阅读和理解更加困难。
脆弱的架构。由于紧密耦合,通常很难重构具有“错误”设计的类,因为许多现有功能依赖于现有设计。
猩猩/香蕉问题。通常,您不希望继承父级的某些部分。子类允许您覆盖父级中的属性,但它不允许您选择要继承的属性。
为了理解原型继承如何解决这些问题,您首先应该了解有两种不同类型的原型继承。JavaScript 支持两种:
委托。如果在实例上找不到属性,则会在实例的原型上搜索该属性。这使您可以在许多实例之间共享方法,从而免费提供享元模式。
拼接。动态添加属性到对象的能力使您可以自由地将任何属性从一个对象复制到另一个对象,全部或选择性地。
您可以结合这两种形式的原型继承来实现非常灵活的代码重用系统。实际上,它非常灵活,以至于使用原型轻松实现经典继承。反之则不然。
原型继承允许您使用大多数经典语言中的重要功能。在 JavaScript 中,闭包和工厂函数允许您实现私有状态,并且函数继承可以轻松地与原型组合,以添加支持数据隐私的混入。
松耦合。实例不需要直接引用父类或原型。虽然可以存储对象原型的引用,但这是不明智的,因为它会在对象层次结构中促进紧密耦合——这是经典继承最大的陷阱之一。
扁平化层次结构。使用串联和委托,使用原型面向对象编程可以轻松保持继承层次结构扁平 - 拥有单个级别的对象委托和单个实例,没有对父类的引用。
简单的多重继承。从多个祖先继承就像使用串联组合多个原型的属性来形成新对象或新对象的新委托那样容易。
灵活的架构。由于使用原型面向对象编程可以选择性地继承,因此您不必担心“错误设计”问题。新类可以从任意组合的源对象中继承任何组合的属性。由于层次结构变得扁平化的容易度,一个地方的更改不一定会导致整个后代对象链中的涟漪。
不再有大猩猩。选择性继承消除了大猩猩香蕉问题。
我不知道传统继承在原型继承方面有什么优势。如果有人知道,请启发我。