在构造函数和原型中声明 javascript 对象方法

在创建 javascript 对象时,我可以将方法声明放在构造函数或原型中。例如,假设我想要一个 Dog 类,它具有 Name 属性和 Bark 方法。我可以将 Bark 方法的声明放到构造函数中:

var Dog = function(name) {
this.Name = name;
this.Bark = function() {
alert(this.Name + " bark");
};
}

或者我可以把它作为原型对象的一个方法:

var Dog = function(name) {
this.Name = name;
}


Dog.prototype.Bark = function() {
alert(this.Name + " bark");
};

当我实例化 Dog 类型的对象时,这两种方法似乎都能很好地工作:

var dog = new Dog("Fido");
dog.Bark();  //Both approaches show "Fido bark"

我是否应该选择这两种方法中的一种?使用其中一种方法有什么好处吗?在幕后,这两种方法最终会完全做同样的事情吗?大多数人倾向于哪种方法?

谢谢你的帮助。

79269 次浏览

我看到的绝大多数 javascript 代码都使用原型方法。我认为有三个原因,我可以想到我的头顶。

首先,你要避免让每个类都成为一个庞大的构造函数,构造函数逻辑放在构造函数中,其他方法的逻辑在其他地方声明——这主要是一个清晰性/关注点分离的问题,但是在 javascript 中,你需要每一点你可以掌握的清晰性。

第二是效率。当你在构造函数中声明方法时,你是在为对象的每个实例创建一个函数对象的新实例,并且将构造函数的作用域绑定到这些函数中的每一个(也就是说,它们可以引用,例如,构造函数的参数,只要对象存在,它就永远不能被 gc’d)。在原型上声明方法时,所有实例都会使用函数对象的一个副本——原型属性不会复制到实例上。

第三个原因是,当您使用原型方法时,您可以以各种方式“扩展”类,例如 Backbone.js 和 CoffeeScript 的类构造所使用的原型链接。

对于您给出的示例,您应该使用原型方法。一般来说,要看情况。第一种方法(在构造函数中初始化方法)的主要优点是,可以通过使用方法中的构造函数中定义的局部变量来利用闭包。这些变量不能在构造函数之外直接访问,因此它们实际上是“私有的”,这意味着与将这些变量定义为对象的属性相比,您的 API 更加干净。一些基本的经验法则:

  • 如果您的方法没有使用构造函数中定义的局部变量(您的示例没有) ,那么使用原型方法。
  • 如果您正在创建大量的 Dog,请使用原型方法。这样,所有“实例”(即由 Dog构造函数创建的对象)将共享一组函数,而构造函数的方式是,每次调用 Dog构造函数时创建一组新的函数,使用更多的内存。
  • 如果您正在创建少量的 Dog,并且发现在构造函数中使用本地的“私有”变量可以改进代码,那么这可能是更好的方法。如果性能或内存消耗是主要考虑因素,请运用您的判断力并做一些基准测试。

可以使用混合方法,即只有需要访问本地私有构造函数变量的方法在构造函数中定义,而将其他方法分配给原型。

例如,下面的代码使用构造函数中的一个局部变量来跟踪这只狗吠叫的次数,同时保持实际数字为私有,因此与吠叫相关的方法在构造函数中定义。摇尾不需要获得树皮的数量,因此该方法可以在原型上定义。

var Dog = function(name) {
this.name = name;


var barkCount = 0;


this.bark = function() {
barkCount++;
alert(this.name + " bark");
};


this.getBarkCount = function() {
alert(this.name + " has barked " + barkCount + " times");
};
};


Dog.prototype.wagTail = function() {
alert(this.name + " wagging tail");
};


var dog = new Dog("Dave");
dog.bark();
dog.bark();
dog.getBarkCount();
dog.wagTail();

两者是不同的: 第一个解决方案将在原型对象上存储对方法 只有的引用,而第二个解决方案将在对象的 每个人上存储方法。这意味着每个对象将包含一个额外的指针,因此每个对象占用更多的内存。

Per-object 方法允许该方法引用构造函数中的变量(一个闭包) ,因此它允许您访问一些无法从原型方法访问的数据。

最后,原型方法可以是 后来变了,也就是说,您可以在运行时在原型对象上重新定义 Bark,并且这种更改将适用于所有具有该原型的对象(因为该方法总是通过原型查找)。