原型是一种优化。
很好地使用它们的一个很好的例子是 jQuery 库。每次使用$('.someClass') 获取一个jQuery 对象时,该对象都有几十个“方法”。该库可以通过返回一个对象来实现:
return {
show: function() { ... },
hide: function() { ... },
css: function() { ... },
animate: function() { ... },
// etc...
};
但这意味着内存中的每个 jQuery 对象都会有几十个包含相同方法的命名槽,一遍又一遍。
相反,这些方法是在原型上定义的,所有 jQuery 对象都“继承”该原型,以便以极少的运行时成本获得所有这些方法。
jQuery 正确处理的一个非常重要的部分是它对程序员是隐藏的。它被视为纯粹的优化,而不是您在使用该库时必须担心的事情。
JavaScript 的问题在于,裸构造函数要求调用者记住在它们前面加上 new,否则它们通常不起作用。这没有充分的理由。 jQuery 将这些废话隐藏在一个普通函数 $ 后面,因此您不必关心对象是如何实现的。
为了方便您创建具有指定原型的对象,ECMAScript 5 包含一个标准函数Object.create。它的一个大大简化的版本如下所示:
Object.create = function(prototype) {
var Type = function () {};
Type.prototype = prototype;
return new Type();
};
它只是解决了编写构造函数然后用new调用它的痛苦。
您何时会避免使用原型?
一个有用的比较是与流行的 OO 语言,如 Java 和 C#。它们支持两种继承:
-
接口继承,您可以在其中
implement 和 interface 以便类为接口的每个成员提供其自己的唯一实现。
-
实现继承,其中
extend class 提供某些方法的默认实现。
在 JavaScript 中,原型继承是一种实现继承。因此,在那些情况下(在 C# 或 Java 中)您将从基类派生以获得默认行为,然后您通过覆盖对其进行小幅修改,然后在 JavaScript 中,原型继承是有意义的。
但是,如果您在 C# 或 Java 中使用接口的情况,那么您不需要 JavaScript 中的任何特定语言功能。无需显式声明表示接口的内容,也无需将对象标记为“实现”该接口:
var duck = {
quack: function() { ... }
};
duck.quack(); // we're satisfied it's a duck!
换句话说,如果对象的每个“类型”都有自己的“方法”定义,那么从原型继承就没有任何价值。之后,这取决于您为每种类型分配的实例数。但在许多模块化设计中,给定类型只有一个实例。
事实上,it has been suggested by many people that implementation inheritance is evil。也就是说,如果一个类型有一些常见的操作,那么如果它们不被放入基类/超类中,而是在某个模块中作为普通函数公开,那么可能会更清楚,你将对象传递给这些模块您希望它们进行操作。