这两者之间只有几个真正的技术差异(Bergi 在评论中指出)。除非你做成千上万个这样的事情,否则这些差异可能并不重要。存在风格差异,这是主观的,但技术差异很小。有关血腥细节,请参阅下面的细分。
在高层次上,这两种形式都创建了一个新对象,它们都依赖于私有成员的闭包。第二种方式使用更多的内存,因为函数在使用后不能被垃圾收集,而在第一种方式中,它可以。同样,创建为函数prototype
属性的对象在第一个版本中是可回收的,但不是第二个版本。对于某些人来说,第二种方式也可能比第一种方式不太清楚(见证下面来自@djechlin 的问题),尤其是因为隐式返回值,但这是一个风格点。
关于你的第二个问题:
还有其他实现私有成员的可能性吗?
对于像你这样的一次性对象,你这样做的方式可能是最好的(真的是任何一种形式)。但是现在,有另一种方式,从 ES.Next(ECMAScript 的下一个版本)开始,有一种新的真正私有的方式。但是,在这两种情况下,当你做对象类而不是一次性的时,它更有用。
在我的博客上的这篇文章中有更多内容(其中也谈到了 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
都有自己的 method1
功能。我们没有得到我们得到的重用method2
,其中只有一个由 .创建的所有对象共享new Foo
。(这对现代引擎来说不一定是什么大问题,即使为每个对象创建一个新对象,它们也能够重用代码。但是有一些动态更改原型的开发模式,显然无法行动在上面。)method1
method1
Foo
method1
这是我们今天可以做的近乎私有的模式:
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 的下一个版本)中的内容显着改进了这种模式。我们将获得私有名称对象,它们本身很有用,也被新类使用。
以下是私有名称如何应用于上述内容:
// **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
枚举中,它们的名称也不是字符串。如果没有特定的名称对象,代码就无法访问名称为私有名称对象的属性。由于上面的变量对类是完全私有的,因此没有其他代码可以使用该属性。它是完全私人的。自然,这些将出现在调试器中,但是,调试器没有什么是私有的。privateKey
Foo
@djechlin 要求详细说明您的每个私人会员表格的工作方式,这有助于我们了解 Bergi 强调的它们之间的区别:
你的第一个例子:
var myObject1 = (function(){
var privateMember = 42;
return {
publicMember: function() {
return privateMember;
}
}
})();
(这份清单遗漏了一些我认为不相关的细节。)
myObject1
在当前变量绑定对象(如果 this 是全局的)上创建一个名为的属性window
,其值为undefined
。
评估函数表达式:
A)Function
要创建的对象。
B) 创建一个空白对象并将其分配给新函数的prototype
属性。
C)constructor
在该对象上创建一个属性并给出对该函数的引用。
D) 对当前变量绑定对象的引用存储在函数中。
调用该函数,为调用的执行上下文创建(除其他外)一个变量绑定对象。
创建一个名为的属性privateMember
并将其分配给步骤 3 中的变量绑定对象。
值 42 分配给privateMember
VBO 的属性。
一个空白对象被创建并被赋予了一个原型Object.prototype
。
评估内部函数表达式,Function
创建一个对象(其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
在当前变量绑定对象(如果 this 是全局的)上创建一个名为的属性window
,其值为undefined
。
评估函数表达式:
A)Function
要创建的对象。
B) 创建一个空白对象并将其分配给新函数的prototype
属性。
C)constructor
在该对象上创建一个属性并给出对该函数的引用。
D) 对当前变量绑定对象的引用存储在函数中。
创建一个新的空白对象,并根据匿名函数的prototype
属性为其分配原型。
调用该函数时将步骤 3 中的对象作为 传入,为this
调用的执行上下文创建(除其他外)一个变量绑定对象。
创建一个名为的属性privateMember
并将其分配给步骤 4 中的变量绑定对象。
值 42 分配给privateMember
VBO 的属性。
评估内部函数表达式,Function
创建一个对象(其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
使用该属性,因为当然在绝大多数情况下,它是从未使用过,因为绝大多数函数从未用作构造函数。因此,您的第一个表单可能会稍微高效一点,因为它可以跳过这些步骤。除非您做数千次,否则这种差异不太可能发生这些。)
FWIW,第一个也可以这样写,如果你关心的是行数,括号数,或者不喜欢对象字面量等:
var myObject1 = function(){
var obj = {};
var privateMember = 42;
obj.publicMember = function() {
return privateMember;
};
return obj;
}();
作为风格问题,我更喜欢显式返回,但这是风格问题。