闭包的创建也许是 JS 最有用也是最易被忽略的能力了。关于闭包如何工作的合理解释。也可以搜索脚本之家以前发布的文章
切勿在循环中创建函数
在简单的循环语句中加入函数是非常容易形成闭包而带来隐患的。下面的例子就是一个典型的陷阱:
不推荐
(function(log, w){ 'use strict'; // numbers and i is defined in the current function closure var numbers = [1, 2, 3], i; for(i = 0; i < numbers.length; i++) { w.setTimeout(function() { // At the moment when this gets executed the i variable, coming from the outer function scope // is set to 3 and the current program is alerting the message 3 times // 'Index 3 with number undefined // If you understand closures in javascript you know how to deal with those cases // It's best to just avoid functions / new closures in loops as this prevents those issues w.alert('Index ' + i + ' with number ' + numbers[i]); }, 0); } }(window.console.log, window));
接下来的改进虽然已经解决了上述例子中的问题或 bug,但还是违反了不在循环中创建函数或闭包的原则。
不推荐
(function(log, w){ 'use strict'; // numbers and i is defined in the current function closure var numbers = [1, 2, 3], i; for(i = 0; i < numbers.length; i++) { // Creating a new closure scope with an IIFE solves the problem // The delayed function will use index and number which are // in their own closure scope (one closure per loop iteration). // --- // Still this is not recommended as we violate our rule to not // create functions within loops and we are creating two! (function(index, number){ w.setTimeout(function() { // Will output as expected 0 > 1, 1 > 2, 2 > 3 w.alert('Index ' + index + ' with number ' + number); }, 0); }(i, numbers[i])); } }(window.console.log, window));
接下来的改进已解决问题,而且也遵循了规范。可是,你会发现看上去似乎过于复杂繁冗了,应该会有更好的解决方案吧。
不完全推荐
(function(log, w){ 'use strict'; // numbers and i is defined in the current function closure var numbers = [1, 2, 3], i; // Create a function outside of the loop that will accept arguments to create a // function closure scope. This function will return a function that executes in this // closure parent scope. function alertIndexWithNumber(index, number) { return function() { w.alert('Index ' + index + ' with number ' + number); }; } // First parameter is a function call that returns a function. // --- // This solves our problem and we don't create a function inside our loop for(i = 0; i < numbers.length; i++) { w.setTimeout(alertIndexWithNumber(i, numbers[i]), 0); } }(window.console.log, window));
将循环语句转换为函数执行的方式问题能得到立马解决,每一次循环都会对应地创建一次闭包。函数式的风格更加值得推荐,而且看上去也更加地自然和可预料。
推荐
(function(log, w){ 'use strict'; // numbers and i is defined in the current function closure var numbers = [1, 2, 3], i; numbers.forEach(function(number, index) { w.setTimeout(function() { w.alert('Index ' + index + ' with number ' + number); }, 0); }); }(window.console.log, window));
eval 函数(魔鬼)
eval() 不但混淆语境还很危险,总会有比这更好、更清晰、更安全的另一种方案来写你的代码,因此尽量不要使用 evil 函数。
this 关键字
只在对象构造器、方法和在设定的闭包中使用 this 关键字。this 的语义在此有些误导。它时而指向全局对象(大多数时),时而指向调用者的定义域(在 eval 中),时而指向 DOM 树中的某一节点(当用事件处理绑定到 HTML 属性上时),时而指向一个新创建的对象(在构造器中),还时而指向其它的一些对象(如果函数被 call() 和 apply() 执行和调用时)。
正因为它是如此容易地被搞错,请限制它的使用场景:
在构造函数中
在对象的方法中(包括由此创建出的闭包内)
首选函数式风格
函数式编程让你可以简化代码并缩减维护成本,因为它容易复用,又适当地解耦和更少的依赖。
接下来的例子中,在一组数字求和的同一问题上,比较了两种解决方案。第一个例子是经典的程序处理,而第二个例子则是采用了函数式编程和 ECMA Script 5.1 的数组方法。
例外:往往在重代码性能轻代码维护的情况之下,要选择最优性能的解决方案而非维护性高的方案(比如用简单的循环语句代替 forEach)。
不推荐