这两者之间只有一些真正的技术差异(伯吉在评论中指出)。除非你做了成千上万次这样的事情,否则这些差异可能并不重要。有一个style差异,这是主观的,但技术差异很小。请参阅下面的细分以了解血淋淋的细节。
在较高层次上,两种形式都创建一个新对象,它们都依赖于私有成员的闭包。第二种方式使用更多的内存,因为该函数在使用后无法进行垃圾收集,而第一种方式可以。类似地,创建为函数的对象prototype
属性在第一个版本中是可回收的,但在第二个版本中则不行。对于某些人来说,第二种方法可能不如第一种方法那么清楚(请参阅@djechlin 下面的问题),尤其是因为隐式返回值,但这是一个风格点。
关于你的第二个问题:
还有其他实现私有成员的可能性吗?
对于像你这样的一次性对象,你这样做的方式可能是最好的(无论是哪种形式,真的)。但现在,有sort of另一种方式,从 ES.Next(ECMAScript 的下一个版本)开始,有一种新的真正私有的方式。不过,在这两种情况下,当您处理对象类而不是一次性对象时,它更有用。
More in 我博客上的这篇文章 http://blog.niftysnippets.org/2013/05/private-properties-in-es6-and-es3-and.html(其中还讨论了 ES.Next 附带的内容),但我将在这里讨论最重要的细节。
对象类中私有成员的典型模式如下所示:
function Foo(toBePrivate) {
var privateMember = toBePrivate;
this.method1 = function() {
// ...do something with the truly private `privateMember`
};
}
Foo.prototype.method2 = function() {
// ...doesn't have access to the truly private `privateMember`
};
var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() cannot
它的问题是通过创建的每个对象new Foo
得到它的own method1
功能。我们没有得到重用method2
,那里只有one其中由创建的所有对象共享new Foo
。 (这对于现代引擎来说不一定是大问题,因为它们能够重复使用code of method1
即使一个新的method1
object为每个创建Foo
目的。但有一些动态改变原型的开发模式,显然无法作用method1
above.)
这是我们今天可以做的近乎私有的模式:
var Foo = (function() {
// Create a private name object for our private property
var privateKey = makeRandomString();
// Our constructor
function Foo(toBePrivate) {
this[privateKey] = toBePrivate;
}
// Define the private property so it's non-enumerable
Object.defineProperty(Foo.prototype, privateKey, {
writable: true
});
// Methods shared by all Foo instances
Foo.prototype.method1 = function() {
// ...use this[privateKey] here...
};
Foo.prototype.method2 = function() {
// ...use this[privateKey] here...
};
return Foo;
})();
var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() can too!
// Both `method1` and `method2` are *reused* by all `Foo` objects
...在哪里makeRandomString
正是这样做的,每次我们调用它时它都会给我们一个新的随机字符串。
我们创建的财产不是私有的,但它确实是晦涩难懂的。它没有出现在for-in
循环(因为我们创建的属性是不可枚举的)并且它的名称每次代码运行时都会改变。因此,任何尝试使用私有数据的代码都必须首先找出属性名称,这是一个不简单的练习,因为该代码无法获取对象的不可枚举属性名称的列表。当然,在调试器中看一眼该对象就会显示该属性及其值。该物业是真的很晦涩难懂,但并不是真正的私密。
ES.Next(ECMAScript 的下一个版本)中的内容显着改进了这种模式。我们会得到私有名称对象 http://wiki.ecmascript.org/doku.php?id=harmony%3aprivate_name_objects,它们本身很有用,也被新课程 http://wiki.ecmascript.org/doku.php?id=harmony%3aclasses.
以下是私有名称如何应用于上述内容:
// **ES.Next, not yet available in the wild**
import Name from "@name";
var Foo = (function() {
// Create a private name object for our private property
var privateKey = new Name();
function Foo(toBePrivate) {
this[privateKey] = toBePrivate;
}
Foo.prototype.method1 = function() {
// ...use this[privateKey] here...
};
Foo.prototype.method2 = function() {
// ...use this[privateKey] here...
};
return Foo;
})();
var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() can too!
// Both `method1` and `method2` are *reused* by all `Foo` objects
使用私有名称对象创建的属性永远不会出现在for-in
枚举at all,并且它们的名称不是字符串。代码cannot访问名称为私有名称对象的属性,而无需该特定名称对象。自从privateKey
上面的变量完全私有Foo
类,没有其他代码可以使用该属性。这是完全私人的。当然,这些将显示在调试器中,但是调试器中没有任何内容是私有的。
@djechlin 要求详细说明每个私人会员表格的具体工作方式,这有助于我们理解 Bergi 强调的它们之间的区别:
你的第一个例子:
var myObject1 = (function(){
var privateMember = 42;
return {
publicMember: function() {
return privateMember;
}
}
})();
(此列表遗漏了一些我认为不相关的细节。)
一个名为myObject1
在当前变量绑定对象上创建(可能是window
如果这是全局的)其值undefined
.
计算函数表达式:
A) A Function
要创建的对象。
B) 创建一个空白对象并将其分配给新函数的prototype
财产。
C) A constructor
属性是在该对象上创建的,并给出对该函数的引用。
D) 对当前变量绑定对象的引用存储在函数上。
调用该函数,为调用的执行上下文创建(除其他外)一个变量绑定对象。
一个名为privateMember
创建并分配给步骤 3 中的变量绑定对象。
值 42 被分配给privateMember
VBO 的财产。
创建一个空白对象并给出原型Object.prototype
.
计算内部函数表达式,aFunction
创建对象(带有一个空白对象prototype
财产和constructor
属性放在该对象上,并带有对当前变量绑定对象的引用[第 3 步中的对象])。
该函数被分配给步骤 5 中空白对象的属性,如下所示publicMember
.
从主匿名函数返回对步骤 6 中对象的引用。
该对象引用存储在myObject1
在步骤 1 中创建的属性。
主匿名函数(来自步骤 2)没有未完成的引用,因此可以被 GC 回收;所以它所引用的对象prototype
属性也可以通过GC回收。
你的第二个例子:
var myObject2 = new function() {
var privateMember = 42;
this.publicMember = function() {
return privateMember;
}
}
(再次省略一些不相关的细节。)
一个名为myObject2
在当前变量绑定对象上创建(可能是window
如果这是全局的)其值undefined
.
计算函数表达式:
A) A Function
要创建的对象。
B) 创建一个空白对象并将其分配给新函数的prototype
财产。
C) A constructor
属性是在该对象上创建的,并给出对该函数的引用。
D) 对当前变量绑定对象的引用存储在函数上。
创建一个新的空白对象并从匿名函数的原型中分配其原型prototype
财产。
使用步骤 3 中传入的对象来调用该函数this
,为调用的执行上下文创建(除其他外)变量绑定对象。
一个名为privateMember
创建并分配给步骤 4 中的变量绑定对象。
值 42 被分配给privateMember
VBO 的财产。
计算内部函数表达式,aFunction
创建对象(带有一个空白对象prototype
财产和constructor
属性放在该对象上,并带有对当前变量绑定对象的引用[第 4 步中的对象])。
该函数被分配给步骤 5 中空白对象的属性,如下所示publicMember
.
该函数返回,并且因为它不返回对象,所以结果new
表达式是对步骤 3 中创建的对象的引用。
该对象引用存储在myObject2
在步骤 1 中创建的属性。
主匿名函数(来自步骤 2)无法被 GC 回收,因为myObject2
的底层原型在constructor
属性(因此函数和分配给其的对象prototype
属性保留在内存中)。
您可以释放该函数(但不能释放分配给其的对象)prototype
属性),在其中添加以下行:
delete this.constructor.prototype.constructor;
这将从分配给其的对象中删除对该函数的引用prototype
财产。该对象仍然是myObject2
的底层原型,但它不再引用该函数,因此该函数有资格进行 GC。
但到那时,我们就已经进入了默默无闻的境地。 :-)
结论
所以它们几乎是一样的,只是主要的匿名函数和它的对象有一点小小的区别。prototype
财产不符合 GC 资格。在现实世界中,需要成千上万的这样的东西才能发挥作用。
(旁注:某些实现可能会推迟创建函数的某些步骤,例如为其创建一个空白对象prototype
属性并设置其constructor
— 直到/除非prototype
使用属性,因为当然在vast大多数情况下,它从未被使用过,因为绝大多数函数从未被用作构造函数。所以你的第一个表格可能是一点点效率更高,因为它可以跳过这些步骤。除非你做了数千次这样的事情,否则这种差异不太重要。)
FWIW,如果您关心的是行数、括号数量或不喜欢对象字面量等,第一个也可以这样写:
var myObject1 = function(){
var obj = {};
var privateMember = 42;
obj.publicMember = function() {
return privateMember;
};
return obj;
}();
作为一个问题style,我更喜欢显式返回,但这是风格问题。