摘要:回憶我一年前,雖然使用過很多,但卻完全不理解閉包是什么。就算你,也會在循環(huán)完成時,輸出次當然,不要以為主要的原因是延遲函數(shù)會在循環(huán)結(jié)束時才執(zhí)行,不然我為什么會在閉包這一節(jié)用使用這個例子,哈哈。
前言
在了解閉包的概念時,我希望你能夠有JavaScript詞法作用域的知識,因為它會讓你更容易讀懂這篇文章。
感觸對于那些使用過JavaScript但卻完全不理解閉包概念的人來說,理解閉包可以看做是某種意義上的重生,但是你需要付出大量的努力和犧牲才能理解這個概念。
回憶我一年前,雖然使用過很多JavaScript,但卻完全不理解閉包是什么。當我了解到模塊模式的時候,我才激動地發(fā)現(xiàn)了原來這就是閉包?
JavaScript中閉包無處不在,你只需要能夠識別并擁抱它。開始
直接上定義
當函數(shù)可以記住并訪問所在的作用域時,就產(chǎn)生了閉包。即函數(shù)是在當前詞法作用域之外執(zhí)行。
function foo () { const a = 2 function bar () { console.log(a) } return bar } const baz = foo() baz() // 2 --- 媽媽呀!這就是閉包?太簡單了吧!
函數(shù)foo()使用它的內(nèi)部方法 bar()作為返回值,而bar()內(nèi)部有著對foo()作用域的引用(即a),在執(zhí)行foo()過后,內(nèi)部函數(shù)bar()賦值給baz,調(diào)用baz()顯然可以執(zhí)行bar()。
可以看到bar()在自身作用域之外執(zhí)行了,通常在foo()執(zhí)行過后,我們會覺得foo()會被JS引擎的垃圾回收機制銷毀,實際上并不會,因為baz有著對bar()的引用,而bar()內(nèi)部有著foo()作用域的引用,因此foo()并不會被銷毀,以供bar()在任何時間被引用,因此bar()記住了并訪問了自身所在的foo()作用域。
當然,這兒還有另外一個例子:
function foo () { const a = 2 function baz () { console.log(a) } bar(baz) } function bar (fn) { fn() // 這就是閉包 }
本例中,baz()在foo()之外調(diào)用,并且baz()自身有著涵蓋foo()作用域的引用,因此baz()可以記住foo()的作用域,保證其不會被垃圾回收機制銷毀
現(xiàn)在我懂了上一節(jié)的代碼過于死板,我們來看看更實用的代碼。
function wait (message) { setTimeout(function timer () { console.log(message) }, 1000) } wait("hello")
很明顯,內(nèi)部函數(shù)timer()持有對wait()的閉包
或者在jQuery中
function setupBot (name, selector) { $(selector).click(function activator () { console.log(name) }) } setupBot ("hello", "#bot")
可以看到,閉包在你寫的代碼中無處不在,特別是回調(diào)函數(shù),全是閉包
循環(huán)與閉包給一個經(jīng)典的案例
for(var i = 1 ; i <= 5 i ++) { setTimeout(function timer () { console.log(i) }, i * 1000) }
你可能會天真的以為它會輸出:1,2,3,4,5?
事實上,它會以每秒一次的頻率輸出5次6
為什么?
因為延遲函數(shù)會在循環(huán)結(jié)束時才執(zhí)行。就算你setTimeout(...,0),也會在循環(huán)完成時,輸出5次6
當然,不要以為主要的原因是延遲函數(shù)會在循環(huán)結(jié)束時才執(zhí)行,不然我為什么會在閉包這一節(jié)用使用這個例子,哈哈。
那么真正導致這個與預(yù)期不符的是閉包
首先內(nèi)部函數(shù)timer()有著涵蓋for循環(huán)的閉包,這5次調(diào)用timer()都是封閉在同一個作用域中,他們共享同一個i,只有一個i
那么我們?nèi)绾巫屗凑瘴覀兊念A(yù)期,輸出1,2,3,4,5呢?
當然是讓每個timer(),都有一個屬于自己的i,這里的解決方案有很多:
IIFE立即執(zhí)行函數(shù)可以形成一個塊作用域,我們只需要把每次迭代的i,保存在timer()的塊作用域中,通過這個保存的值打印出來就ok了
for(var i = 1 ; i <= 5; i ++) { (function() { var j = i setTimeout(function timer () { console.log(j) }, i * 1000) } )(i) }
ES6中的const或者let,它們都可以構(gòu)造一個塊級作用域(PS:const 定義常量,無法被修改)
for(var i = 1 ; i <= 5; i ++) { const j = i setTimeout(function timer () { console.log(j) }, j * 1000) }
我們可以用let稍微改進一下(為什么在for循環(huán)中使用let,不用const,上面已經(jīng)說得很清楚了)
for(let i = 1 ; i <= 5; i ++) { setTimeout(function timer () { console.log(i) }, i * 1000) }
不知道你怎么想,反正塊級作用域與閉包的使用,讓我成為了一只快樂的JavaScript程序員
模塊這是閉包運用得最廣的地方了吧
看看下面的代碼
function Module(){ const something = "Do A" const another = "Do B" function doA(){ console.log(something) } function doB(){ console.log(another) } return { doA, doB } } const foo = Module() foo.doA() foo.doB()
這種模式,在JavaScript中被稱為模塊,其中包含的閉包,相信大家一眼就看出來了吧。
Module()中的 doA() 與 doB() 都包含了對Module()的閉包
那么模塊模式需要具備的條件是:
必須有外部的封閉函數(shù),且至少被調(diào)用一次(每次調(diào)用都會產(chǎn)生一個新的模塊)
封閉函數(shù)必須返回至少一個內(nèi)部函數(shù),形成閉包,并且可以修改和訪問私有狀態(tài)。
由于調(diào)用一次就會產(chǎn)生一個模塊,那么是否有單例模式呢?
const foo = (function Module(another){ const something = "Do A" function doA(){ console.log(something) } function doB(another){ console.log(another) } return { doA, doB } })() foo.doA() foo.doB("Do B")
通過IIFE,立即調(diào)用這個模塊,只暴露foo,那么這個模塊只有foo這一個實例。
現(xiàn)在的模塊機制// bar.js function hello(who) { return `hello ${who}` } export hello
// foo.js // 僅導入hello() import hello from "bar" const name = "jack" function awesome () { console.log(hello(name)) } export awesome
// baz.js // 導入完整模塊 module foo from "foo" module bar from "bar" console.log(bar.hello("john")) foo.awesome()
這里模塊文件中的內(nèi)容同樣被當做好像包含在作用域中的閉包一樣處理
小結(jié)閉包就好像是JavaScript中,充滿神奇色彩的一部分,但是當我們揭開她的面紗,才發(fā)現(xiàn)她竟然這么美,她一直陪在你身邊,但是你卻一直逃避她,這次我不想你再錯過她了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/95120.html
摘要:詞法作用域的查找規(guī)則是閉包的一部分。因此的確同閉包息息相關(guān),即使本身并不會真的使用閉包。而上面的創(chuàng)建一個閉包,本質(zhì)上這是將一個塊轉(zhuǎn)換成一個可以被關(guān)閉的作用域。結(jié)合塊級作用域與閉包模塊這個模式在中被稱為模塊。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具有許多復雜微妙技術(shù)的語言,即使是經(jīng)驗豐富的 Jav...
摘要:吐槽一下,閉包這個詞的翻譯真是有很大的誤解性啊要說閉包,要先說下詞法作用域。閉包兩個作用通過閉包,在外部環(huán)境訪問內(nèi)部環(huán)境的變量。閉包使得函數(shù)可以繼續(xù)訪問定義時的詞法作用域。 閉包是真的讓人頭暈啊,看了很久還是覺得很模糊。只能把目前自己的一些理解先寫下來,這其中必定包含著一些錯誤,待日后有更深刻的理解時再作更改。 吐槽一下,閉包這個詞的翻譯真是有很大的誤解性啊…… 要說閉包,要先說下詞法...
摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數(shù)作用域早期盛行的立即執(zhí)行函數(shù)就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經(jīng)典面試題下面的代碼輸出內(nèi)容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設(shè)計》讀書筆記系列的升華版本,旨在將零碎...
摘要:理解作用域在引擎看來是兩個完全不同的聲明。在循環(huán)中使用閉包閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。回到我們上面說的在自己定義的作用域以外的地方執(zhí)行,這里聲明的是全局變量,使用全局變量不構(gòu)成閉包。 第一章:作用域是什么 程序中變量存儲在哪里,需要是怎么找到它,這就需要設(shè)計一套存儲以及能方便的找到它的規(guī)則,這個規(guī)則就是作用域 編譯原理 JavaScript 是一門編譯語言,它與傳統(tǒng)編譯語言...
摘要:最近剛剛看完了你不知道的上卷,對有了更進一步的了解。你不知道的上卷由兩部分組成,第一部分是作用域和閉包,第二部分是和對象原型。附錄詞法這一章并沒有說明機制,只是介紹了中的箭頭函數(shù)引入的行為詞法。第章混合對象類類理論類的機制類的繼承混入。 最近剛剛看完了《你不知道的 JavaScript》上卷,對 JavaScript 有了更進一步的了解。 《你不知道的 JavaScript》上卷由兩部...
閱讀 1669·2021-11-19 09:40
閱讀 2926·2021-09-24 10:27
閱讀 3215·2021-09-02 15:15
閱讀 1876·2019-08-30 15:54
閱讀 1202·2019-08-30 15:54
閱讀 1369·2019-08-30 13:12
閱讀 626·2019-08-28 18:05
閱讀 2794·2019-08-27 10:53