让我们开始
在 JavaScript 中,每一个方法被调用时都会创建一个新的 execution context (2) 。因为在一个方法中定义的变量和方法只能从内部访问,从外部则不能,而这个调用着方法的 context 就让我们拥有了一个非常简单的途径来实现私有化。
在大部分情况下,你不需要拥有这里的 makeCounter 的多个实例,一个对你已经够用了,在其它的一些情况下,你甚至都不会明确的返回一个值。
核心问题
无论你是通过 foo(){} 或 var foo = function(){} 来定义一个方法,都可以通过在其后放一对括号来调用它,就像这样 foo() 。
就像你看到的,报错了。当解析器遇到一个 function 关键字时,不管是在全局范围或是在另一个方法里,它都会默认的被当作一个 function declaration (3) 而非一个 function expression (4) 来对待。如果你不明确的告诉解析器它正在处理的是一个 expression,它就只会将其当作一个缺少标识符的 function declaration 来对待并因此抛出一个语法错误,因为一个 function declaration 需要一个标识符。
方法,括号和语法错误
如果你给方法指定一个名称,解析器依旧会抛出语法错误,不过这次是全然不同的原因。当()被放置在一个 function declaration 之后时,仅仅会被当作一个分组操作符而已。
如果你对此想了解更多,请参考 Dmitry A. Soshnikov 的 ECMA-262-3 in detail. Chapter 5. Functions. ,里面有更详细的相关内容。
Immediately-Invoked Function Expression (IIFE)
幸运的是,‘修复’语法错误很容易。让解析器‘明确’它正在操作的是一个 function expression 只需要用一个()将它们包裹起来,因为在 JavaScript 里,括号中是不能包含指令的。这样,当解析器遇到 function 关键字时,就会知道将它作为一个 function expression解析,而不是一个 function declaration。
关于()的必要性
有时候这些的额外的用来‘消除歧义’的()是多余的(比如解析器在它之前已经遇到了另一个表达式),但作为一个良好惯例我们还是加上它为好。
加上()可以让人知道这个方法会被立即调用,变量指向的是方法的返回结果而非方法本身。这可以方便其他人阅读你的代码,当你的方法很长时,如果不加上(),别人就需要一直滚屏到方法末尾来检查方法是否已经被调用。
明晰的代码不但有必要防止编译器抛出语法错误,也同样有必要防止别人向你抛出“WTFError” (5)
通过闭包保存状态
当我们通过一个方法的标识符调用方法时我们可以对其传递参数,同样的,在使用 IIFE 时我们也可以传递参数。因为这里传入的参数在方法(也就是闭包)中是被‘锁定’的,所以一个 Immediately-Invoked Function Expression 可以有效的被用来保存状态。
如果你想了解闭包的更多知识,请阅读闭包在 JavaScript 中的解释。
注意以上两个实例,虽然这里的‘锁定索引’可以写作 i,没有任何执行问题,但用一个类似方法参数的标识符来代替 i 显然能使代码更具有解释性。
使用 Immediately-Invoked Function Expressions 带来的另一个好处是不会污染当前作用域,因为它是匿名的,没有使用标识符。
“Self-executing anonymous function”有什么问题?
你可能早就听说过这个叫法了,但事实上它并不怎么准确。我认为它应该称作“Immediately-Invoked Function Expression”,或者“IIFE”————如果你喜欢首字母缩写。有人建议将它发音成“iffy”,我挺喜欢,就这么念好了。
什么是 Immediately-Invoked Function Expression?就是一个被立即调用的方法 expression,就像它的名字告诉我们的。
我希望看到 JavaScript 社区里更多人使用“Immediately-Invoked Function Expression”和“IIFE”的称呼,我觉得这个名称可以更好的诠释这种模式的概念,而“self-executing anonymous function”确实不够准确。
希望这些例子能帮助大家理解为什么“self-executing”是一种有误导性的说法,因为它并不是方法调用方法自身。同样,匿名也是一个不必要的说词,因为一个立即调用的方法表达式既可以是匿名的也可以是命名的。之所以我用了“调用”而不是“执行”,是因为我认为“IIFE”比“IEFE”看起来更顺眼些,当然读起来也是。
好了,这就是我伟大的想法。
对了,因为在 ECMAScript 5 严格模式下 arguments.callee 已经被弃用,所以技术上来说,在 ECMAScript 5 严谨模式下创造一个“self-executing anonymous function”是不可能的。
关于模块模式
既然都说到了这,那么附带提提模块模式也就成了顺利成章的事。如果你对 JavaScript 的模块模式不熟悉,请回看我的第一个代码示例,只不过用返回一个 Object 来代替返回一个 function(通常像这个例子一样它都是以单例的方式实现的)
模块模式不但强大而且简单。极短的代码就可以让你的方法和属性具有命名空间,最大限度的避免污染全局域并创建私有对象。
延伸阅读
这些文章可以帮助你了解更多更全面的关于方法和模块模式的相关知识。
ECMA-262-3 in detail. Chapter 5. Functions. - Dmitry A. Soshnikov
Functions and function scope - Mozilla Developer Network
Named function expressions - Juriy “kangax” Zaytsev
JavaScript Module Pattern: In-Depth - Ben Cherry
Closures explained with JavaScript - Nick Morgan
在 JavaScript 中,每一个方法被调用时都会创建一个新的 execution context (2) 。因为在一个方法中定义的变量和方法只能从内部访问,从外部则不能,而这个调用着方法的 context 就让我们拥有了一个非常简单的途径来实现私有化。
在大部分情况下,你不需要拥有这里的 makeCounter 的多个实例,一个对你已经够用了,在其它的一些情况下,你甚至都不会明确的返回一个值。
核心问题
无论你是通过 foo(){} 或 var foo = function(){} 来定义一个方法,都可以通过在其后放一对括号来调用它,就像这样 foo() 。
就像你看到的,报错了。当解析器遇到一个 function 关键字时,不管是在全局范围或是在另一个方法里,它都会默认的被当作一个 function declaration (3) 而非一个 function expression (4) 来对待。如果你不明确的告诉解析器它正在处理的是一个 expression,它就只会将其当作一个缺少标识符的 function declaration 来对待并因此抛出一个语法错误,因为一个 function declaration 需要一个标识符。
方法,括号和语法错误
如果你给方法指定一个名称,解析器依旧会抛出语法错误,不过这次是全然不同的原因。当()被放置在一个 function declaration 之后时,仅仅会被当作一个分组操作符而已。
如果你对此想了解更多,请参考 Dmitry A. Soshnikov 的 ECMA-262-3 in detail. Chapter 5. Functions. ,里面有更详细的相关内容。
Immediately-Invoked Function Expression (IIFE)
幸运的是,‘修复’语法错误很容易。让解析器‘明确’它正在操作的是一个 function expression 只需要用一个()将它们包裹起来,因为在 JavaScript 里,括号中是不能包含指令的。这样,当解析器遇到 function 关键字时,就会知道将它作为一个 function expression解析,而不是一个 function declaration。
关于()的必要性
有时候这些的额外的用来‘消除歧义’的()是多余的(比如解析器在它之前已经遇到了另一个表达式),但作为一个良好惯例我们还是加上它为好。
加上()可以让人知道这个方法会被立即调用,变量指向的是方法的返回结果而非方法本身。这可以方便其他人阅读你的代码,当你的方法很长时,如果不加上(),别人就需要一直滚屏到方法末尾来检查方法是否已经被调用。
明晰的代码不但有必要防止编译器抛出语法错误,也同样有必要防止别人向你抛出“WTFError” (5)
通过闭包保存状态
当我们通过一个方法的标识符调用方法时我们可以对其传递参数,同样的,在使用 IIFE 时我们也可以传递参数。因为这里传入的参数在方法(也就是闭包)中是被‘锁定’的,所以一个 Immediately-Invoked Function Expression 可以有效的被用来保存状态。
如果你想了解闭包的更多知识,请阅读闭包在 JavaScript 中的解释。
注意以上两个实例,虽然这里的‘锁定索引’可以写作 i,没有任何执行问题,但用一个类似方法参数的标识符来代替 i 显然能使代码更具有解释性。
使用 Immediately-Invoked Function Expressions 带来的另一个好处是不会污染当前作用域,因为它是匿名的,没有使用标识符。
“Self-executing anonymous function”有什么问题?
你可能早就听说过这个叫法了,但事实上它并不怎么准确。我认为它应该称作“Immediately-Invoked Function Expression”,或者“IIFE”————如果你喜欢首字母缩写。有人建议将它发音成“iffy”,我挺喜欢,就这么念好了。
什么是 Immediately-Invoked Function Expression?就是一个被立即调用的方法 expression,就像它的名字告诉我们的。
我希望看到 JavaScript 社区里更多人使用“Immediately-Invoked Function Expression”和“IIFE”的称呼,我觉得这个名称可以更好的诠释这种模式的概念,而“self-executing anonymous function”确实不够准确。
希望这些例子能帮助大家理解为什么“self-executing”是一种有误导性的说法,因为它并不是方法调用方法自身。同样,匿名也是一个不必要的说词,因为一个立即调用的方法表达式既可以是匿名的也可以是命名的。之所以我用了“调用”而不是“执行”,是因为我认为“IIFE”比“IEFE”看起来更顺眼些,当然读起来也是。
好了,这就是我伟大的想法。
对了,因为在 ECMAScript 5 严格模式下 arguments.callee 已经被弃用,所以技术上来说,在 ECMAScript 5 严谨模式下创造一个“self-executing anonymous function”是不可能的。
关于模块模式
既然都说到了这,那么附带提提模块模式也就成了顺利成章的事。如果你对 JavaScript 的模块模式不熟悉,请回看我的第一个代码示例,只不过用返回一个 Object 来代替返回一个 function(通常像这个例子一样它都是以单例的方式实现的)
模块模式不但强大而且简单。极短的代码就可以让你的方法和属性具有命名空间,最大限度的避免污染全局域并创建私有对象。
延伸阅读
这些文章可以帮助你了解更多更全面的关于方法和模块模式的相关知识。
ECMA-262-3 in detail. Chapter 5. Functions. - Dmitry A. Soshnikov
Functions and function scope - Mozilla Developer Network
Named function expressions - Juriy “kangax” Zaytsev
JavaScript Module Pattern: In-Depth - Ben Cherry
Closures explained with JavaScript - Nick Morgan