为啥不对对象属性使用闭包?

Posted

技术标签:

【中文标题】为啥不对对象属性使用闭包?【英文标题】:Why not to use closures for object attributes?为什么不对对象属性使用闭包? 【发布时间】:2011-12-11 17:05:53 【问题描述】:

我目前正在用 javascript 编写对象,我想以一种清晰、漂亮的方式,使用最佳实践等。但我很困扰我必须总是写 this. 来处理属性,不像其他OO 语言。

所以我明白了 - 为什么不只对对象属性使用闭包?看看我的示例对象。所以代替这个,经典的方式:

var MyObjConstructor = function (a, b) 

    // constructor - initialization of object attributes
    this.a = a;
    this.b = b;
    this.var1 = 0;
    this.var2 = "hello";
    this.var3 = [1, 2, 3];

    // methods
    this.method1 = function () 
        return this.var3[this.var1] + this.var2; 
            // terrible - I must always write "this." ...
    ;


...我会这样做使用闭包,然后我不需要每次都写this.来访问属性!

var MyObjConstructor = function (a, b) 

    // constructor - initialization of object attributes
    // the attributes are in the closure!!!
    var a = a;
    var b = b;
    var var1 = 0;
    var var2 = "hello";
    var var3 = [1, 2, 3];

    // methods
    this.method1 = function () 
        return var3[var1] + var2;
            // nice and short
    ;

    // I can also have "get" and "set" methods:
    this.getVar1 = function ()  return var1; 
    this.setVar1 = function (value)  var1 = value; 

此外,它还有一个隐藏的好处,就是属性真的不能通过 get/set 方法以外的任何其他方式访问!!

所以问题是:

    这是个好主意吗?它是否“干净”,是否符合最佳做法? 这两种解决方案之间是否存在其他语义差异? 有没有像这样使用闭包的陷阱?

【问题讨论】:

也许对象复制不起作用? 这是两个不同的东西 - 使用 this.prop 定义公共属性,使用 var prop 定义私有属性。你不能用一个代替另一个,它们有不同的用途。 @ŠimeVidas var prop 定义了一个局部变量,只能在构造函数内部或在构造函数中定义的函数内部访问。 private 这个词具有误导性。 为了减少混淆:javascript.crockford.com/private.html 【参考方案1】:

95% 的性能下降。

一个实际的Benchmark,所以对于您的(简单)示例来说,跨浏览器的性能下降了 50%-85%。

说真的,关闭速度非常慢。

现在对数据使用闭包不是问题,但对函数/方法使用闭包是问题。你不能没有另一个。原型上的方法没有访问构造函数内的本地变量的机制。

另一个问题是您的“经典”示例不使用原型:\

你真正想要的是

Doing OO properly Understanding prototypes as classes

所以下面的也不好。

var MyObjConstructor = function (a, b) 

    // constructor - initialization of object attributes
    this.a = a;
    this.b = b;
    this.var1 = 0;
    this.var2 = "hello";
    this.var3 = [1, 2, 3];

    // methods
    this.method1 = function () 
        return this.var3[this.var1] + this.var2; 
    ;


你想要

// constructor - initialization of object attributes
var MyObjConstructor = function (a, b) 
    this.a = a;
    this.b = b;


Object.extend(MyObjConstructor.prototype, 
    var1: 0,
    var2: "hello",
    var3: [1, 2, 3],
    // methods
    method1: function () 
        return this.var3[this.var1] + this.var2; 
    
);

对于Object.extend 的某个值。这里是在原型上放置任何通用数据或方法,并在 所有 实例之间共享这些数据。这样,我们就不会每次都在记忆复制所有内容。

// 太糟糕了——我必须总是写“这个”。 ...

另一种方法是为每个对象复制状态。闭包模式很好,但性能不佳。

【讨论】:

感谢您的链接,但是我不喜欢使用新库 (pd) - 是否有一些类似的指令如何做 OO,但只是“赤手空拳”? (或者,也许使用 jquery)。 @TomasT。肯定有。以详细的方式进行。 pd 只是让它不那么冗长。或者用“经典”的方式来做。 如果我错了,请纠正我,但 jQuery 框架不是几乎完全使用闭包吗?如果这是一个性能问题,他们为什么要对整个库使用闭包? 我在摆弄你的图书馆,但我一定是做错了什么。您的代码似乎扩展了构造函数,而不是它的原型。 jsfiddle.net/mK4ws 实例没有var1 可用。 @MrMisterMan 因为 jQuery 慢得要命。我的意思是,嗯,jQuery 没有在 javascript 级别进行优化,而是在 DOM 级别进行了优化。【参考方案2】:

直接回答您的问题:

1.使用对象作为闭包可以吗?是否符合最佳做法?

是的。在某些情况下,您确实希望拥有私有字段,因此这是唯一的方法,其中一种方法可以做到这一点。对于一个真实的、具体的示例,请查看 Dojo 或 JQuery 中的 Deferreds/Promises。 Promises 只实现了 deferred 的非可变子集,因此它们需要保持其内部 Deferred 私有,以避免其他人直接更改它。

请记住,您应该只在真正需要它们的地方真正使用隐藏字段(而不是因为不必键入“this”等琐碎的原因)。使用普通的旧公共字段和“普通”对象也很好,特别是如果您认为它们具有闭包版本没有的一些优点:

2。这两个版本在语义上有区别吗?

是的。

您不能直接检查私有字段 (duh)。这也意味着您无法轻松克隆对象或对它们进行其他类型的反射。 通过对象属性而不是直接引用访问字段允许您使用原型继承。重要的部分是: 您很难覆盖子类中的内容(闭包中的变量是静态的,而不是虚拟的) 您不能添加其他使用这些隐藏字段的方法(因为它们只能在构造函数内部访问)。特别讨厌子类化。

3.使用这样的闭包有什么陷阱吗?

与使用原型的代码相比,如今大多数 Javascript 引擎在具有大量闭包的代码上的性能较差。这不是造成差异的“真正”原因(因为 LISP 引擎一直以来都可以使用闭包),而是您必须忍受的东西。

【讨论】:

你可以在没有闭包的情况下实现 Deferreds/Promises。闭包与原型实际上只是一种风格或“做你想做的事”。 好吧,如果你真的希望 promise 隐藏它们的私有状态,那就不要了。否则你只会得到 Deferred 部分。 您最终将不得不使用.bind 和某种代理模式。 真的......我仍然认为必须支持旧的东西,其中闭包是执行bind 的唯一方法。 :) (虽然我确信找到更多关于闭包更惯用的例子并不难) 感谢您的回复,很好的分析。

以上是关于为啥不对对象属性使用闭包?的主要内容,如果未能解决你的问题,请参考以下文章

为啥向元类属性闭包组合添加第二个属性会更改第一个属性?

为啥 Swift 不更新闭包外的变量?

直接调用分配给对象属性的闭包

javascript 使用闭包保护对象内的属性不被外部修改

JS面向对象 -- Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法

js闭包