构造函数与原型中的函数

时间:2015-05-27 15:59:27

标签: javascript

我知道有类似的问题,但我希望看看这些答案在新Javascript引擎中的优化是否仍然有效。

在我看来,在构造函数中定义函数的最大好处是可以轻松避免知道'this'关键字的值:

var Person = function() {
  var self = this;
  self.firstName = null;
  self.lastName = null;
  self.fullName = function() {
    return self.firstName + self.lastName;
  };
};

Knockout Managing 'this'推荐这种方法。这是一个很大的优势,特别是当许多开发人员修改代码时,因为它很容易理解和使用。

另一种方法是使用对象原型:

var Person = function() {
  this.firstName = null;
  this.lastName = null;
};
Person.prototype.fullName = function() {
  return this.firstName + this.lastName;
};

在这种情况下,存在性能优势,因为函数对象将被创建一次。然而,我遇到的主要问题是处理'this'关键字可能会很复杂。上面的例子非常简单,但是如果你有事件处理程序,forEach调用,jQuery each()调用,从不同上下文调用的方法等,很容易误用它。

当然,如果您了解'this'是如何工作的并且知道如何调用方法,那么您应该没有太多问题。但是,根据我的经验,这需要时间并且容易出错,尤其是当代码由许多开发人员制作时。

我知道新的JS引擎,比如V8,正在通过创建隐藏的类:How the V8 engine works?来对在构造函数中声明函数的情况应用优化。

所以我的问题是,鉴于新JS引擎完成的这些优化以及必须处理'this'关键字的复杂性,使用基于原型的方法仍然有意义吗?通过使用将所有内容放在构造函数中的方法,我会放松什么?

更新1:

我刚刚在Chrome上进行了微基准测试(第42版)。我创建1M对象,其中的函数在构造函数中,函数在原型中。这是一个非常简单的对象,有两个变量和三个函数,结果如下:

Functions inside constructor: 1.91 seconds
Functions in prototype: 1.10 seconds

即使在V8中进行优化,它仍然快73%。然而,这是一个微观基准。不确定这在现实世界的应用程序中是否会有很大的不同。

更新2:

我还看了一下内存消耗,也有很大差异。对于构造函数内部的函数:

Shallow size: 64,000,120
Retained size: 336,001,128

原型功能:

Shallow size: 40,000,000
Retained size: 40,000,000

隐藏类的优化都不是那么好,或者我错过了一些关于它的东西。我正在使用V8建议的单形代码(没有args的构造函数),但不确定我是否做错了。

更新3:

以下是我所做的测试链接,以防有人在其中指出错误:http://jsperf.com/dg-constructor-vs-prototype

3 个答案:

答案 0 :(得分:3)

我进行了快速测试。如果在构造函数中声明函数,则即使在优化之后,两个对象实例也具有不同的函数实例。但是对于原型,您只有一个函数实例来解释性能差异。

    var Person = function () {
        var self = this;
        self.firstName = null;
        self.lastName = null;
        self.fullName = function () {
            return self.firstName + self.lastName;
        };
    };

    Person.prototype.fullName2 = function () {
        return this.firstName + this.lastName;
    };

    var a = new Person();
    var b = new Person();

    console.log(a.fullName == b.fullName); // returns false
    console.log(a.fullName2 == b.fullName2); // returns true

答案 1 :(得分:0)

我一直在使用一种略有不同的方法,IMO具有清晰性的优点,并且避免了每次调用构造函数时都重新创建成员函数:

  • 将类的成员函数定义为构造函数外部的模块级函数
  • 将函数明确分配给构造函数中的类实例

例如:

// Constructor for MyClass
function MyClass(a, b){

    // set properties of the instance from constructor arguments
    this.a = a;
    this.b = b;

    // assign the report function as a member of this instance
    this.report = Report;

}

// Report function is defined at the module level,
// but used by assigning it to an instance variable
// within the constructor.
function Report(){
    console.log( "a=" + this.a, "b=" + this.b);
}

只有一个成员函数实例可以由同一类的所有实例共享(就像将函数分配给class.prototype.function时的情况一样),因此这种方法非常有效,并且具有额外的优点,IMO:

  1. 构造明确地包括,而不必添加到构造的原型外方法的所有的类的方法的声明。
  2. 构造的代码更简洁和容易按照比它如果整个函数定义的构造内编码。
  3. 由于该方法是在模块级别定义的,因此会在模块执行前 实例化,因此可以在构造函数声明之前的代码中引用该类。分配给class.prototype.function时不是这种情况,必须在调用构造函数之前执行该类。

用法:

// Create instances of my class
var inst1 = new MyClass( "as", "df");
var inst2 = new MyClass( "gh", "jk");

// Report the instances
inst1.report();
inst2.report();

// Class and method declarations follow below here...

是否有任何缺点这种方法?

答案 2 :(得分:0)

就像@Ersin Basaran提到的那样,在构造函数内部创建的函数对于每个对象实例都是唯一的,与使用原型创建时使每个对象实例的功能相同的函数不同。

但是,在ES6(ECMAScript2015)中引入类之后,如果使用类来创建方法而不是使用构造函数,并且在构造函数外部(但在类内部)创建此方法,则将是相同的对于每个对象实例,就像使用原型时一样。

以下是创建fullName()方法的示例:

class Person {
    constructor () {
        var self = this;
        self.firstName = null;
        self.lastName = null;
    }
    fullName () {
        return self.firstName + self.lastName;
    }
}

Person.prototype.fullName2 = function () {
    return this.firstName + this.lastName;
};

var a = new Person();
var b = new Person();

console.log(a.fullName == b.fullName); // returns true
console.log(a.fullName2 == b.fullName2); // returns true

我希望这会有所帮助。