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