摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數作用域早期盛行的立即執行函數就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經典面試題下面的代碼輸出內容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記
下一篇:《你不知道的javascript》筆記_this
寫在前面這一系列的筆記是在《javascript高級程序設計》讀書筆記系列的升華版本,旨在將零碎未知的知識總結
一、基本概念 1.1 編譯在傳統編譯語言的流程中,程序中的一段源代碼在執行之前會經歷三個步驟,統稱為編譯:
1. 分詞/詞法分析(Tokenizing/Lexing)
這個過程會將由字符組成的字符串分解成(對編程語言來說)有意義的代碼塊,這些代碼塊被稱為詞法單元(token)。例如,考慮程序var a = 2;。這段程序通常會被分解成為下面這些詞法單元:var、a、=、2 、;。空格是否會被當作詞法單元,取決于空格在這門語言中是否具有意義
2. 解析/語法分析(Parsing)
這個過程是將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的代表了程序語法結構的樹。這個樹被稱為抽象語法樹(Abstract Syntax Tree,AST)
3. 代碼生成
將 AST 轉換為可執行代碼的過程稱被稱為代碼生成。拋開具體細節,簡單來說就是有某種方法可以將var a = 2;的 AST 轉化為一組機器指令,用來創建一個叫作 a 的變量(包括分配內存等),并將一個值儲存在 a 中
相對于上面的流程,javascript在語法分析和代碼生成階段有特定的步驟來對運行性能進行優化,包括對冗余元素進行優化等。
1.2 組成引擎:從頭到尾負責整個JavaScript程序的編譯及執行過程1.3 查詢&異常編譯器:負責語法分析及代碼生成等臟活累活
作用域:負責收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限
RHS 查詢:當變量出現在賦值操作的右側時進行 RHS 查詢
LHS 查詢:當變量出現在賦值操作的左側時進行 LHS 查詢(賦值、傳參、函數執行)
RHS查詢異常:RHS 查詢在所有嵌套的作用域中遍尋不到所需的變量,引擎就會拋出 ReferenceError 異常
LHS查詢異常:非嚴格模式下,LHS 查詢失敗會在全局創建變量;在嚴格模式中 LHS 查詢失敗時,并不會創建并返回一個全局變量,引擎會拋出同 RHS 查詢失敗時類似的 ReferenceError 異常
javascript引擎執行代碼var a = 2;的過程?
編譯階段:var a;,如果作用域內已存在變量 a,則忽略;若不存在,則在該作用域內聲明
執行階段:a = 2;,對 a 進行 LHS 引用,并對其賦值
負責收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限
通俗的說,作用域是維護變量并確定訪問權限的一套規則
2.1 詞法作用域詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的,因此當詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)
下面有個簡單的作用域嵌套的例子:
【1】包含著整個全局作用域,其中只有一個標識符: foo。
【2】包含著 foo 所創建的作用域,其中有三個標識符: a、bar 和 b,可訪問全局作用域變量。
【3】包含著 bar 所創建的作用域,其中只有一個標識符: c,可訪問foo和全局作用域變量。
另外有兩個比較特殊的欺騙詞法機制:
eval(..) 函數
with 關鍵字
這兩個機制的副作用是引擎無法在編譯時對作用域查找進行優化,因為引擎只能謹慎地認為這樣的優化是無效的。使用這其中任何一個機制都將導致代碼運行變慢。不要使用它們。2.2 作用域查找規則
書中對作用域鏈和作用域查找做了一個非常形象的比喻,如下圖
這個建筑代表程序中的嵌套作用域鏈。第一層樓代表當前的執行作用域,也就是你所處的位置。建筑的頂層代表全局作用域。
LHS 和 RHS 引用都會在當前樓層進行查找,如果沒有找到,就會坐電梯前往上一層樓, 如果還是沒有找到就繼續向上,以此類推。一旦抵達頂層(全局作用域),可能找到了你所需的變量,也可能沒找到,但無論如何查找過程都將停止
作用域查找會在找到第一個匹配的標識符時停止
2.2 塊級作用域早期的javascript語句中塊級作用域就是函數塊,這是在讀本書之前我粗淺的認識。實際的塊級作用域遠不止如此
塊級作用域:
(1)函數作用域
早期盛行的立即執行函數(IIFE)就是為了形成塊級作用域,不污染全局。常用的寫法有:
(function(形參){函數體})(實參) (function(形參){函數體}(實參)) !function(形參){函數體}(實參)
(2) with關鍵字
(3) try/catch語句
Google 維護著一個名為 Traceur 的項目,該項目正是用來將 ES6 代碼轉換成兼容 ES6 之前 的環境(大部分是 ES5,但不是全部),下面是用來兼容低版本創建塊級作用域的寫法:
{ try { throw undefined; } catch (a) { a = 2; console.log( a ); } }
(4) let/const關鍵字
三、變量提升在之前的兩篇文章中對變量提升(預解析)有比較充分的說明:
《javascript高級程序設計》筆記:變量對象與預解析
《javascript高級程序設計》筆記:內存與執行環境
本書中定義:當函數可以記住并訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。MDN定義:閉包是函數和聲明該函數的詞法環境的組合
個人理解:當外部能夠訪問到某個函數的私有變量時,就會產生閉包(不嚴謹,僅用于理解)
兩個經典的閉包例子:
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc(); // "Mozilla"
思考:myFunc是執行makeFunc時創建的displayName函數實例的引用,為什么執行myFunc時會打印出makeFunc中私有變量name呢?
解釋:閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量
function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
分析:按照閉包能暫存變量的思路,執行makeAdder時,會把參數暫存在所return的函數中,當再次執行函數時,會把兩次的參數之和輸出
4.2 應用閉包在js編程中隨處可見,書中有這樣一個結論:
在定時器、事件監聽器、 Ajax 請求、跨窗口通信、Web Workers 或者任何其他的異步(或者同步)任務中,只要使用了回調函數,實際上就是在使用閉包!
定時器閉包案例:
function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
事件監聽閉包案例:
function setupBot(name, selector) { $(selector).click( function activator() { console.log( "Activating: " + name ); }); } setupBot( "Closure Bot 1", "#bot_1" ); setupBot( "Closure Bot 2", "#bot_2" );
上面的案例中,有個相同的特點:先定義函數,后執行函數時能夠調用到函數中的私有變量或者實參。這便是閉包的特點吧
4.3 經典面試題(1)下面的代碼輸出內容?
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
答案:5個6
(2)如何處理能夠輸出1~5
// 閉包方式 for (var i=1; i<=5; i++) { (function(index) { setTimeout( function timer() { console.log( index ); }, index*1000 ); })(i) } // ES6 方式 for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
下一篇:《你不知道的javascript》筆記_this
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100220.html
摘要:的分句會創建一個塊作用域,其聲明的變量僅在中有效。而閉包的神奇作用是阻止此事發生。依然持有對該作用域的引用,而這個引用就叫做閉包。當然,無論使用何種方式對函數類型的值進行傳遞,當函數在別處被調用時都可以觀察到閉包。 date: 16.12.8 Thursday 第一章 作用域是什么 LHS:賦值操作的目標是誰? 比如: a = 2; RHS:誰是賦值操作的源頭? 比如: conso...
下一篇:《你不知道的javascript》筆記_對象&原型 寫在前面 上一篇博客我們知道詞法作用域是由變量書寫的位置決定的,那this又是在哪里確定的呢?如何能夠精準的判斷this的指向?這篇博客會逐條闡述 書中有這樣幾句話: this是在運行時進行綁定的,并不是在編寫時綁定,它的上下文取決于函數調用時的各種條件this的綁定和函數聲明的位置沒有任何關系,只取決于函數的調用方式當一個函數被調用時...
摘要:而閉包的神奇之處正是可以阻止事情的發生。拜所聲明的位置所賜,它擁有涵蓋內部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時間進行引用。依然持有對該作用域的引用,而這個引用就叫閉包。 引子 先看一個問題,下面兩個代碼片段會輸出什么? // Snippet 1 a = 2; var a; console.log(a); // Snippet 2 console.log(a); v...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內部,也就是說為其聲明的變量隱式的劫持了所在的塊級作用域。 作用域與閉包 如何用js創建10個button標簽,點擊每個按鈕時打印按鈕對應的序號? 看到上述問題,如果你能看出來這個問題實質上是考對作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對作用域已經理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
閱讀 900·2021-11-22 12:04
閱讀 2102·2021-11-02 14:46
閱讀 624·2021-08-30 09:44
閱讀 2107·2019-08-30 15:54
閱讀 728·2019-08-29 13:48
閱讀 1597·2019-08-29 12:56
閱讀 3451·2019-08-28 17:51
閱讀 3287·2019-08-26 13:44