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