(關注福利,關注本公衆號回覆[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導)前端
本週正式開始前端進階的第二期,本週的主題是做用域閉包,今天是第7天。webpack
本計劃一共28期,每期重點攻克一個面試重難點,若是你還不瞭解本進階計劃,點擊查看前端進階的破冰之旅git
若是以爲本系列不錯,歡迎轉發,您的支持就是我堅持的最大動力。github
JavaScript深刻之閉包 ,因爲微信不能訪問外鏈,點擊閱讀原文就能夠啦。web
本文是從做用域鏈的角度來介紹閉包,不一樣於上文圖解做用域和閉包,本文語言簡練,結構清晰,相比上文要容易理解些。建議搭配上文一塊兒閱讀。面試
紅寶書(p178)上對於閉包的定義:閉包是指有權訪問另一個函數做用域中的變量的函數,算法
MDN 對閉包的定義爲:閉包是指那些可以訪問自由變量的函數。跨域
其中自由變量,指在函數中使用的,但既不是函數參數arguments
也不是函數的局部變量的變量,其實就是另一個函數做用域中的變量。數組
使用上一篇文章的例子來講明下自由變量:【進階2-1期】深刻淺出圖解做用域鏈和閉包瀏覽器
function getOuter(){ var date = '1127'; function getDate(str){ console.log(str + date); //訪問外部的date } return getDate('今天是:'); //"今天是:1127" } getOuter();
其中date
既不是參數arguments
,也不是局部變量,因此date
是自由變量。
總結起來就是下面兩點:
首先來一個簡單的例子
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); // foo指向函數f foo(); // 調用函數f()
簡要的執行過程以下:
那麼問題來了, 函數f 執行的時候,checkscope 函數上下文已經被銷燬了,那函數f是如何獲取到scope變量的呢?
上文(【進階2-1期】深刻淺出圖解做用域鏈和閉包)介紹過,函數f 執行上下文維護了一個做用域鏈,會指向指向checkscope
做用域,做用域鏈是一個數組,結構以下。
fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }
因此指向關係是當前做用域 --> checkscope
做用域--> 全局做用域,即便 checkscopeContext 被銷燬了,可是 JavaScript 依然會讓 checkscopeContext.AO(活動對象) 活在內存中,f 函數依然能夠經過 f 函數的做用域鏈找到它,這就是閉包實現的關鍵。
上面介紹的是實踐角度,其實閉包有不少種介紹,說法不一。
湯姆大叔翻譯的關於閉包的文章中的定義,ECMAScript中,閉包指的是:
二、從實踐角度:如下函數纔算是閉包:
var data = []; for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();
若是知道閉包的,答案就很明顯了,都是3
循環結束後,全局執行上下文的VO是
globalContext = { VO: { data: [...], i: 3 } }
執行 data[0] 函數的時候,data[0] 函數的做用域鏈爲:
data[0]Context = { Scope: [AO, globalContext.VO] }
因爲其自身沒有i變量,就會向上查找,全部從全局上下文查找到i爲3,data[1] 和 data[2] 是同樣的。
改爲閉包,方法就是data[i]
返回一個函數,並訪問變量i
var data = []; for (var i = 0; i < 3; i++) { data[i] = (function (i) { return function(){ console.log(i); } })(i); } data[0](); // 0 data[1](); // 1 data[2](); // 2
循環結束後的全局執行上下文沒有變化。
執行 data[0] 函數的時候,data[0] 函數的做用域鏈發生了改變:
data[0]Context = { Scope: [AO, 匿名函數Context.AO, globalContext.VO] }
匿名函數執行上下文的AO爲:
匿名函數Context = { AO: { arguments: { 0: 0, length: 1 }, i: 0 } }
由於閉包執行上下文中貯存了變量i
,因此根據做用域鏈會在globalContext.VO
中查找到變量i
,並輸出0。
上面必刷題改動一個地方,把for循環中的var i = 0
,改爲let i = 0
。結果是什麼,爲何???
var data = []; for (let i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();
JavaScript深刻之閉包
每週面試重難點計劃以下,若有修改會通知你們。每週一期,爲期半年,準備明年跳槽的小夥伴們能夠把本公衆號[置頂]()了。
本人Github連接以下,歡迎各位Star
http://github.com/yygmind/blog
我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!
若是你想加羣討論每期面試知識點,公衆號回覆[加羣]便可