在查看 Github 上的 CoffeeScript 源代码 时,我注意到大多数(如果不是全部)模块都定义如下:
(function() {
...
}).call(this);
这个模式看起来像是将整个模块包装在一个匿名函数中并调用它本身。
这种方法的优点(和缺点)是什么?有没有其他实现相同目标的方法?
在查看 Github 上的 CoffeeScript 源代码 时,我注意到大多数(如果不是全部)模块都定义如下:
(function() {
...
}).call(this);
这个模式看起来像是将整个模块包装在一个匿名函数中并调用它本身。
这种方法的优点(和缺点)是什么?有没有其他实现相同目标的方法?
Harmen的回答非常好,但让我稍微详细解释一下为什么这是由CoffeeScript编译器完成的。
当你使用coffee -c foo.coffee
编译某个代码时,你会得到一个foo.js
文件,其代码看起来像这样:
(function() {
...
}).call(this);
为什么呢?好吧,假设你进行了如下的赋值操作:
x = 'stringy string'
在foo.coffee
中,编译器会这样问:在当前作用域或者外部作用域是否已经存在x
?如果不存在,它会在 JavaScript 输出的顶部放置一个var x
声明。
现在假设你写下:
x = 42
在 bar.coffee
中编译两个文件,然后在部署时将 foo.js
和 bar.js
进行合并。你会得到:
(function() {
var x;
x = 'stringy string';
...
}).call(this);
(function() {
var x;
x = 42;
...
}).call(this);
因此,在foo.coffee
中的x
和bar.coffee
中的x
是完全独立的。这是 CoffeeScript 的一个重要特点:变量永远不会从一个 .coffee 文件泄漏到另一个文件中,除非显式地导出(通过附加到共享全局或附加到 Node.js 中的 exports
)。-b
("裸")标志覆盖此行为,但这只应在非常特殊的情况下使用。 如果您在上面的示例中使用它,则会得到以下输出。var x;
x = 'stringy string';
...
var x;
x = 42;
...
这可能会带来严重的后果。想要自行测试,请尝试在foo.coffee
中添加setTimeout(-> alert x), 1
。请注意,您不必手动连接这两个JS文件 - 如果您使用两个单独的<script>
标签将它们包含在页面中,则它们仍然有效地运行为一个文件。
通过隔离不同模块的作用域,CoffeeScript编译器使您免于担心项目中的不同文件可能使用相同的局部变量名称而感到头痛。这是JavaScript世界的常见做法(例如,查看jQuery源代码或几乎任何jQuery插件),但是CoffeeScript已经为您处理好了。
这种方法的好处是它创建了私有变量,因此不会与变量名称发生冲突:
(function() {
var privateVar = 'test';
alert(privateVar); // test
})();
alert(typeof privateVar); // undefined
添加.call(this)
使得this
关键字引用外部函数中的相同值。如果不添加,则this
关键字将自动引用全局对象。
下面是一个小例子来展示不同之处:
function coffee(){
this.val = 'test';
this.module = (function(){
return this.val;
}).call(this);
}
var instance = new coffee();
alert(instance.module); // test
function coffee(){
this.val = 'test';
this.module = (function(){
return this.val;
})();
}
var instance = new coffee();
alert(typeof instance.module); // undefined
这与此类似的语法:
(function() {
}());
这被称为立即函数。该函数被定义并立即执行。这样做的好处是,您可以将所有代码放在此块内,并将函数分配给单个全局变量,从而减少全局命名空间污染。它在函数内提供了一个良好的封闭作用域。
这是我编写模块时使用的典型模式:
var MY_MODULE = (function() {
//local variables
var variable1,
variable2,
_self = {},
etc
// public API
_self = {
someMethod: function () {
}
}
return _self;
}());
不确定可能存在哪些缺点,如果有人知道任何缺点,我很乐意了解。