为什么JavaScript原型设计?

时间:2011-01-10 19:13:59

标签: javascript

这可能会让你觉得这是一个语法不正确且可能是疯狂的问题,但这就是我的意思:当我试图在JavaScript中理解prototype的概念时,我遇到了一些稍微或多或少复杂版本的例子以下内容:

//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');

所以,关于我的问题:你为什么要这么做呢?为什么不将play函数放在Guitar开头?为什么声明一个实例然后开始添加方法?我能看到的唯一原因是,如果您希望myGuitar在最初创建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范例。

似乎prototype允许你做一些你原本无法做到的事情,但我没有充分的理由说明你为什么要这样做。

编辑:一些回复:

  • 当我说“为什么要声明一个实例然后再开始添加方法?”我更批评我看到的所有例子,按照我的第一个例子的顺序播放。当这个顺序改变时,如下面Harmen的回答,它确实在视觉上更有意义。但是,这并没有改变这样一个事实,就像我的第一个例子一样,你可以创建一个空对象函数构造函数,声明这个对象的100个实例,然后才定义原始对象实际是什么通过prototype提供方法和属性。也许通常这样做是为了暗示下面概述的Copy vs. Reference的想法。
  • 基于几个响应,这是我的新理解:如果将所有属性和方法添加到对象函数构造函数,然后创建该对象的100个实例,则获得所有属性和方法的100个副本。相反,如果将所有属性和方法添加到对象函数构造函数的prototype,然后创建该对象的100个实例,则会获得100个引用到单个(1)副本对象的属性和方法。这显然更快,效率更高,也是使用String的原因(除了改变Imagefunction 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'); 之类的内容,如下所述)。所以,为什么不这样做:

(项目符号列表会在它们之后突破任何代码,显然,所以我必须在这里添加一行单独的文本)

{{1}}

9 个答案:

答案 0 :(得分:24)

  

所以,关于我的问题:你为什么要这么做呢?你为什么不开始把吉他的播放功能?为什么声明一个实例然后开始添加方法?

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

您可以在定义Guitar的函数中声明该方法,但是,当您这样做时,每个新吉他都会获得其播放方法的自己的副本。当您开始创建吉他时,将它放在原型上会在运行时环境中更有效。每个实例共享相同的play方法,但是在调用时设置了context / scope,因此它在你的经典继承语言中扮演了一个适当的实例方法。

注意区别。在您发布的“为什么不是这种方式”示例中,每次创建新吉他时,都需要创建一个与其他播放方法相同的新播放方法。然而,如果游戏是在原型上,所有吉他都从相同的原型中借用,所以他们都共享相同的游戏代码。它是 x 吉他数量之间的差异,每个吉他都有相同的播放代码(所以你有 x 播放的副本)vs x 吉他共享的数量相同的游戏代码(无论有多少吉他,都可以播放1份)。权衡当然是在运行时游戏需要与调用它的对象相关联,但是javascript具有允许您非常有效且容易地执行该操作的方法(即call和{{ 1}}方法)

许多javascript框架定义了自己的用于创建“类”的实用程序。通常,它们允许您编写代码,就像您希望看到的示例一样。在幕后,他们正在为您准备原型功能。


编辑 - 在回答您更新的问题时,为什么不能做到?

apply

它与javascript如何使用'new'关键字创建对象有关。请参阅第二个答案here - 基本上在创建实例时,javascript创建对象,然后分配原型属性。所以this.prototype.play真的没有意义;事实上,如果你尝试它就会出错。

答案 1 :(得分:3)

如果您不使用原型,每次调用Guitar的构造函数时,您将创建一个新函数。如果您要创建很多吉他对象,您会发现性能有所不同。

使用原型的另一个原因是模仿经典继承。

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

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

答案 2 :(得分:3)

JavaScript是一种原型语言,是一种相当罕见的品种。这根本不是任意的,它是一种现场评估并且能够“评估”,动态修改和REPL的语言的要求。

与基于运行时“实时”类定义而非静态预定义类的面向对象编程相比,可以理解原型继承。

编辑:从以下链接中窃取的另一个解释也很有用。在面向对象语言(类 - >对象/实例)中,任何给定X的所有可能属性都在类X中枚举,并且实例为每个属性填充其自己的特定值。在原型继承中,您只描述现有X与现有X的引用之间的差异,但不同的实时Y,并且没有主副本

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

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

“eval”/ REPL的概念需要动态变量输入。您无法有效地实时编辑必须具有预定义的基于类的基于类的继承结构的环境。这是没有意义的,你可能只是预编译到汇编或字节码。

除此之外,我们还有原型继承,您可以在其中链接对象的INSTANCE属性。这个概念是,如果您处于全实时环境中,则类(静态,预定义构造)会受到不必要的限制。类是基于JavaScript中不存在的约束构建的。

通过这种策略,JavaScript基本上可以实现“活动”的一切。没有什么是禁止的,没有“定义和完成”的类,你永远不能触及。变量中没有“One True Scotsmen”比你的代码更安全,因为一切都遵循与你今天决定编写的代码相同的规则。

这种结果是显而易见的,也是以人为本的。它促使语言实现者在提供本机对象时使用轻松,高效的触摸。如果他们做得不好,那么暴徒就会篡夺平台并重建他们自己(阅读MooTools的来源,从字面上重新定义/重新实现一切,从功能和对象开始)。这就是旧版Internet Explorer等平台的兼容性。它促进了浅而窄,功能密集的库。深度继承导致最常用的部分被(轻松)挑选出来并成为最终的首选图书馆。当人们挑选和选择他们需要的东西时,宽图书馆会导致破裂,因为咬一口很容易,而不是像大多数其他环境那样不可能。

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

答案 3 :(得分:2)

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

//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不需要执行构造函数来创建变量,它只能使用原型的预定义变量。

对于一个类似于此问题的问题,see this speed test


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

function Guitar(){
  // constructor
}

Guitar.prototype = {
  play: function(a){
    alert(a);
  },

  stop: function(){
    alert('stop it');
  }
};

答案 4 :(得分:1)

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

答案 5 :(得分:1)

Javascript 基于原型。在罗马时,就像罗马人在JS中那样,使用原型继承。

它更有效,因为该方法在每个对象上都是继承的。如果它不是原型方法,那么该对象的每个实例都将拥有自己的play方法。当我们能够有效和自然地走向JS路线时,为什么选择低效路线和非传统路线?

答案 6 :(得分:1)

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

  

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

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

实际上,如果你考虑对象和引用是有意义的:与对象共享一个引用比在每个具有自己的对象副本的对象(对象)更好(以内存方式)在这种情况下,函数是play)。

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

答案 7 :(得分:1)

它基于Prototype创作设计模式。这个维基百科链接有一个很好的讨论。

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

答案 8 :(得分:-1)

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

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

所以最后每当我们需要一个可以成为其他构造函数的函数的函数时,我们应该在prototype中定义该特定函数并相应地使用:)