在原型上使用闭包有缺点吗?

Is there a downside to using closures over prototypes?

我喜欢使用闭包来创建具有私有属性的对象的一般风格。我不确定是在闭包内还是在对象的实际原型上创建原型方法更有效。考虑以下示例:

const A = function(a, b) {

  this.a = a;
  this.b = b;

};

A.prototype = {

  add:function() { this.a += this.b; }

};

const B = (function() {

  function add() { this.a += this.b; }

  return function(a, b) {

    return { a, b, add };

  };

})();

示例 A 是一个带有原型的传统构造函数。使用它看起来像这样:

var a = new A(1, 1);
a.add(); // a.a == 2;

示例 B 是使用闭包的技术。使用它看起来像这样:

var b = B(1, 1);
b.add(); // b.a == 2;

我期望 a 的工作方式是 A 的每个实例都有 a 和 b 属性以及指向包含 add 方法的原型对象的指针。

我期望 b 的工作方式是 B 的每个实例都有 a 和 b 属性以及指向 add 方法的指针。 B 函数有一个包含 add 方法的闭包。 add 方法只在定义 B 时定义一次。这似乎是在 JS 中创建“原型”属性和方法的一种不错的方式。有谁知道这是创建具有“共享”属性的对象的高效方法还是使用原型的传统方法的可行替代方法?

您的闭包示例对于那种类型的构建器函数来说有点不典型,因为它不会在 B 本身中创建任何函数,只是在创建 B 的匿名函数中;这意味着它避免了这样做的通常的小缺点(每次调用 B 时重新创建函数)。 (通常“闭包风格”在 B 本身中创建函数,因此它可以直接在执行上下文中使用 ab,而不是使用 this.athis.b属性。)

我看不出您正在做的事情有任何性能上的缺点。从理论上讲,它会有非常轻微的性能提升(完全不同于您观察到的对象文字比使用 new 快得多),因为实例上的方法是自己的属性而不是继承的属性,所以(理论上)它们的查找速度 稍微 快(因为 JavaScript 引擎会立即在对象上找到它们,而不是在对象本身上找不到它们并具有去看看原型)。但实际上,我希望任何现代 JavaScript 引擎都能进行优化,这样您就不会从中受益。

我可以看到几个非性能的缺点,但不是主要的:

  1. 一切都只是一个对象,所以如果你正在调试一个不相关的性能问题并查看堆快照,一切都只是 Object 而不是 ABC,这使得使用内存分析器更加困难。我试图通过向它们添加 constructor 属性 来获取内存配置文件以对它们进行分类,但它不起作用(无论如何在 Chrome 中)。我认为它使用了对象原型的构造函数。您可以通过为对象提供特定于构造函数的原型来解决它,只是不 使用 它,但这似乎有点奇怪。

  2. 要在不使用原型对象的情况下增强对象,您必须改用 mixins,这意味着将所有属性从一个对象复制到另一个对象,这需要更多工作,并且意味着每个对象都是比他们使用原型继承时更大,可能会在低端设备(手机等)上造成内存流失。 (或者,使用组合而不是增强,这有单独的论点。)

  3. 你在逆语言游泳。 JavaScript 的原型性质是其本质的重要组成部分(即使您不使用构造函数而是使用 Object.create 或类似函数)。 JavaScript 引擎旨在优化原型关系。你会做别的事情。可能还好吧,就是感觉不太理想。

  4. 当使用非典型模式时,其他人更难跟上代码库的速度。使用 JavaScript 的三种典型方式是构造函数(带原型)、非 this 闭包/Object.create 方式(带原型)和函数式编程。您看到的是前两者的混合体。

但是如果您正在解决与对象创建速度相关的特定问题,那么有针对性地使用这种稍微不寻常的模式可能会非常成功。或者您可能只是更喜欢它,并且乐于接受缺点和优点。 :-)

更好的是,不要使用原型或闭包。只需使用常规函数即可。

const C = (a, b) => ({ a, b });

function add(c) {
    c.a += c.b;
}

const c = C(1, 1);
add(c); // c.a === 2;

console.log(c); // { a: 2, b: 1 }