【问题标题】:Closures vs. classes for encapsulation?用于封装的闭包与类?
【发布时间】:2012-02-02 12:32:29
【问题描述】:

我是 JS 新手(来自 C++/etc),我突然想到,闭包似乎比类更简单、更方便地处理封装。这段代码似乎提供了一种处理封装的简单方法:

function addProperty(o) {
   var value;

   o["get"] = function()  { return value; }
   o["set"] = function(v) { value = v; }
}

// create two independent objects which are instances of pseudo-class 'addProperty',
// which each have their own version of a set of local fields, and methods that
// operate on those fields:
var a = {};
addProperty(a);
var b = {};
addProperty(b);

如果你只是想要一个类来处理封装(在 C++ 中,我发现这是大多数时候),有什么好的理由使用 JS 类而不是闭包?上面的代码在我看来比 JS 类更直观、更紧凑。没有构造函数、原型或过度使用“this”。您还可以获得必须显式声明本地字段的好处,而不是希望您不要在构造函数中输入任何拼写错误。

编辑

好的,我会澄清一下。似乎“类”这个词得到了一些支持。无论如何,对我来说,“类”是类型概念的扩展,而 JS 在这方面不符合条件,但我可以用我的书 (Flanagan) 花费 50 页调用类的内容来做各种类的事情。有点像。

无论如何,真正的问题是:我在我的第一个 Web 应用程序上使用 jQuery 选项卡。我昨天注意到这不起作用,因为我的 JS 代码没有为每个选项卡保留任何私有状态。当我在选项卡之间切换时,我的代码的动态/鼠标部分不再正常工作。

那么,将私有状态引入应用的最佳方式是什么?大多数代码是安全的,但处理动态行为的所有内容都需要某种方式来封装每个选项卡的本地状态。对于 C++ 背景,对我来说显而易见的答案是编写一个定义选项卡动态部分的类,并在每次创建选项卡时实例化一个新的“选项卡”对象。我发现很难理解的是 JS 伪类在这里是否真的有意义,或者我是否应该扩展上面的代码。

只需阅读 Jonathan 的模块模式链接,现在在我看来这可能就是答案。

谢谢。

【问题讨论】:

  • 没有 JavaScript 类这样的东西。要么这样,要么在过去的 6 年里我一直做错了。
  • 恕我直言,这是语义。它们在代码中可能不称为类,但出于所有实际目的,您可以声明类并将它们实例化为对象。
  • 好的 - (a) 闭包和 (b) “包括原型、构造函数和继承在内的一组语言构造的相对优点如何,它们在其他语言中的用途包括对象的封装以及对这些对象进行操作的方法”,作为 JavaScript 中的通用封装机制?

标签: javascript class closures


【解决方案1】:

避免闭包的原因是开销。

您的 get 和 set 函数比属性慢 20 倍。您的闭包也有很大的内存开销,即 O(N) 与实例的数量。

还要注意,这些封装变量的实际收益为零,它们只是推断性能损失。

var AddProperty = {
  constructor: function (v) { this._value = v; return this; },
  get: function () { return this._value; },
  set: function (v) { this._value = v; }
};

var a = Object.create(AddProperty).constructor(1);
var b = Object.create(AddProperty).constructor(2);

我昨天注意到这不起作用,因为我的 JS 代码没有为每个选项卡保留任何私有状态。

您的问题不是您没有私有状态,而是您使用的是全局状态。

简单的解决方案是每个选项卡都有一个对象(如果您愿意,也可以是一个“结构”)并在其中存储状态。

所以你所要做的就是定义一个标签

var Tab = {
  constructor: function (...) {
    /* init state */
  },
  doTabStuff: function () { /* some method */ },
  ...
}

然后在需要时创建新标签

var tab = Object.create(Tab).constructor(...)

【讨论】:

  • 目前ab 分配给undefined。您可能希望在 constructor 中启用链接。
  • @pimvdb 我忘了return this;
  • +1 - 很好的答案。我知道闭包会保留对“东西”的实时引用,但我不知道它们会慢 20 倍。
  • @AdamRackis 函数调用比属性赋值慢 20 倍
  • @AdamRackis 我认为那是 Java,Java 通常喜欢教人们你应该拥有私有状态和 getter/setter,即使它与公共状态相同。
【解决方案2】:

为了封装功能而使用函数的好处是您可以使用模块模式:

http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

模块模式提供了创建私有成员和方法的能力,而无需像ease.js这样的大量开销:

http://easejs.org/

【讨论】:

    【解决方案3】:

    这是一个古老的问题,但有一些答案是缺失的,所以我想我会补充一下。

    首先,正如评论者喜欢指出的那样,javascript 中没有任何真正的类,我们只是用闭包来模仿它们。这是 javascript 教程中所说的“类”:

    function Class1(){
        var privateState='blah';
        this.get=function(){
            return privateState;
        }
    }
    var myObj=new Class1();
    

    重要的是,get 方法只是一个闭包。因此,这与您的代码基本相同。因为闭包必须引用创建它们的环境(这是它们可以使用私有内部状态数据的方式),所以它们比仅使用公共函数成本更高。所以如果你有一个不需要内部状态的静态方法,你应该把它添加到Class1,而不是使用prototype关键字:

    Class1.prototype.staticMethod=function(){...};
    

    这样,可以通过原型链使用静态方法,而无需通过不必要的闭包占用额外的内存。您也可以通过在AddPropertyfunction 之外添加静态方法来使用您的方法来实现这一点。

    所以,最终,正式的 javascript“类”和您的 AddProperty 函数之间的唯一区别是,当您实际使用类构造函数时,该类出现在对象的原型链中,而您的对象只是直接继承自通用的object 原型。您当然可以编写使用这种区别的代码,但这可能是糟糕的代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-13
      • 2012-03-10
      • 2013-03-12
      • 1970-01-01
      • 2012-10-23
      • 2012-08-22
      • 1970-01-01
      相关资源
      最近更新 更多