译者按: 在上一篇博客,咱们经过实现一个计数器,了解了如何使用闭包(Closure),这篇博客将提供一些代码示例,帮助你们理解闭包。javascript
原文: JavaScript Closures for Dummieshtml
译者: Fundebugjava
为了保证可读性,本文采用意译而非直译。另外,本文版权归原做者全部,翻译仅用于学习。程序员
其实,只要你领会了闭包的关键概念,一切就很是简单了。做为JavaScript开发者,你应该能够理解如下代码:web
function sayHello(name) { var text = 'Hello ' + name; var sayAlert = function() { console.log(text); } sayAlert(); } sayHello("Bob") // 输出"Hello Bob"
在sayHello()函数中定义并调用了sayAlert()函数;sayAlert()做为内层函数,能够访问外层函数sayHello()中的text变量。理解这一点,你就能够继续阅读这篇博客了。小程序
两句话总结闭包(注意,这个定义并不规范,可是有助于理解):微信小程序
function sayHello2(name) { var text = 'Hello ' + name; // 局部变量 var sayAlert = function() { console.log(text); } return sayAlert; } var say2 = sayHello2("Jane"); say2(); // 输出"Hello Jane"
调用sayHello2()函数返回了sayAlert,它是一个引用变量,指向一个函数。相信大多数JavaScript程序员可以理解什么是引用变量,而C程序员则能够把sayAlert以及say2理解为指向函数的指针。数组
C指针与JavaScript引用变量并没有实质区分。在JavaScript中,不妨这样理解,指向函数的引用变量不只指向函数自己,还隐含地指向了一个闭包。微信
代码中匿名函数**function() { alert(text); }是在另外一个函数,即sayHello2()**中定义的。在JavaScript中,若是你在函数中定义了一个函数,则建立了闭包。闭包
对于C语言,以及其余绝大多数语言:函数return以后,其局部变量将没法访问,由于内存中的堆栈会被销毁。
对于JavaScript,若是你在函数中定义函数的话,当外层函数return以后,其局部变量仍然能够访问。代码中已经证实了这一点:当sayHello2()函数return以后,咱们调用了say2()函数,成功打印了text变量,而text变量正是**sayHello2()**函数的局部变量。
若是只是从定义的角度去理解闭包,显然是很是困难。然而,若是经过代码示例去理解闭包,则简单不少。所以,强烈建议你认真地理解每个示例,弄清楚它们是如何运行的,这样你会避免不少奇怪的BUG。
Example 3中,say667()函数return后,num变量将仍然保留在内存中。而且,sayNumba函数中的num变量并不是复制而是引用,所以它输出的是667而非666。
function say667() { var num = 666; // say667()函数return后,num变量将仍然保留在内存中 var sayAlert = function() { console.log(num); } num++; return sayAlert; } var sayNumba = say667(); sayNumba(); // 输出667
Example 4中,3个全局函数gAlertNumber,gIncreaseNumber,gSetNumber指向了同一个闭包,由于它们是在同一次setupSomeGlobals()调用中声明的。它们所指向的闭包就是setupSomeGlobals()函数的局部变量,包括了num变量。也就是说,它们操做的是同一个num变量。
function setupSomeGlobals() { var num = 666; gAlertNumber = function() { console.log(num); } gIncreaseNumber = function() { num++; } gSetNumber = function(x) { num = x; } } setupSomeGlobals(); gAlertNumber(); // 输出666 gIncreaseNumber(); gAlertNumber(); // 输出667 gSetNumber(5); gAlertNumber(); // 输出5
Example 5的代码比较难,很多人都会犯一样的错误,由于它的执行结果极可能违背了你的直觉。
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + list[i]; result.push( function() { console.log(item + ' ' + list[i])} ); } return result; } var fnlist = buildList([1,2,3]); for (var j = 0; j < fnlist.length; j++) { fnlist[j](); // 连续输出3个"item3 undefined" }
**result.push( function() {alert(item + ' ' + list[i])}将指向匿名函数function() {alert(item + ' ' + list[i])}**的引用变量加入了数组,其效果等价于:
pointer = function() {alert(item + ' ' + list[i])}; result.push(pointer);
代码执行后,连续输出了3个"item3 undefined",明显与直觉不一样。
调用buildList()函数以后,咱们获得了一个数组,数组中有3个函数,而这3个函数指向了同一个闭包。而闭包中的item变量值为**"item3",i变量值为3**。若是理解了3个函数指向的是同一个闭包,则输出结果就不难理解了。
Example 6中,alice变量在sayAlert函数以后定义,这并未影响代码执行。由于返回函数sayAlice2所指向的闭包会包含sayAlice()函数中的全部局部变量,这天然包括了alice变量,所以能够正常打印"Hello Alice"。
function sayAlice() { var sayAlert = function() { console.log(alice); } var alice = 'Hello Alice'; return sayAlert; } var sayAlice2 = sayAlice(); sayAlice2(); // 输出"Hello Alice"
由Example 7可知,每次调用newClosure()都会建立独立的闭包,它们的局部变量num与ref的值并不相同。
function newClosure(someNum, someRef) { var anArray = [1,2,3]; var num = someNum; var ref = someRef; return function(x) { num += x; anArray.push(num); console.log('num: ' + num + "; " + 'anArray ' + anArray.toString() + "; " + 'ref.someVar ' + ref.someVar); } } closure1 = newClosure(40, {someVar: "closure 1"}); closure2 = newClosure(1000, {someVar: "closure 2"}); closure1(5); // 打印"num: 45; anArray 1,2,3,45; ref.someVar closure 1" closure2(-10); // 打印"num: 990; anArray 1,2,3,990; ref.someVar closure 2"
严格来说,我对闭包的解释并不许确。不过,将闭包简单地看作局部变量,理解起来会更加简单。
Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了7亿+错误事件,获得了Google、360、金山软件、百姓网等众多知名用户的承认。欢迎免费试用!
转载时请注明做者Fundebug以及本文地址: https://blog.fundebug.com/2017/08/07/javascript-closure-examples/