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

IT技术 javascript methods scope prototype
2021-02-08 16:24:30

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

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

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

我之前成功地使用了第一个结构,但我想确保我不会让自己为调试头疼,或者由于糟糕的编码实践而导致开发人员的悲伤和恶化。

6个回答

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

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

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


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

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访问最后创建的实例的构造函数的局部变量的方法。这是一场编程灾难,因为这可能永远不是预期的,并且很容易让人头疼,以找出问题所在以及原因。

你的例子Counter和解释很精彩。谢谢!
2021-04-09 16:24:30

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

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

可能您喜欢这种表示法提供的干净封装- 属于该类的所有内容都被{}正确地“限定”到它的范围内(当然,谬误在于它的范围仅限构造函数的每次运行)。

我建议你学习JavaScript 提供的(揭示)module模式你会得到一个更明确的结构、独立的构造函数声明、类范围的私有变量,以及正确封装在一个块中的所有内容:

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;
}());
我认为它会成为一个命名函数表达式,但这可能不正确。它适用于我测试过的每个浏览器——不是一个详尽的列表,但它包括 IE 6。
2021-03-16 16:24:30
对于代码样式问题,IIFE 是一种低效的解决方案,在这种情况下,可以删除前两行和最后两行,并且没有任何实际更改,并且避免了一些闭包。通常,它会在分配给Filter.prototype的函数根据某些逻辑而不同的情况下使用,例如功能测试。另一种方法是使用标签和块:Filter: { /* code here */ }或者只是评论。
2021-03-17 16:24:30
2021-03-19 16:24:30
@RobG:是的,这个小例子不需要module模式。但是,随着代码的增长,它还有其他一些优势;例如,您可以引入一个本地别名Filter.prototype以避免重复。我会避免使用块,因为函数声明在它内部在语法上是无效的。
2021-03-26 16:24:30

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

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

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

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

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

如果我写:

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

var a = new Filter(p1,p2);

a.checkProduct(product);

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

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

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

Object.create

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

如果忘记或不知道首先调用,则原型链中总是以空原型结束Filter

好吧,只要您调用ExtendedFilter构造函数,它就会调用Filter并且该方法“神奇地”出现在您的原型上。它会工作得很好,原型最初为空对于继承无关紧要。
2021-03-17 16:24:30
是的,在调用构造函数之前等待原型初始化是不舒服的(例如@lujcon 已显示),但不是因为继承不起作用。
2021-03-17 16:24:30
鉴于您的第一点,这是否意味着使用函数声明而不是函数表达式会更好地服务于第一个结构?
2021-03-28 16:24:30
它不会Filter自动调用,只有当程序员决定在编写ExtendedFilter正文时......(不这样做可能是不好的做法,但仍然......)。我认为这个答案不值得投反对票,因为 OP 询问这种做法是否会“引起开发人员的痛苦和恶化”,而且我在 JavaScript 世界中感到不舒服,因为原型在第一个构造函数调用之前是空的。
2021-03-29 16:24:30