闭包是Javascript中最多见的语法和形式之一,闭包能够避免全局污染,模块化编程。javascript
要理解闭包,先要熟悉scope, Javascript的基本概念java
Javacript中的做用域有两种jquery
定义在函数以内的变量处于local scope, 定义在函数以外的变量处于global scope, 函数每调用一次,就新生成一个scopeexpress
context和scope不一样,scope决定变量的可见性,context则决定了this的值,在一样的scope里编程
context是能够经过function methods修改的, .apply(), .bind(), .call()浏览器
execution context就是execution scope,里面的context和上面讲到context不同🤣闭包
由于Javascript是一个单线程语言,因此同一时间只能执行一个任务,其他的任务会在execution context排队等候。app
当Javascript interperter开始执行代码,scope首先会被设为global, global context会被添加到execution context,在那以后,每一次函数调用都会生成scope, 被添加到execution context中,须要注意的是,内部函数调用后,一样会将新的scope添加到外部函数的execution context中,也就是说,每一个函数会生成它本身的execution context。curl
一旦当前context里面的代码执行完毕(浏览器执行),这个context会从execution context中popped off(出栈),当前context的状态就会转换成parent contextide
浏览器老是执行栈顶部的execution context,其实就是最内部的scope, 代码的执行是从内而外
function parent () { child() } function child () { console.log('child') } parent()
图1 execution context
'variableObject': { // contains function arguments, inner variable and function declarations }
Scope Chain: 在variable object生成以后就会生成scope chain, scope chain 包含variable object,scope chain是用来解析变量的,当浏览器开始解析变量时,Javascript会从最里层的代码开始向外找,其实scope chain就是包含本身的execution context和父的execution context
'scopeChain': { // contains its own variable object and other variable objects of the parent execution contexts }
executionContextObject = { 'scopeChain': {}, // contains its own variableObject and other variableObject of the parent execution contexts 'variableObject': {}, // contains function arguments, inner variable and function declarations 'this': valueOfThis }
闭包就是内部函数访问外部函数的变量
A closure is an inner function that has access to the outer (enclosing) function’s variables—scope chain.
当一个函数被建立后,它就能够访问建立它的scope,若是函数innerFunc是在另外一个函数outerFunc内部建立的,那么innerFunc就能够访问建立它的outerFunc的scope, 即便outerFunc 执行结束了returns
示例:
function fnGenerator(str) { var stringToLog = 'The string I was given is "' + str + '"'; return function() { console.log(stringToLog); } } var fnReturned = fnGenerator('Bat-Man'); fnReturned(); // -> The string I was given is "Bat-Man"
即便上面的fnGenerator执行完了,它的scope仍然在内存中,它返回的函数依旧能够访问fnGenerator的scope
闭包有三种scope chains:
例子:
function showName (firstName, lastName) { var nameIntro = "Your name is "; // this inner function has access to the outer function's variables, including the parameter function makeFullName () { return nameIntro + firstName + " " + lastName; } return makeFullName (); } showName ("Michael", "Jackson"); // Your name is Michael Jackson
jquery的例子:
$(function() { var selections = []; $(".niners").click(function() { // this closure has access to the selections variable selections.push (this.prop("name")); // update the selections variable in the outer function's scope }); });
这样一个需求,给传入的参数加10, 或者20, 30...
function add10(num) { return num + 10; } function add20() { return num + 20; } function add30() { return num + 30; } ...
代码看来有重复,怎么解决呢?看看下面使用闭包来减小重复代码
function addFactory(storedNum) { return function(num2) { return storedNum + num2; } } var add10 = addFactory(10); var add20 = addFactory(20); var add30 = addFactory(30); console.log(add10(5)); // -> 15 console.log(add20(6)); // -> 26 console.log(add30(7)); // -> 37
addFactory 接收一个参数storedNum, 返回了一个函数,这个内部函数永久地保留了访问storedNum的权限,并且内部函数接收一个参数,加在storedNum上
每一次调用addFactory,会生成一个scope, 里面包含对传入的参数storedNum的访问权限,返回的函数能够访问这个scope,而且保留了对这个scope的访问权限,即便addFactory执行完毕
小结:若是咱们须要的函数绝大部分都相同,闭包经常是一个技巧
将内部的实现细节封装起来,只暴露接口给外部调用,更新代码,接口并不变化
示例:一个计数函数,每次调用都会+1
function counterGenerator() { var counter = 1; return function() { return counter++; } } var incrementCounter = counterGenerator(); console.log(incrementCounter()); // -> 1 console.log(incrementCounter()); // -> 2 counter = 100; // <- sets a new global variable 'counter'; // the one inside counterGenerator is unchanged console.log(incrementCounter()); // -> 3
上面的代码给调用者incrementCounter函数,隐藏了counterGenerator函数,incrementCounter是惟一操做counter变量的方法
闭包能够访问外部函数的变量,即便外部函数已经return
这是由于函数的执行使用的是同一个scope chain, 闭包内访问了外部函数的变量,当函数返回时,闭包的context并无出栈,从而该函数的context也没法出栈,这个scope chain一直存在
function celebrityName (firstName) { var nameIntro = "This celebrity is "; // this inner function has access to the outer function's variables, including the parameter function lastName (theLastName) { return nameIntro + firstName + " " + theLastName; } return lastName; } var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned. // The closure (lastName) is called here after the outer function has returned above // Yet, the closure still has access to the outer function's variables and parameter mjName ("Jackson"); // This celebrity is Michael Jackson
function celebrityID () { var celebrityID = 999; // We are returning an object with some inner functions // All the inner functions have access to the outer function's variables return { getID: function () { // This inner function will return the UPDATED celebrityID variable // It will return the current value of celebrityID, even after the changeTheID function changes it return celebrityID; }, setID: function (theNewID) { // This inner function will change the outer function's variable anytime celebrityID = theNewID; } } } var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned. mjID.getID(); // 999 mjID.setID(567); // Changes the outer function's variable mjID.getID(); // 567: It returns the updated celebrityId variable
// This example is explained in detail below (just after this code box). function celebrityIDCreator (theCelebrities) { var i; var uniqueID = 100; for (i = 0; i < theCelebrities.length; i++) { theCelebrities[i]["id"] = function () { return uniqueID + i; } } return theCelebrities; } var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}]; var createIdForActionCelebs = celebrityIDCreator (actionCelebs); var stalloneID = createIdForActionCelebs [0]; console.log(stalloneID.id()); // 103
在上面函数的循环体中,闭包访问了外部函数循环更新后的变量i,在stalloneID.id()执行前,i = 3,因此,结果为103,要解决这个问题,可使用 Immediately Invoked Function Expression (IIFE)
function celebrityIDCreator (theCelebrities) { var i; var uniqueID = 100; for (i = 0; i < theCelebrities.length; i++) { theCelebrities[i]["id"] = function (j) { // the j parametric variable is the i passed in on invocation of this IIFE return uniqueID + j; // each iteration of the for loop passes the current value of i into this IIFE and it saves the correct value to the array // returning just the value of uniqueID + j, instead of returning a function. } (i); // immediately invoke the function passing the i variable as a parameter } return theCelebrities; } var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}]; var createIdForActionCelebs = celebrityIDCreator (actionCelebs); var stalloneID = createIdForActionCelebs [0]; console.log(stalloneID.id); // 100 var cruiseID = createIdForActionCelebs [1]; console.log(cruiseID.id); // 101
通常状况下,若是闭包访问了外部循环变量,会和当即执行函数(immediately invoked function expression)结合使用,再看一个例子
for (var i = 0; i < 5; i++) { setTimeout( function() {console.log(i);}, i * 1000 ); }
结果是0,1,2,3,4秒后都log的是5,由于当i log的时候,循环已经执行完了,全局变量i变成了5
那么怎么让每秒log的是0,1,2,3,4呢?能够在IIFE里使用闭包,将变量循环的值传给当即执行函数
for (var i = 0; i < 5; i++) { setTimeout( (function(num) { return function() { console.log(num); } })(i), i * 1000 ); } // -> 0 // -> 1 // -> 2 // -> 3 // -> 4
咱们在setTimeout里当即执行了匿名函数,传递了i给num, 闭包返回的函数将log num, 返回的函数将在setTimeout 0,1,2,3,4秒 后执行
参考资料:https://scotch.io/tutorials/understanding-scope-in-javascript#toc-scope-in-javascript
http://javascriptissexy.com/understand-javascript-closures-with-ease/