看到一篇介绍JS闭包的文章,写的非常易于理解,转载过来。
原文地址: http://blog.jobbole.com/77078/
词法闭包+作用域
我深信理解如何有效利用闭包和作用域是成为一个伟大JavaScript开发者的关键。
那么…什么是闭包?
可能需要个例子。
var createCounter = function() {
var count = 0;
return function() {
return ++count;
};
};
var counter1 = createCounter();
counter1();// 1
counter1();// 2
var counter2 = createCounter();
counter2();// 1
counter1();// 3
一旦createCounter函数被调用,变量count就被分配一个新的内存区域。然后,返回一个函数,这个函数持有对变量count的引用,并且每次调用的时候执行count加1操作。
注意从createCounter函数的作用域之外,我们是没有办法直接操作count的值。Counter1和Counter2函数可以操作各自的count变量的副本,但是只有在这种非
常具体的方式操作count(自增1)才是被支持的。
在JavaScript,作用域的边界检查只在函数被声明的时候。逐个函数,并且仅仅逐个函数,拥有它们各自的作用域表。(注:在ECMAScript 6中不再是这样,因为let的引入)
一些进一步的例子来证明这论点:
// global scope
var scope = "global";
var foo = function(){
// inner scope 1
var scope = "inner";
var myscope = function(){
// inner scope 2
return scope;
};
return myscope;
};
console.log(foo()()); // "inner"
console.log(scope); // "global"
关于作用域还有一些重要的事情需要考虑。例如,我们需要创建一个函数,接受一个数字(0-9),返回该数字相应的英文名称。
简单点,有人会这样写:
var names = ['zero','one','two','three','four','five','six','seven','eight','nine'];
var digit_name2 = function(n){
return names[n];
};
但是缺点是,names定义在了全局作用域,可能会意外的被修改,这样可能致使digit_name1函数所返回的结果不正确。那么,这样写:
var digit_name2 = function(n){
var names = ['zero','one','two','three','four','five','six','seven','eight','nine'];
return names[n];
};
这次把names数组定义成函数digit_name2局部变量.这个函数远离了意外风险,但是带来了性能损失,由于每次digit_name2被调用的时候,都将重新为names数组定义和分配空间。换个例子如果names是个非常大的数组,或者可能digit_name2函数在一个循环中被调用多次,这时候性能影响将非常明显。
这时候我们面临第三个选择。这里我们实现立即调用的函数表达式,仅仅实例化names变量一次,然后返回digit_name3函数,在 IIFE (Immediately-Invoked-Function-Expression 立即执行表达式)的闭包函数持有names变量的引用。
这个方案兼具前两个的优点,回避了缺点。搞定!这是一个常用的模式用来创建一个不可被外部环境修改“private”(私有)状态。
注:
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有父函数内部的子函数才能读取父函数的局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。》这就是Javascript语言特有的“链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。