摘要:詞法作用域的查找規(guī)則是閉包的一部分。因此的確同閉包息息相關(guān),即使本身并不會(huì)真的使用閉包。而上面的創(chuàng)建一個(gè)閉包,本質(zhì)上這是將一個(gè)塊轉(zhuǎn)換成一個(gè)可以被關(guān)閉的作用域。結(jié)合塊級(jí)作用域與閉包模塊這個(gè)模式在中被稱為模塊。
你不知道的JS(上卷)筆記
你不知道的 JavaScript
JavaScript 既是一門充滿吸引力、簡(jiǎn)單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗(yàn)豐富的 JavaScript 開發(fā)者,如果沒有認(rèn)真學(xué)習(xí)的話也無法真正理解它們.
上卷包括倆節(jié):
作用域和閉包
this 和對(duì)象原型
作用域和閉包希望 Kyle 對(duì) JavaScript 工作原理每一個(gè)細(xì)節(jié)的批判性思 考會(huì)滲透到你的思考過程和日常工作中。知其然,也要知其所以然。
作用域閉包 啟示秘訣: JavaScript中閉包無處不在,你只需要能夠識(shí)別并擁抱它。
閉包是基于詞法作用域書寫代碼時(shí)所產(chǎn)生的自然結(jié)果,你甚至不需要為了利用它們而有意識(shí)的創(chuàng)建閉包。
閉包的創(chuàng)建和使用在你的代碼中隨處可見。
你缺少的是根據(jù)你自己的意愿來識(shí)別、擁抱和影響閉包的思維環(huán)境。
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
詞法作用域的查找規(guī)則是閉包的一部分。
function foo() { var a = 2; function bar() { console.log( a ); // 2 } bar(); } foo();
純學(xué)術(shù)的角度上說,上述代碼片段中,函數(shù) bar() 具有一個(gè)涵蓋 foo() 作用域的閉包 (事實(shí)上,涵蓋了它能訪問的所有作用域,比如全局作用域)。也可以認(rèn)為 bar() 被封閉在了 foo() 的作用域中。為什么呢?原因簡(jiǎn)單明了,因?yàn)?bar() 嵌套在 foo() 內(nèi)部。
閉包使得函數(shù)可以繼續(xù)訪問定義時(shí)的詞法作用域。
無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會(huì)持有對(duì)原始定義作用 域的引用,無論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。
思考:
function foo() { var a = 2; function baz() { console.log( a, b ); // 2 , b能獲取到1嗎? } bar( baz ); } function bar(fn) { var b = 1; fn(); // 媽媽快看呀,這就是閉包! }常見的閉包場(chǎng)景
function wait(message) { setTimeout( function timer() { // timer函數(shù)由引擎調(diào)用,但是已經(jīng)超出了wait作用域,所以存在閉包 console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
在定時(shí)器、事件監(jiān)聽器、 Ajax 請(qǐng)求、跨窗口通信、Web Workers 或者任何其他的異步(或者同步)任務(wù)中,只要使 用了回調(diào)函數(shù),實(shí)際上就是在使用閉包!
var a = 2; (function IIFE() { console.log( a ); })();
盡管 IIFE 本身并不是觀察閉包的恰當(dāng)例子,但它的確創(chuàng)建了閉包,并且也是最常用來創(chuàng)建 可以被封閉起來的閉包的工具。因此 IIFE 的確同閉包息息相關(guān),即使本身并不會(huì)真的使用 閉包。
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
延遲函數(shù)的回調(diào)會(huì)在循環(huán)結(jié)束時(shí)才執(zhí)行。事實(shí)上, 當(dāng)定時(shí)器運(yùn)行時(shí)即使每個(gè)迭代中執(zhí)行的是setTimeout(.., 0),所有的回調(diào)函數(shù)依然是在循 環(huán)結(jié)束后才會(huì)被執(zhí)行,因此會(huì)每次輸出一個(gè) 6 出來。
缺陷是我們?cè)噲D假設(shè)循環(huán)中的每個(gè)迭代在運(yùn)行時(shí)都會(huì)給自己“捕獲”一個(gè) i 的副本。但是 根據(jù)作用域的工作原理,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的, 但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè) i。
這樣說的話,當(dāng)然所有函數(shù)共享一個(gè) i 的引用。
它需要有自己的變量,用來在每個(gè)迭代中儲(chǔ)存 i 的值:
for (var i=1; i<=5; i++) { (function() { // IIFE 每次執(zhí)行都會(huì)立即創(chuàng)建一個(gè)詞法上的函數(shù)作用域 var j = i; // 閉包作用域的變量j, 立即得到i的值 setTimeout( function timer() { console.log( j ); // 訪問閉包作用域的變量j }, j*1000 ); })(); }
變體:
for (var i=1; i<=5; i++) { (function(j) { // IIFE 每次執(zhí)行都會(huì)立即創(chuàng)建一個(gè)詞法上的函數(shù)作用域 // 閉包作用域的變量j, 參數(shù)傳遞立即得到i的值 setTimeout( function timer() { console.log( j ); // 訪問閉包作用域的變量j }, j*1000 ); })(i); }
let 聲明,可以用來劫 持塊作用域,并且在這個(gè)塊作用域中聲明一個(gè)變量。
而上面的IIFE創(chuàng)建一個(gè)閉包,本質(zhì)上這是將一個(gè)塊轉(zhuǎn)換成一個(gè)可以被關(guān)閉的作用域。
結(jié)合塊級(jí)作用域與閉包:
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }模塊
function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething1: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething1(); // cool foo.doAnother(); // 1 ! 2 ! 3
這個(gè)模式在 JavaScript 中被稱為模塊。最常見的實(shí)現(xiàn)模塊模式的方法通常被稱為模塊暴露, 這里展示的是其變體。
doSomething() 和 doAnother() 函數(shù)具有涵蓋模塊實(shí)例內(nèi)部作用域的閉包(通過調(diào)用 CoolModule() 實(shí)現(xiàn))。
當(dāng)通過返回一個(gè)含有屬性引用的對(duì)象的方式來將函數(shù)傳遞到詞法作 用域外部時(shí),我們已經(jīng)創(chuàng)造了可以觀察和實(shí)踐閉包的條件。
模塊模式需要具備兩個(gè)必要條件。
必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊 實(shí)例)。
封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并 且可以訪問或者修改私有的狀態(tài)。
一個(gè)具有函數(shù)屬性的對(duì)象本身并不是真正的模塊。從方便觀察的角度看,一個(gè)從函數(shù)調(diào)用 所返回的,只有數(shù)據(jù)屬性而沒有閉包函數(shù)的對(duì)象并不是真正的模塊。
模塊模式另一個(gè)簡(jiǎn)單但強(qiáng)大的變化用法是,命名將要作為公共 API 返回的對(duì)象:
在上述模塊中,dosomething1被作為模塊內(nèi)部dosomething的公開訪問名。
var MyModules = (function Manager() { // 模塊 管理器/依賴加載器 var modules = {}; function define(name, deps, impl) { for (var i=0; i未來的模塊機(jī)制 基于函數(shù)的模塊并不是一個(gè)能被穩(wěn)定識(shí)別的模式(編譯器無法識(shí)別),它們 的 API 語義只有在運(yùn)行時(shí)才會(huì)被考慮進(jìn)來。因此可以在運(yùn)行時(shí)修改一個(gè)模塊 的 API。
相比之下,ES6 模塊 API 更加穩(wěn)定(API 不會(huì)在運(yùn)行時(shí)改變)。由于編輯器知 道這一點(diǎn),因此可以在(的確也這樣做了)編譯期檢查對(duì)導(dǎo)入模塊的 API 成 員的引用是否真實(shí)存在。如果 API 引用并不存在,編譯器會(huì)在運(yùn)行時(shí)拋出一 個(gè)或多個(gè)“早期”錯(cuò)誤,而不會(huì)像往常一樣在運(yùn)行期采用動(dòng)態(tài)的解決方案。ES6 的模塊沒有“行內(nèi)”格式,必須被定義在獨(dú)立的文件中(一個(gè)文件一個(gè)模塊)。瀏覽 器或引擎有一個(gè)默認(rèn)的“模塊加載器”(可以被重載,但這遠(yuǎn)超出了我們的討論范圍)可 以在導(dǎo)入模塊時(shí)異步地加載模塊文件。
小結(jié)閉包就好像從 JavaScript 中分離出來的一個(gè)充滿神秘色彩的未開化世界,只有最勇敢的人 才能夠到達(dá)那里。但實(shí)際上它只是一個(gè)標(biāo)準(zhǔn),顯然就是關(guān)于如何在函數(shù)作為值按需傳遞的 詞法環(huán)境中書寫代碼的。
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這時(shí) 就產(chǎn)生了閉包。
如果沒能認(rèn)出閉包,也不了解它的工作原理,在使用它的過程中就很容易犯錯(cuò),比如在循 環(huán)中。但同時(shí)閉包也是一個(gè)非常強(qiáng)大的工具,可以用多種形式來實(shí)現(xiàn)模塊等模式。
模塊有兩個(gè)主要特征:(1)為創(chuàng)建內(nèi)部作用域而調(diào)用了一個(gè)包裝函數(shù);
(2)包裝函數(shù)的返回 值必須至少包括一個(gè)對(duì)內(nèi)部函數(shù)的引用,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉 包。現(xiàn)在我們會(huì)發(fā)現(xiàn)代碼中到處都有閉包存在,并且我們能夠識(shí)別閉包然后用它來做一些有用 的事!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101541.html
摘要:如果提升改變了代碼執(zhí)行的順序,會(huì)造成非常嚴(yán)重的破壞。聲明本身會(huì)被提升,而包括函數(shù)表達(dá)式的賦值在內(nèi)的賦值操作并不會(huì)提升。要注意避免重復(fù)聲明,特別是當(dāng)普通的聲明和函數(shù)聲明混合在一起的時(shí)候,否則會(huì)引起很多危險(xiǎn)的問題 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡(jiǎn)單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗(yàn)豐富的 Ja...
摘要:如果是聲明中的第一個(gè)詞,那么就是一個(gè)函數(shù)聲明,否則就是一個(gè)函數(shù)表達(dá)式。給函數(shù)表達(dá)式指定一個(gè)函數(shù)名可以有效的解決以上問題。始終給函數(shù)表達(dá)式命名是一個(gè)最佳實(shí)踐。也有開發(fā)者干脆關(guān)閉了靜態(tài)檢查工具對(duì)重復(fù)變量名的檢查。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡(jiǎn)單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗(yàn)豐富的 Ja...
摘要:詞法作用域定義在詞法階段的作用域由你在寫代碼時(shí)將變量和塊作用域?qū)懺谀膩頉Q定的,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變。欺騙詞法作用域在詞法分析器處理過后依然可以修改作用域。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡(jiǎn)單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗(yàn)豐富的 JavaScript 開發(fā)者,如果沒...
摘要:的抽象語法樹中可能會(huì)有一個(gè)叫作的頂級(jí)節(jié)點(diǎn),接下來是一個(gè)叫作它的值是的子節(jié)點(diǎn),以及一個(gè)叫作的子節(jié)點(diǎn)。值得注意的是,是非常重要的異常類型。嚴(yán)格模式下,未聲明的和倆者行為相同,都會(huì)是。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡(jiǎn)單易用的語言,又是一門具有許多復(fù)雜微妙技術(shù)的語言,即使是經(jīng)驗(yàn)豐富的 JavaScript 開發(fā)者,如果...
閱讀 2139·2019-08-29 16:53
閱讀 2712·2019-08-29 16:07
閱讀 2057·2019-08-29 13:13
閱讀 3278·2019-08-26 13:57
閱讀 1344·2019-08-26 13:31
閱讀 2447·2019-08-26 13:22
閱讀 1234·2019-08-26 11:43
閱讀 2095·2019-08-23 17:14