Javascript 在构造函数函数中指定原型方法-为什么不?

Javascript 在构造函数函数中指定原型方法-为什么不?,javascript,methods,scope,prototype,Javascript,Methods,Scope,Prototype,在风格上,我更喜欢这种结构: 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

在风格上,我更喜欢这种结构:

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;
}
对于该结构:

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方法。虽然它将仅在原型上定义,并且不会为每个实例消耗内存,但它仍然需要时间

如果希望封装该类,可以在声明checkProduct方法之前检查该方法是否存在:

if(!Filter.prototype.checkProduct) {
  Filter.prototype.checkProduct = function( product ){
    // run some checks
    return is_match;
  }
}
还有一件事你应该考虑。该匿名函数的闭包现在可以访问构造函数中的所有变量,因此访问它们可能很有诱惑力,但这会让您陷入一个兔子洞,因为该函数只对单个实例的闭包有权限。在你的例子中,它将是最后一个例子,在我的例子中,它将是第一个

从功能上讲,这样构造代码有什么缺点吗? 将向中的原型对象添加原型方法 构造函数的主体(即在构造函数的 表达式语句关闭)是否导致意外的作用域问题

是的,存在缺陷和意外的范围界定问题

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

  • 在某些情况下会出现意外的范围界定问题。请参阅我答案末尾的
    计数器
    示例,了解明确的示例。如果从prototype方法中引用构造函数的局部变量,那么第一个示例会在代码中创建一个潜在的严重bug

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

    Filter.prototype.checkProduct.apply(someFilterLikeObject, ...)
    
    当然,如果有人使用:

    Object.create(Filter.prototype) 
    
    如果不运行
    过滤器
    构造函数,这也会产生不同的结果,这可能不太可能,因为有理由期望使用
    过滤器
    原型的东西应该运行
    过滤器
    构造函数,以获得预期的结果


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

    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“专家”声称不再需要使用原型节省内存(几天前我观看了一个关于这一点的视频讲座),因此是时候开始直接在对象上而不是原型上使用性能更好的方法了。我不知道我自己是否已经准备好宣传这一点,但这是一个值得思考的有趣的观点


    我能想到的第一种方法的最大缺点是,它非常非常容易犯严重的编程错误。如果您碰巧认为可以利用prototype方法现在可以看到构造函数的局部变量这一事实,那么当您有多个对象实例时,您就会很快地自食其果。想象一下这种情况:

    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
    
    问题的演示:


    这是因为,虽然它看起来像是
    get
    方法形成了一个闭包,并且可以访问作为构造函数的局部变量的实例变量,但实际上它不是这样工作的。由于所有实例共享同一原型对象,因此
    计数器
    对象的每个新实例都会创建
    get
    函数的一个新实例(该函数可以访问刚刚创建的实例的构造函数局部变量),并将其分配给原型,因此,现在所有实例都有一个
    get
    方法,该方法访问最后创建的实例的构造函数的局部变量。这是一场编程灾难,因为这可能从来都不是我们想要的,很容易让人摸不着头脑,弄不清哪里出了问题以及原因。

    在第一个示例中,
    Filter
    原型在至少调用一次
    Filter
    之前都没有填充函数。如果有人试图继承
    过滤器
    原型,该怎么办?使用任一节点

    function ExtendedFilter() {};
    util.inherit(ExtendedFilter, Filter);
    
    对象。创建

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

    如果忘记或不知道首先调用
    过滤器
    ,则原型链中的原型始终为空。

    您的代码最大的缺点是可能会覆盖您的方法

    如果我写:

    Filter.prototype.checkProduct = function( product ){
        // run some checks
        return different_result;
    }
    
    var a = new Filter(p1,p2);
    
    a.checkProduct(product);
    

    结果将不同于预期,因为将调用原始函数,而不是my。

    当其他答案集中在从构造函数内部分配给原型的错误之处时,我将集中在您的第一句话:

    在风格上,我更喜欢这种结构

    您可能喜欢这种表示法提供的简洁性—属于类的所有内容都被
    {}
    块正确地“限定”到它的范围。(当然,谬误在于它的作用域是构造函数的每次运行)

    我建议您看看JavaScript提供的(揭示性的)功能。您可以获得更显式的结构、独立的构造函数声明、类范围的私有变量,以及正确封装在块中的所有内容:

    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;
    }());
    

    仅供参考,您也不能安全地执行此操作