在构造函数*内部*分配原型方法——为什么不呢?

Assigning prototype methods *inside* the constructor function - why not?

在文体上,我更喜欢这种结构:

var Filter = function( category, value ){
  this.category = category;
  this.value = value;

  // product is a JSON object
  Filter.prototype.checkProduct = function( product ){
    // run some checks
    return is_match;
  }

};

到这个结构:

var Filter = function( category, value ){
  this.category = category;
  this.value = value;
};// var Filter = function(){...}

Filter.prototype.checkProduct = function( product ){
  // run some checks
  return is_match;
}

在功能上,以这种方式构建我的代码有什么缺点吗?将原型方法添加到构造函数主体内的原型对象(即在构造函数的表达式语句关闭之前)会导致意外的范围问题吗?

我之前成功地使用了第一个结构,但我想确保我不会让自己陷入调试的困境,或者由于糟糕的编码实践而导致其他开发人员的悲伤和愤怒。

第一个示例代码有点偏离了原型的目的。您将为每个实例重新创建 checkProduct 方法。虽然它只会在原型上定义,不会为每个实例消耗内存,但仍然需要时间。

如果您想封装 class 您可以在声明 checkProduct 方法之前检查该方法是否存在:

if(!Filter.prototype.checkProduct) {
  Filter.prototype.checkProduct = function( product ){
    // run some checks
    return is_match;
  }
}

您还应该考虑一件事。该匿名函数的闭包现在可以访问构造函数内的所有变量,因此访问它们可能很诱人,但这会让您陷入困境,因为该函数只能访问单个实例的闭包。在您的示例中,它将是最后一个实例,而在我的示例中,它将是第一个。

Functionally, are there any drawbacks to structuring my code this way? Will adding a prototypical method to a prototype object inside the constructor function's body (i.e. before the constructor function's expression statement closes) cause unexpected scoping issues?

是的,存在缺点和意想不到的范围问题。

  1. 将原型一遍又一遍地分配给本地定义的函数,每次都重复该分配并创建一个新的函数对象。较早的分配将被垃圾收集,因为它们不再被引用,但与第二个代码块相比,运行构造函数的执行时间和垃圾收集方面都是不必要的工作。

  2. 在某些情况下会出现意想不到的范围问题。有关明确示例,请参阅我的答案末尾的 Counter 示例。如果您从原型方法中引用构造函数的局部变量,那么您的第一个示例会在您的代码中创建一个潜在的严重错误。

还有一些其他(更小的)差异。您的第一个方案禁止在构造函数之外使用原型,如:

Filter.prototype.checkProduct.apply(someFilterLikeObject, ...)

当然,如果有人使用:

Object.create(Filter.prototype) 

没有 运行 构造 Filter 构造函数,这也会产生不同的结果,这可能不太可能,因为可以合理地期望使用 Filter 原型的东西应该运行 Filter 构造函数以达到预期效果。


从 运行 时间性能的角度来看(对象上调用方法的性能),你最好这样做:

var Filter = function( category, value ){
  this.category = category;
  this.value = value;

  // product is a JSON object
  this.checkProduct = function( product ){
    // run some checks
    return is_match;
  }

};

有些 Javascript "experts" 声称不再需要使用原型节省的内存(我几天前看了一个视频讲座)所以是时候开始了直接在对象而不是原型上使用方法的更好性能。我不知道我是否准备好自己提倡这一点,但这是一个值得思考的有趣观点。


我能想到的第一种方法的最大缺点是,它真的非常容易犯严重的编程错误。如果您碰巧认为您可以利用原型方法现在可以看到构造函数的局部变量这一事实,那么一旦您拥有多个对象实例,您就会很快搬起石头砸自己的脚。想象一下这种情况:

var Counter = function(initialValue){
  var value = initialValue;

  // product is a JSON object
  Counter.prototype.get = function() {
      return value++;
  }

};

var c1 = new Counter(0);
var c2 = new Counter(10);
console.log(c1.get());    // outputs 10, should output 0

问题演示:http://jsfiddle.net/jfriend00/c7natr3d/

这是因为,虽然看起来 get 方法形成了一个闭包并且可以访问作为构造函数局部变量的实例变量,但实际上并不是这样工作的。因为所有实例共享同一个原型对象,所以 Counter 对象的每个新实例都会创建一个 get 函数的新实例(它可以访问刚刚创建的实例的构造函数局部变量)并赋值给它到原型,所以现在所有实例都有一个 get 方法来访问最后创建的实例的构造函数的局部变量。这是一场编程灾难,因为这可能从来都不是预期的,并且很容易让人头疼,弄清楚出了什么问题以及为什么。

在第一个示例中,Filter 原型在至少调用一次 Filter 之前不会填充函数。如果有人试图继承 Filter 原型怎么办?使用任一 nodejs'

function ExtendedFilter() {};
util.inherit(ExtendedFilter, Filter);

Object.create:

function ExtendedFilter() {};
ExtendedFilter.prototype = Object.create(Filter.prototype);

如果忘记或不知道先调用 Filter,原型链中的原型总是空的。

您的代码的最大缺点是关闭了覆盖您的方法的可能性。

如果我写:

Filter.prototype.checkProduct = function( product ){
    // run some checks
    return different_result;
}

var a = new Filter(p1,p2);

a.checkProduct(product);

结果将与预期不同,因为将调用原始函数,而不是我的函数。

虽然其他答案都集中在从构造函数内部分配给原型的错误之处,但我将重点放在您的第一条陈述上:

Stylistically, I prefer this structure

可能您喜欢这种表示法提供的干净的 encapsulation - 属于 class 的所有内容都由 {} 块正确地 "scoped" 给它。 (当然,谬误在于构造函数的每个运行都是scoped)。

我建议您接受 JavaScript 提供的(揭示)module patterns。您将获得更明确的结构、独立的构造函数声明、class-作用域的私有变量以及正确封装在块中的所有内容:

var Filter = (function() {

    function Filter(category, value) { // the constructor
        this.category = category;
        this.value = value;
    }

    // product is a JSON object
    Filter.prototype.checkProduct = function(product) {
        // run some checks
        return is_match;
    };

    return Filter;
}());

仅供参考,您也无法安全地执行此操作:

 function Constr(){

    const privateVar = 'this var is private';

    this.__proto__.getPrivateVar = function(){
       return privateVar;
    };

 }

原因是因为Constr.prototype === this.__proto__,所以你也会有同样的错误行为。