在上一篇介绍JavaScript this 关键字的文章中我们提到了闭包这个概念。闭包是指有权访问另一个函数作用域中的变量的函数。从函数对象中能够对外部变量进行访问(引用、更新),是构成闭包的条件之一。创建闭包的常见方式,就是在一个函数内部创建另一个函数。为了理解闭包,先来看一下什么是变量的生命周期。
变量的声明周期,就是变量的寿命,相对于表示程序中变量可见范围的作用域来说,生命周期这个概念指的是一个变量可以在多长的周期范围内存在并能够被访问。看下面一个例子:
functionextent() { var n = 0; return function() { n++; console.log("n=" + n); }; } var returnFun = extent(); returnFun(); //"n=1" returnFun(); //"n=2"
局部变量 n 是在 extent 函数中声明的,这个从属于外部作用域中的局部变量,被函数对象给封闭在里面了。被封闭起来的变量的寿命,与封闭它的函数对象的生命周期相同,即闭包延长了局部变量的声明周期。
闭包的概念比较抽象,来来回回反反复复就是那一句话,并不好理解,下面主要通过几个例子来看一下如何使用闭包。
1. 第一个例子:在循环中使用闭包
<!-- JavaScriptSnippet_1 Begin --> function createFunction() { var result = []; for(var i = 0; i < 10; i++) { result[i] = function() { return i; }; } return result; }; //调用 result[0](); //9 result[1](); //9 … … result[9](); // 9 <!-- JavaScriptSnippet_1 End --> <!-- JavaScriptSnippet_2 Begin --> function createFunction() { var result = []; for(var i = 0; i < 10; i++) { (function(num) { result[i] = function() { return num; }; })(i); } return result; } //调用 result[0](); //0 result[1](); //1 … … result[9](); //9 <!-- JavaScriptSnippet_2 End --> <!-- JavaScriptSnippet_3 Begin --> function createFunction() { var result = []; for(var i = 0; i < 10; i++) { result[i] = (function(num) { return function() { return num; }; })(i); } return result; } //调用 result[0](); //0 result[1](); //1 … … result[9](); //9 <!-- JavaScriptSnippet_3 End --> <!-- JavaScriptSnippet_4 Begin --> function createFunction() { var result = []; for(var i = 0; i < 10; i++) { result[i] = (function(num) { return function() { return num; }; }(i)); } return result; } //调用 result[0](); //0 result[1](); //1 … … result[9](); //9 <!-- JavaScriptSnippet_4 End --> <!-- JavaScriptSnippet_5 Begin --> function createFunction() { var result = []; for(var i = 0; i < 10; i++) { result[i] = function(num) { return function() { return num; }; }(i); } return result; } //调用 result[0](); //0 result[1](); //1 … … result[9](); //9 <!-- JavaScriptSnippet_5 End -->
2. 第二个例子:结合例一再次理解一下闭包
<!-- HTML Begin--> <pid="help">Helpful notes will appear here</p> <p>E-mail: <input type="text" id="email" name="email"></p> <p>Name: <input type="text" id="name" name="name"></p> <p>Age: <input type="text" id="age" name="age"></p> <!-- HTML End--> <!--JavaScript Common Snippet Begin --> function showHelp(info) { document.getElementById(‘help’).innerHTML =info; } //为了之后调用 function closureFunc(info) { return function() { showHelp(info); }; } function setupHelp() { var helpText = [ {id: "email", info: "Please Input YourEmail Address"}, {id: "name", info: "Please Input Your Name"}, {id: "age", info: "Please Input Your Age"} ]; <!-- JavaScript Common Snippet End --> <!-- JavaScript Snippet_1 Begin --><!-- Can Not --> for(var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.info); } } <!-- JavaScript Snippet_1 End --> <!-- JavaScript Snippet_2 Begin --><!-- Can --> for(var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = closureFunc(item.info); } <!-- JavaScript Snippet_2 End --> <!-- JavaScript Snippet_3 Begin --><!-- Can --> for(var i = 0; i < helpText.length; i++) { (function(num) { var item = helpText[num]; document.getElementById(item.id).onfocus = function() { showHelp(item.info); }; })(i); } <!-- JavaScript Snippet_3 End --> <!-- JavaScript Snippet_4 Begin --><!-- Can --> for(var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = (function(info) { return function() { showHelp(info); }; })(item.info); } <!-- JavaScript Snippet_4 End --> <!-- JavaScript Snippet_5 Begin --><!-- Can --> for(var i = 0; i < helpText.lenght; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = ( function(info) { return function() { showHelp(info); }; }(item.info) ); } <!-- JavaScript Snippet_5 End --> <!-- JavaScript Snippet_6 Begin --><!-- Can --> for(var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function(info) { return function() { showHelp(info); }; }(item.info); } <!-- JavaScript Snippet_6 End --> } setupHelp();
代码片段6也能成功,但建议最好使用括号将其括起来,即推荐使用代码片段4或代码片段5的形式。如果直接在JavaScript中这样写:
function(info) { return function() { showHelp(info); }; }(item.info);
会导致错误,这是因为JavaScript 把 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号,然而函数表达式后面可以跟圆括号。要想将函数声明转换为函数表达式,只要像下面这样给它加上一对圆括号:
(function(info){ return function() { showHelp(info); }; })(item.info);
3. 第三个例子:创建公有方法访问私有变量
var Counter = (function() { var privateCounter = 0; var changeBy = function(val) { privateCounter += val; }; return { getCounter: function() { return privateCounter; }, setCounter: function(newCounter) { privateCounter = newCounter; }, increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); } }; })(); //调用 Counter.privateCounter //undefined Counter.getCounter(); //0 Counter.setCounter(100); Counter.getCounter(); //100 Counter.increment(); Counter.getCounter(); //101 Counter.decrement(); Counter.getCounter(); //100 //改造为工厂类 var makeCounter = function() { var privateCounter = 0; var changeBy = function(val) { privateCounter += val; }; return { getCounter: function() { return privateCounter; }, setCounter: function(newCounter) { privateCounter = newCounter; }, increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); } }; }; //生成及调用 var Counter_1 = makeCounter(), Counter_2 = makeCounter(); Counter_1.getCounter(); //0 Counter_2.getCounter(); //0 Counter_1.setCounter(88); Counter_2.getCounter(); //0
4. 第四个例子:模仿块级作用域
在 Java ,C 等许多编程语言中都有块级作用域的概念,以 Java 为例:
for(int i = 0; i< 10; i++) { //do something } System.out.println(i); //i cannot be resolved to a variable
而 JavaScript中没有块级作用域的概念,如:
for(var i = 0; i< 10; i++) { //do something } var i; console.log(i); //10 var i = 5; console.log(i); //5
很多情况下为了不污染全局命名空间,可以使用闭包来模拟块级作用域:
(function() { var just_a_number = 100; console.log(just_a_number); //100 })(); console.log(just_a_number);//Uncaught ReferenceError: just_a_number is not defined
关于闭包就说这么多,重在理解。
完。
参考/扩展资料:
《JavaScript高级程序设计(第3版)》 作者: Nicholas C.Zakas 译者:李松峰曹力
http://www.zhihu.com/question/19554716
http://www.cnblogs.com/dolphinX/archive/2012/09/29/2708763.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
http://bonsaiden.github.io/JavaScript-Garden/zh/#function.closures