为什么是JavaScript原型?

这可能会引起你一个语法不正确,可能是疯狂的问题,但这是我的意思:当试图理解JavaScript的prototype的概念,我遇到的例子是略微复杂或更less的版本:

 //Guitar function constructor function Guitar(color, strings) { this.color = color; this.strings = strings; } //Create a new instance of a Guitar var myGuitar = new Guitar('Black', ['D', 'A', 'D', 'F', 'A', 'E']); //Adding a new method to Guitar via prototype Guitar.prototype.play = function (chord) { alert('Playing chord: ' + chord); }; //Now make use of this new method in a pre-declared instance myGuitar.play('D5'); 

所以,在我的问题上:为什么你会想这样做? 你为什么不把GuitarplayfunctionGuitar呢? 为什么要声明一个实例,然后开始添加方法? 我能看到的唯一原因是,如果你希望我的myGuitar在最初创build时不能play ,但是我不能拿出一个例子来说明为什么你会想要这样的东西。

似乎这样做会更有意义:

 function Guitar(color, string) { this.color = color; this.strings = strings; this.play = function (chord) { alert('Playing chord: ' + chord); }; } var myGuitar = new Guitar('White', ['E', 'A', 'D', 'G', 'B', 'E']); myGuitar.play('E7#9'); 

这里真正的问题是,第二个例子对我来说是合理的,而第一个例子没有,而实际上,第一个例子可能由于某些原因而更好。 不幸的是,我所发现的每一个教程都只是通过使用prototype的步骤,而不是为什么prototype范例存在的原因。

看起来, prototype允许你做一些你不能做的事情,但是我不能想出为什么要这么做。

编辑:一些回应:

  • 当我说“为什么要声明一个实例,然后开始添加方法?” 我更多的批评了我看到的第一个例子中所有的例子。 当这个顺序发生变化时,正如Harmen在下面的回答中所做的那样,它在视觉上的确有点意义。 然而,这并没有改变这个事实,就像我的第一个例子一样,你可以创build一个空的对象函数的构造函数,声明这个对象的100个实例,然后只有通过给它定义实际的原始对象方法和属性通过prototype 。 也许这通常是通过这种方式来暗示下面列出的复制与参考思想。
  • 基于几个响应,下面是我的新理解:如果将所有的属性和方法添加到对象函数构造函数中,然后创build该对象的100个实例,则会获得所有属性和方法的100个副本。 相反,如果将所有属性和方法添加到对象函数构造函数的prototype中,则创build该对象的100个实例,则会获得100个对该对象属性和方法的单个(1)副本的引用 。 这显然更快,更高效,这就是为什么要使用prototype (除了改变像StringImage类的东西,如下所述)。 那么,为什么不这样做呢:

(项目符号列表后面的任何代码都会被打断,显然,所以我必须在这里添加一行单独的文本)

 function Guitar(color, strings) { this.prototype.color = color; this.prototype.strings = strings; this.prototype.play = function (chord) { alert('Playing chord: ' + chord); }; } var myGuitar = new Guitar('Blue', ['D', 'A', 'D', 'G', 'B', 'E']); myGuitar.play('Dm7'); 

所以,在我的问题上:为什么你会想这样做? 你为什么不把吉他的播放function放在首位呢? 为什么要声明一个实例,然后开始添加方法?

Javascript不是一种“经典”inheritance语言。 它使用原型inheritance。 它就是这样。 既然如此,在“类”上创build方法的正确方法是将该方法放在原型上。 请注意,我把'class'放在引号中,因为严格来说JS没有'class'的概念。 在JS中,您将处理定义为函数的对象。

您可以在定义吉他的函数中声明该方法,但是当您这样做时,每一个新的吉他都会获得自己的播放方法副本。 当您开始创build吉他时,在运行环境中将它放在原型上效率更高。 每个实例共享相同的播放方法,但是在调用时设置上下文/作用域,因此它在您的经典inheritance语言中用作适当的实例方法。

注意不同之处。 在你发布的“为什么不这样做”的例子中,每当你创build一个新的吉他,你需要创build一个新的播放方法,就像其他播放方法一样。 但是,如果玩的是原型,所有的吉他都从同一个原型中借用,所以他们都使用相同的代码进行游戏。 它是x个吉他之间的差别,每个吉他都有相同的播放代码(所以你有x个播放的副本)与x个吉他共享相同的播放代码(无论多less个吉他都是1个播放副本)。 当然,在运行时玩游戏需要与它被调用的范围相关联的对象相关联,但是JavaScript有一些方法可以让你非常高效和容易地完成这个任务(即callapply方法)

许多javascript框架定义了自己的工具来创build“类”。 通常他们允许你写代码,就像你说的你想看到的例子。 在幕后,他们正在把function放在原型上。


编辑 – 回答你的更新问题,为什么不能做

 function Guitar() { this.prototype.play = function().... } 

它与JavaScript如何使用“新”关键字创build对象有关。 看到第二个答案在这里 – 基本上当你创build一个实例,JavaScript创build对象, 然后分配原型属性。 所以这个原型没有意义。 事实上,如果你尝试它,你会得到一个错误。

作为开始之前的说明 – 我在这里使用ECMAScript而不是JavaScript,因为ActionScript 1和2在运行时performance出完全相同的行为。

我们这些在一个更传统的面向对象的世界(阅读Java / C#/ PHP)中工作的人发现,在运行时扩展一个类几乎是完全陌生的。 我的意思是,这应该是我的目标。 我的对象将会出现,并且已经被设定好了。 子类别EXTEND其他类。 它有一个非常结构化的,坚实的,石头的感觉。 而且,大多数情况下,这个工作起来很合理。 (这也是高斯林所主张的原因之一,我认为我们大多数人会认为这是非常有效的,因为它适合于庞大的系统)

另一方面,ECMAScript遵循更为主要的面向对象的概念。 在ECMAScript中,类inheritance完全相信与否,是一个巨大的装饰模式。 但是,这不仅仅是您可能会说的C ++和Python中的装饰器模式(您可以轻松地说这些是装饰器)。 ECMAScript允许您将一个类的原型分配给一个实例。

想象一下这发生在Java中:

 class Foo { Foo(){} } class Bar extends new Foo() { // AAAHHHG!!!! THE INSANITY! } 

但是,这正是ECMAScript中可用的(我相信Io也允许这样的事情,但不要引用我)。

我之所以说这是原始的,是因为这种devise理念与McCarthy使用Lambda微积分实现Lisp的方式非常相关。 它比closures的想法更多,比如Java OOP。

所以,当天早些时候,Alonzo教堂写了The Calculi Lambda Conversion ,这是Lambda Calculus的开创性着作。 其中他提出了两种查看多元论证函数的方法。 首先,它们可以被认为是接受单例,元组,三元组等的函数。基本上f(x,y,z)将被理解为接受参数(x,y,z)的f。 (顺便说一句,这是我认为这是Python的参数列表结构的主要推动力,但这是猜测)。

另一个(也是为了我们的目的(说实话,教会的目的)更重要)的定义是由麦卡锡挑选出来的。 f(x,y,z)应该被翻译成f(xg(yh(z)))。 最外层方法的parsing可能来自内部函数调用产生的一系列状态。 这个存储的内部状态是封闭的基础,而这又是现代OOP的基础之一。 闭包允许在不同点之间传递封闭的可执行状态。

“土地的Lisp:

 ; Can you tell what this does? It it is just like your favorite ; DB's sequence! ; (getx) returns the current value of X. (increment) adds 1 to x ; The beauty? Once the let parens close, x only exists in the ; scope of the two functions! passable enclosed executable state! ; It is amazingly exciting! (let (x 0) ; apologies if I messed up the syntax (defun increment ()(setf x (+ 1 x))) (defun getx ()(x))) 

现在,这与ECMAScript与Java有什么关系呢? 那么,在ECMAScript中创build一个对象时,它可以精确地遵循这个模式:

  function getSequence() { var x = 0; function getx(){ return x } function increment(){ x++ } // once again, passable, enclosed, executable state return { getX: getX, increment:increment} } 

这里是原型开始进入的地方。ECMAScript中的inheritance意味着“从对象A开始并添加到它”。它不会复制它。 它需要这个神奇的状态,ECMAScript将其附加。 这是为什么它必须允许MyClass.prototype.foo = 1来源和顶峰。

至于为什么你会追加方法“事后”。 大多数情况下,它归结为真正的风格偏好。 在原定义内部发生的一切事情,只不过是在外面发生的相同types的装饰。

在大多数情况下,将所有的定义放在同一个地方是有益的,但有时这是不可能的。 例如,jQuery扩展基于直接附加jQuery对象原型的思想。 Prototype库实际上具有扩展一致使用的类定义的专门方法。

如果我正确地记得Prototype.js,它是这样的:

  var Sequence = function(){} // Object.extend takes all keys & values from the right object and // adds them to the one on the left. Object.extend( Sequence.prototype, (function() { var x = 0; function getx(){ return x } function increment(){ x++ } return { getX: getX, increment:increment} })()); 

至于在原始定义里面使用原型关键字,那么在大多数情况下,这不起作用,因为“this”是指被定义的对象实例(在构造实例的时候)。 除非实例也有一个“原型”属性,否则这个原型必定是未定义的!

由于原来定义中的所有内容都是该对象的实例,所以修改this就足够了。 但是,(我微笑着,我说这是因为它与原型一起),每个都有一个constructor属性。

  // set the id of all instances of this “class”. Event those already // instantiated... this.constructor.prototype.id = 2 console.log( this.id ); 

如果你不使用原型,每次调用吉他的构造函数时,都会创build一个新的函数。 如果您创build了很多吉他对象,您将会注意到性能的差异。

使用原型的另一个原因是模仿古典inheritance。

 var Instrument = { play: function (chord) { alert('Playing chord: ' + chord); } }; var Guitar = (function() { var constructor = function(color, strings) { this.color = color; this.strings = strings; }; constructor.prototype = Instrument; return constructor; }()); var myGuitar = new Guitar('Black', ['D', 'A', 'D', 'F', 'A', 'E']); myGuitar.play('D5'); 

在这个例子中,吉他扩展了乐器,因此具有“播放”function。 如果您愿意,您也可以在吉他中覆盖乐器的“播放”function。

JavaScript是一种原型语言,是一种相当稀有的品种。 这不是任意的,这是一个现场评估和能够“评估”,dynamic修改和REPL语言的要求。

原型inheritance可以被理解为与基于运行时“实时”类定义而不是静态预定义类的面向对象编程相比。

编辑:从以下链接中偷取的另一个解释也是有用的。 在面向对象的语言(类 – >对象/实例)中,任何给定的X的所有可能的属性在X类中被枚举,并且一个实例为它们中的每一个填充它自己的特定值。 在原型inheritance中,你只能描述现有的X和类似但是不同的Y的参考之间的区别,并且没有主副本

http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html

首先你需要了解上下文。 JavaScript是一种可执行的解释型语言,可在实时环境中进行修改。 程序的内部结构本身可以在运行时修改。 这与任何编译语言,甚至CLR链接语言(如.net)都有不同的限制和优势。

“eval”/ REPL的概念需要dynamicvariablesinput。 你不能有效地生活编辑一个环境,你必须有预定义的单一的基于类的inheritance结构。 这是毫无意义的,你也可以预编译成汇编或字节码。

相反,我们有原型inheritance链接的对象的实例的属性。 这个概念是,如果你在一个全生命的环境,类(静态,预定义的结构)是不必要的限制。 类是基于JavaScript中不存在的约束构build的。

有了这个策略,JavaScript基本上就是“活”的一切。没有什么是禁止的,没有“定义和完成”的课程是你永远不能触及的。 在比你的代码更加圣洁的variables中,没有“一个真正的苏格兰人”,因为所有的东西都遵循与你今天要写的代码相同的规则。

这个结果是显而易见的,也是非常基于人的。 它推动语言实施者在提供本地对象时使用轻而有效的触摸。 如果他们做得不好,他们只会篡夺平台并重build自己的(阅读MooTools的来源,它从字面上重新定义/重新实现了一切,从Function和Object开始)。 这就是如何将兼容性带入像旧的Internet Explorer版本这样的平台。 它促进图书馆是浅而窄,function密集。 深度inheritance导致最常用的部分(很容易)挑选樱桃,成为最终的去库。 随着人们挑选和select他们需要的部分,宽泛的库会导致压裂,因为在大多数其他环境中,咬人是很容易的,而不是不可能的。

微型图书馆的概念在JavaScript中是独一无二的,它绝对可以追溯到语言的基础。 它鼓励在人类消费方面的效率和简洁性,而不是我所知的其他语言所促进的。

你给出的第一种方法更快,当你以另一种顺序写时,它确实开始有意义:

 //Guitar function constructor function Guitar(color, strings) { this.color = color; this.strings = strings; } Guitar.prototype.play = function (chord) { alert('Playing chord: ' + chord); }; var myGuitar = new Guitar('Black', ['D', 'A', 'D', 'F', 'A', 'E']); 

这是因为Javascript不需要执行构造函数来创buildvariables,它可以使用原型的预定义variables。

对于一个certificate, 看到这个速度testing在一个很像这个问题。


也许这个替代版本对你更有意义:

 function Guitar(){ // constructor } Guitar.prototype = { play: function(a){ alert(a); }, stop: function(){ alert('stop it'); } }; 

首先,您可以使用原型扩展构build到JavaScript语言中的对象(如String)。 我更喜欢自定义对象的第二个例子。

Javascript是原型的基础 。 在罗马时,如同罗马人在JS时那样做,使用原型inheritance。

这样更有效,因为方法在每个对象上都被inheritance了。 如果不是原型方法,那么每个对象的每个实例都会有自己的play方法。 为什么走JS效率低下的路线和非传统路线呢?

你已经有很多很好的答案,这就是为什么我没有通过你所有的观点。

为什么要声明一个实例,然后开始添加方法?

这是不正确的。 原型对象独立于每个实例存在。 它是函数对象 (构造函数)的一个属性
当你创build一个新的实例时,它会“inheritance”原型中的所有属性(实际上它有一个对它的引用)。

实际上,如果考虑对象和引用,这是有道理的: 分享一个对象的引用比每个拥有自己的对象副本的实例(在这种情况下,对象是函数play )更好(记忆方式)。

至于为什么它是基于原型的:你也可以问为什么不同的语言范式存在(function性,oo,声明性)。 不只有一个正确的做法。

它是基于原型的创造性devise模式。 这个维基百科链接有一个很好的讨论。

http://en.wikipedia.org/wiki/Prototype_pattern

兄弟,让我问你一件事,如果你有吉他,卡西欧,小提琴和你想在这些乐器的每一个演奏相同的和弦。

所以我想为什么我们不单独保留一个函数play_chord,并使用这个(play_chord)函数与上述任何一种仪器,而不是使用吉他,casio或小提琴内的每个function。

所以最后每当我们需要一个函数可能是其他构造函数的一部分,那么我们应该在原型中定义该特定的function,并相应地使用:)