【问题标题】:Advantages of using prototype, vs defining methods straight in the constructor? [duplicate]使用原型与直接在构造函数中定义方法的优势? [复制]
【发布时间】:2011-05-29 08:13:24
【问题描述】:

我想知道使用其中任何一个是否有任何优势,我应该走哪条路?

构造方法:

var Class = function () {

    this.calc = function (a, b) {
        return a + b;
    };

};

原型方法:

var Class = function () {};

Class.prototype.calc = function (a, b) {
    return a + b;
};

我不喜欢这样,使用原型,方法定义与类分开,我不知道是否有任何具体原因我应该使用它而不是第一种方法。

另外,使用函数字面量来定义“类”有什么好处,而不仅仅是函数定义:

var Class = function () {};

function Class () {};

谢谢!

【问题讨论】:

  • 简而言之:你会节省内存。

标签: javascript oop prototype


【解决方案1】:

通过原型链继承的方法可以针对所有实例进行通用更改,例如:

function Class () {}
Class.prototype.calc = function (a, b) {
    return a + b;
}

// Create 2 instances:
var ins1 = new Class(),
    ins2 = new Class();

// Test the calc method:
console.log(ins1.calc(1,1), ins2.calc(1,1));
// -> 2, 2

// Change the prototype method
Class.prototype.calc = function () {
    var args = Array.prototype.slice.apply(arguments),
        res = 0, c;

    while (c = args.shift())
        res += c;

    return res; 
}

// Test the calc method:
console.log(ins1.calc(1,1,1), ins2.calc(1,1,1));
// -> 3, 3

注意如何更改应用于两个实例的方法?这是因为ins1ins2 共享相同的calc() 函数。为了使用构建期间创建的公共方法执行此操作,您必须将新方法分配给已创建的每个实例,这是一项尴尬的任务。这是因为ins1ins2 将拥有自己的、单独创建的calc() 函数。

在构造函数中创建方法的另一个副作用是性能较差。每次构造函数运行时都必须创建每个方法。原型链上的方法创建一次,然后由每个实例“继承”。另一方面,公共方法可以访问“私有”变量,而继承方法则无法做到这一点。

至于您的function Class() {} vs var Class = function () {} 问题,前者在执行前被“提升”到当前范围的顶部。对于后者,变量声明被提升,但不是赋值。例如:

// Error, fn is called before the function is assigned!
fn();
var fn = function () { alert("test!"); } 

// Works as expected: the fn2 declaration is hoisted above the call
fn2();
function fn2() { alert("test!"); }

【讨论】:

  • 啊,这让事情变得更清楚了 :) 我没有意识到效率差异 - 知道这一点非常有用。提升效果也一样——确实很棘手。谢谢你这么棒的回答,我从中学到了很多!
  • 非常老的问题,但不知何故遵循了一个链接并在这里偶然发现——我认为如果你保持参数数量一致,这个例子会更能说明问题(只是为了证明它正在使用a+b。这个确实是一个小问题,但它可以帮助读者识别您正在关注的差异以及排除他可能正在阅读的其他因素(例如:如果您确实有第三个电话会发生什么论据)。这个例子很简单,希望程序员足够优秀,不会被细微的差异所困扰。
  • 使用Functional模式当然可以达到同样的效果,从而避免模仿经典语言的继承模式。
  • 这部分代码在做什么:Class.prototype.calc = function () { var args = Array.prototype.slice.apply(arguments), res = 0, c;
  • 由于一段时间过去了,我们现在有了 ES6+,我想指出箭头函数定义是 var Class = function() {...} 的简写,因此也没有提升。
【解决方案2】:

原型方法的优点是效率。在所有Class 对象之间共享一个calc() 函数对象(我的意思是通过调用Class 构造函数创建的对象)。另一种方式(在构造函数中分配方法)为每个Class 对象创建一个新的函数对象,在调用Class 构造函数时使用更多内存并花费更多处理时间。但是,这种方法确实有一个优势:calc() 方法可以访问构造函数中的局部变量,您可以利用它来发挥自己的优势:

function Class() {
    var calcCallCount = 0;

    this.calc = function (a, b) {
        ++calcCallCount;
        alert("Calc called " + calcCallCount + " times");
        return a + b;
    };
};

关于var Class = function() {...}function Class() {...},我一般更喜欢后者,因为它意味着函数有一个名称,这在调试时很有用。另一个区别是后一个版本(函数声明)被提升了,这意味着它在定义它的范围内的任何地方都可用,而不仅仅是在定义之后。然而,some people 更喜欢在任何地方使用前者(一个函数表达式)。

【讨论】:

  • 也感谢您的回答,蒂姆,非常感谢!
  • Re Class = function() {...},即在全局/窗口范围内定义,我在名称方面没有遇到任何调试问题,尽管似乎没有发生提升可以理解。不确定这种方法与您的两种方法之间是否还有其他区别。
  • @NickWiggill:自从我写了这个答案以来,内置的浏览器开发人员工具已经走了很长一段路,现在他们在从上下文中推断出适当的函数名称方面做得更好,所以我同意这很容易如今,调试的问题已经不再那么重要了。
  • 做了一个 js Perf 测试来可视化性能差异。 jsperf.com/class-comparison
  • @DaveVoyles:或者确实是这篇文章:stackoverflow.com/questions/9772307/…
【解决方案3】:
var YourClass = function(){
  var privateField = "somevalue";
  this.publicField = "somevalue";
  this.instanceMethod1 = function(){
     //you may access both private and public field from here:
     //in order to access public field, you must use "this":
     alert(privateField + "; " + this.publicField);
  };
}

YourClass.prototype.instanceMethod2 = function(){
  //you may access only public field 2 from this method, but not private fields:
  alert(this.publicField);
  //error: drawaback of prototype methods:
  alert(privateField);  
};

原型方法的优点:

  1. 当您通过原型定义方法时,它们在所有 YourClass 实例之间共享。因此,如果您在构造函数中定义方法,此类实例的总大小小于;有一些测试显示通过原型定义方法如何减少 html 页面的总大小,从而降低其加载速度。

  2. 通过原型定义的方法的另一个优点是,当您使用继承类时,您可以覆盖这些方法,并且在派生类的被覆盖方法中,您可以调用具有相同名称的基类方法,但是使用构造函数中定义的方法,你不能这样做。

【讨论】:

  • 感谢您的回答,我很感激,现在更加意识到 StackOverflow 的资源有多棒。
  • 您好,继承类是什么意思?我不认为这是正确的术语,因为 javascript 没有类的概念。当你说派生类的重写方法时,你的意思是另一个对象,它的原型是你的对象吗?我迷路了..你能编辑或解释一下吗?
  • Alexandr 你能用一个例子解释#2吗?
猜你喜欢
  • 2011-10-30
  • 2014-01-11
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
  • 1970-01-01
  • 2012-01-22
  • 1970-01-01
相关资源
最近更新 更多