Javascript何时使用原型


93

我想了解何时在js中使用原型方法。应该一直使用它们吗?还是在某些情况下不优选使用它们和/或导致性能下降?

在此站点上搜索js中命名空间的常用方法时,似乎大多数都使用了基于非原型的实现:简单地使用对象或函数对象来封装名称空间。

来自基于类的语言,很难不尝试画出相似之处,并认为原型就像“类”,而我提到的命名空间实现就像静态方法。

Answers:


133

原型是一种优化

很好地使用它们的一个很好的例子是jQuery库。每次使用来获取jQuery对象时$('.someClass'),该对象都有数十种“方法”。该库可以通过返回一个对象来实现:

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这样的类提供了自己独特的实施接口的每一个成员。
  • 实现继承,您可以extendclass其中提供某些方法的默认实现。

在JavaScript中,原型继承是一种实现继承。因此,在某些情况下(在C#或Java中)您将从基类派生以获得默认行为,然后对这些行为进行一些小的修改以通过重写进行覆盖,然后在JavaScript中,原型继承才有意义。

但是,如果您处在使用C#或Java接口的情况下,则JavaScript不需要任何特定的语言功能。无需显式声明表示接口的内容,也无需将对象标记为“实现”该接口:

var duck = {
    quack: function() { ... }
};

duck.quack(); // we're satisfied it's a duck!

换句话说,如果对象的每个“类型”都有自己的“方法”定义,则从原型继承没有任何价值。之后,这取决于您为每种类型分配多少个实例。但是在许多模块化设计中,只有一个给定类型的实例。

实际上,许多人已经暗示实现继承是邪恶的。就是说,如果某个类型有一些通用的操作,那么也许不把它们放到基类/超级类中,而是将它们作为普通的函数公开在某个模块中,然后再将对象传递给它们就可以了。您希望他们继续操作。


1
很好的解释。那么您是否会同意,因为您认为原型是一种优化,所以它们总是可以用来改进您的代码?我想知道是否在某些情况下使用原型没有意义,或者实际上会导致性能下降。
opl

在后续工作中,您提到“这取决于您为每种类型分配的实例数”。但是您引用的示例没有使用原型。分配实例的概念在哪里(在这里您还会使用“ new”)吗?另外:说quack方法有一个参数-每次调用duck.quack(param)都会在内存中创建一个新对象(如果有参数则无关紧要)吗?
opl

3
1.我的意思是,如果有大量鸭子类型的实例,那么修改示例以使该quack函数处于原型中是很有意义的,许多鸭子实例都链接到该原型。2.对象文字语法{ ... }创建一个实例(无需new与之一起使用)。3.调用任何函数JS会导致在内存中创建至少一个对象-这就是所谓的arguments对象并将其存储在调用中传递的参数:developer.mozilla.org/en/JavaScript/Reference/...
丹尼尔·埃里克

谢谢,我接受了你的回答。但是,我对您的观点仍然有些困惑(1):我没有理解“大量的一种鸭子的实例”的意思。就像您在(3)中所说的那样,每次调用JS函数时,都会在内存中创建一个对象-因此,即使您只有一种类型的鸭子,也不会在每次调用鸭子函数时分配内存(在在哪种情况下使用原型总是有意义的?
opl

11
+1与jQuery的比较是对何时以及为什么使用已阅读的原型的第一个清晰明了的解释。非常感谢你。
GFoley83

46

如果要声明对象的“非静态”方法,则应使用原型。

var myObject = function () {

};

myObject.prototype.getA = function (){
  alert("A");
};

myObject.getB = function (){
  alert("B");
};

myObject.getB();  // This works fine

myObject.getA();  // Error!

var myPrototypeCopy = new myObject();
myPrototypeCopy.getA();  // This works, too.

@keatsKelleher,但是我们可以使用this示例this.getA = function(){alert("A")}对吗?只需在构造函数内部定义方法,即可为对象创建一个非静态方法?
Amr Labib

17

使用内置prototype对象的一个原因是,如果您将多次复制一个对象,这些对象将共享共同的功能。通过将方法附加到原型,您可以节省每个方法创建的重复方法new实例。但是,当您将方法附加到时prototype,所有实例都可以访问这些方法。

假设您有一个基Car()类/对象。

function Car() {
    // do some car stuff
}

然后创建多个Car()实例。

var volvo = new Car(),
    saab = new Car();

现在,您知道每辆汽车都需要驾驶,打开等。不必将方法直接附加到Car()类(每个创建的实例占用内存),您可以将方法附加到原型(仅创建方法)。一次),因此可以同时访问new volvo和的那些方法saab

// just mapping for less typing
Car.fn = Car.prototype;

Car.fn.drive = function () {
    console.log("they see me rollin'");
};
Car.fn.honk = function () {
    console.log("HONK!!!");
}

volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'

2
实际上这是不正确的。volvo.honk()无法工作,因为您完全替换了原型对象,而不是对其进行了扩展。如果您要执行这样的操作,它将像您期望的那样工作:Car.prototype.honk = function(){console.log('HONK');} volvo.honk(); //'HONK'–
29er

1
@ 29er-以我编写此示例的方式,您是正确的。顺序很重要。如果我将本示例保持不变,则Car.prototype = { ... }必须在调用new Car()jsfiddle中所示的jsfiddle 之前来:jsfiddle.net/mxacA。至于您的论点,这将是正确的方法:jsfiddle.net/Embnp。有趣的是,我不记得回答这个问题=)
hellatan 2012年

@hellatan可以通过设置构造函数Car来解决此问题,因为您用对象文字覆盖了prototype属性。
2014年

@josh感谢您指出这一点。我已经更新了答案,所以我不会像以前那样用对象文字覆盖原型。
hellatan

12

当您要创建大量特定类型对象的副本时,请将函数放在原型对象上,并且它们都需要共享共同的行为。这样,每个功能只有一个副本即可节省一些内存,但这只是最简单的好处。

更改原型对象上的方法或添加方法会立即更改相应类型的所有实例的性质。

现在,为什么要执行所有这些操作主要取决于您自己的应用程序设计,以及您需要在客户端代码中执行的各种操作。(一个完全不同的故事是服务器中的代码;更容易想象在其中执行更多大规模的“ OO”代码。)


所以当我用原型方法实例化一个新对象时(通过new关键字),那么该对象不会获得每个函数的新副本(只是一种指针)?如果是这种情况,为什么您不想使用原型?
opl

就像@marcel,d'oh ... =)
hellatan 2011年

@opi是的,您是对的-没有副本。取而代之的是,原型对象上的符号(属性名称)只是自然地“存在”作为每个实例对象的虚拟部分。人们不愿打扰的唯一原因是对象的生命周期短且与众不同,或者没有太多“行为”可分享。
Pointy

3

如果我用基于类的术语解释,那么Person是类,walk()是原型方法。因此,仅在您用此实例化新对象之后,walk()才会存在。

因此,如果您要像Person这样创建对象的副本,则可以创建许多用户。Prototype是一个很好的解决方案,因为它可以通过为内存中的每个对象共享/继承相同的函数副本来节省内存。

在这种情况下,static并不是很大的帮助。

function Person(){
this.name = "anonymous";
}

// its instance method and can access objects data data 
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}

var userOne = new Person();
var userTwo = new Person();

//Call instance methods
userOne.walk();

//Call static methods
Person.ProcessPerson(userTwo);

因此,它更像实例方法。对象的方法类似于静态方法。

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.