站長資訊網
        最全最豐富的資訊網站

        深入解析JavaScript中的作用域

        本篇文章帶大家深入理解JavaScript作用域。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有所幫助。

        深入解析JavaScript中的作用域

        這篇文章稱為筆記更為合適一些,內容來源于 《你不知道的JavaScript(上卷)》第一部分 作用域和閉包。講的很不錯,非常值得一看。

        什么是作用域

        作用域是根據名稱查找變量的一套規則

        理解作用域

        先來理解一些基礎概念:

        • 引擎:從頭到尾負責整個JavaScript程序的編譯及執行過程。
        • 編譯器:負責語法分析和代碼生成。這部分也可以看 JavaScript代碼是如何被執行的
        • 作用域:負責收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限。

        接下來來看看下面代碼的執行過程:

        var a = 2;
        • 遇見 var a,編譯器 會問 作用域 變量a是否存在于同一個作用域集合中。如果存在,編譯器會忽略聲明,繼續編譯;否則,會要求作用域在當前作用域集合中聲明一個新的變量,并命名為 a

        • 接下來 編譯器 會為 引擎 生成運行時所需的代碼,用來處理 a = 2 這個賦值操作。引擎運行時會先問作用域,當前作用域集中是否存在變量a。如果是,引擎就會使用該變量;如果不存在,引擎會繼續查找該變量

        • 如果 引擎 找到了a 變量,就會將 2 賦值給它,否則引擎就拋出一個錯誤。

        總結:變量的賦值操作會執行兩個動作,首先編譯器會在當前作用域中聲明一個變量,然后在運行時引擎就會會作用域中查找該變量,如果能夠找到就對它賦值。

        編譯器在編譯過程的第二步中生成了代碼,引擎執行它時,會通過查找變量 a來判斷它是否已聲明過。查找的過程中由作用域進行協助,但是引擎執行怎么樣的查找,會影響最終的查找結果。

        在我們的例子中,引擎會為變量 a 進行 LHS 查詢,另外一個查找的類型叫做 RHS。 ”L“ 和 "R" 分別代表一個賦值操作左側和右側。當變量出現在賦值操作的左側時進行 LHS 查詢,出現在右側時進行 RHS 查詢。

        LHS:試圖找到變量的容器本身,從而可以對其賦值;RHS: 就是簡單地查找某個變量的值。

        console.log(a);

        對 a 的引用是一個 RHS 引用,因為這里 a 并沒有賦予任務值,相應地需要查找并取得 a 的值,這樣才能將值傳遞給 console.log(…)

        a = 2;

        這里對 a 的引用是 LHS 引用,因為實際上我們并不關心當前的值是什么,只是想要為 = 2這個賦值操作找到目標。

        funciton foo(a) {     console.log(a) }  foo(2);
        1. 最后一行 foo 函數的調用需要對 foo 進行 RHS 引用,去找 foo的值,并把它給我
        2. 代碼中隱式的 a = 2 操作可能很容易被你忽略掉,這操作發生在 2 被當做參數傳遞給 foo 函數時,2 會被分配給參數 a,為了給參數 a (隱式地) 分配值,需要進行一次 LHS 查詢。
        3. 這里還有對 a 進行的 RHS 引用,并且將得到的值傳給了 console.log(...)console.log(...) 本身也需要一個引用才能執行,因此會對 console對象進行 RHS 查詢,并且檢查得到的值中是否有一個叫做 log的方法。

        RHS查詢在所有嵌套的作用域中遍尋不到所需的變量,引擎就會拋出 ReferenceError 異常。進行RHS查詢找到了一個變量,但是你嘗試對這個變量的值進行不合理的操作,比如試圖對一個非函數類型的值進行調用,后者引用null或 undefined 類型的值中的屬性,那么引擎會拋出一個另外一種類型的異常 TypeError。
        引擎執行 LHS 查詢時如果找不到該變量,則會在全局作用域中創建一個。但是在嚴格模式下,并不是自動創建一個全局變量,而是會拋出 ReferenceError 異常

        補充JS幾種常見的錯誤類型

        簡單總結如下:

        作用域是一套規則,用于確定在哪里找,怎么找到某個變量。如果查找的目的是對變量進行賦值,那么就會使用 LHS查詢; 如果目的是獲取變量的值,就會使用 RHS 查詢;
        JavaScript 引擎執行代碼前會對其進行編譯,這個過程中,像 var a = 2 這樣的聲明會被分解成兩個獨立的步驟

        • var a 在其作用域中聲明變量,這會在最開始的階段,也就是代碼執行前進行

        • 接下來,a = 2 會查詢 (LHS查詢)變量 a 并對其進行賦值。

        詞法作用域

        詞法作用域是你在寫代碼時將變量寫在哪里來決定的。編譯的詞法分析階段基本能夠知道全局標識符在哪里以及是如何聲明的,從而能夠預測在執行過程中如果對他們查找。

        有一些方法可以欺騙詞法作用域,比如 eval, with, 這兩種現在被禁止使用,1是嚴格模式和非嚴格模式下表現不同 2是有性能問題, JavaScript引擎在編譯階段會做很多性能優化,而其中很多優化手段都依賴于能夠根據代碼的詞法進行靜態分析,并預先確定所有變量和函數的定義位置,才能在執行過程中快速找到識別符,eval, with會改變作用域,所以碰到它們,引擎將無法做優化處理。

        全局作用域和函數作用域

        全局作用域

        • 在最外層函數和最外層函數外面定義的變量擁有全局作用域
        var a = 1; function foo() {  }

        變量a 和函數聲明 foo 都是在全局作用域中的。

        • 所有未定義直接賦值的變量自動聲明為擁有全局作用域

        var a = 1; function foo() {     b = 2; } foo(); console.log(b); // 2
        • 所有 window 對象的屬性擁有全局作用域

        函數作用域

        函數作用域是指在函數內聲明的所有變量在函數體內始終是可見的。外部作用域無法訪問函數內部的任何內容。

        function foo() {     var a = 1;     console.log(a); // 1 } foo(); console.log(a); // ReferenceError: a is not defined

        只有函數的{}構成作用域,對象的{}以及 if(){}都不構成作用域;

        變量提升

        提升是指聲明會被視為存在與其所出現的作用域的整個范圍內。

        JavaScript編譯階段是找到找到所有聲明,并用合適的作用域將他們關聯起來(詞法作用域核心內容),所以就是包含變量和函數在內的所有聲明都會在任何代碼被執行前首先被處理。

        每個作用域都會進行提升操作。

        function foo() {     var a;     console.log(a); // undefined     a = 2; } foo();

        注意,函數聲明會被提升,但是函數表達式不會被提升。

        關于 塊級作用域和變量提升的內容之前在 從JS底層理解var、let、const這邊文章中詳細介紹過,這里不再贅述。

        塊級作用域

        我們來看下面這段代碼

        for(var i = 0; i < 5; i++) {     setTimeout(() => {         console.log(i);     }) } console.log(`當前的i為${i}`); // 當前的i為5

        上面這段代碼我們希望是輸出 0,1, 2, 3, 4 ,但是實際上輸出的是 5,5, 5, 5, 5。我們在 for 循環的頭部直接定義了變量 i,通常是因為只想在 for 循環內部的上下文中使用 i,但是實際上 此時的 i 被綁定在外部作用域(函數或全局)中。

        ,塊級作用域是指在指定的塊級作用域外無法訪問。在ES6之前是沒有塊級作用域的概念的,ES6引入了 let 和 const。我們可以改寫上面的代碼,使它按照我們想要的方式運行。

        for(let i = 0; i < 5; i++) {     setTimeout(() => {         console.log(i);     }) } // 0 1 2 3 4 console.log(`當前的i為${i}`); // ReferenceError: i is not defined

        此時 for 循環頭部的 let 不僅將 i 綁定到了 for 循環的迭代中,事實上將它重新綁定到了循環的每一個迭代中,確保使用上一次循環迭代結束的值重新進行賦值。

        let聲明附屬于一個新的作用域而不是當前的函數作用域(也不屬于全局作用域)。但是其行為是一樣的,可以總結為:任何聲明在某個作用域內的變量,都將附屬于這個作用域。
        const也是可以用來創建塊級作用域變量,但是創建的是固定值。

        作用域鏈

        JavaScript是基于詞法作用域的語言,通過變量定義的位置就能知道變量的作用域。全局變量在程序中始終都有都定義的。局部變量在聲明它的函數體內以及其所嵌套的函數內始終是有定義的。

        每一段 JavaScript 代碼都有一個與之關聯的作用域鏈(scope chain)。這個作用域鏈是一個對象列表或者鏈表。當 JavaScript 需要查找變量 x 的時候(這個過程稱為變量解析),它會從鏈中的第一個變量開始查找,如果這個對象上依然沒有一個名為 x 的屬性,則會繼續查找鏈上的下一個對象,如果第二個對象依然沒有名為 x 的屬性,javaScript會繼續查找下一個對象,以此類推。如果作用域鏈上沒有任何一個對象包含屬性 x, 那么就認為這段代碼的作用域鏈上不存在 x, 并最終拋出一個引用錯誤 (Reference Error) 異常。

        下面作用域中有三個嵌套的作用域。

        function foo(a) {     var b = a * 2;     function bar(c) {         console.log(a, b, c)     }     bar( b * 3); } foo(2);

        1.png

        氣泡1包含著整個全局作用域,其中只有一個標識符:foo;
        氣泡2包含著foo所創建的作用域,其中有三個標識符:a、bar 和 b;
        氣泡3包含著 bar所創建的作用域,其中只有一個標識符:c

        執行 console.log(...),并查找 a,b,c三個變量的引用。下面我們來看看查找這幾個變量的過程.
        它首先從最內部的作用域,也就是 bar(..) 函數的作用域氣泡開始找,引擎在這里無法找到 a,因此就會去上一級到所嵌套的 foo(…)的作用域中繼續查找。在這里找到了a,因此就使用了這個引用。對b來說也一樣,而對 c 來說,引擎在 bar(..) 中就找到了它。

        如果 a,c都存在于 bar(…) 內部,console.log(…)就可以直接使用 bar(…) 中的變量,而無需到外面的 foo(..)中查找。作用域會在查找都第一個匹配的標識符時就停止。

        在多層的嵌套作用域中可以定義同名的標識符,這叫”遮蔽效應“。

        var a = '外部的a'; function foo() {     var a = 'foo內部的a';     console.log(a); // foo內部的a } foo();

        作用域與執行上下文

        JavaScript的執行分為:解釋和執行兩個階段

        解釋階段

        • 詞法分析
        • 語法分析
        • 作用域規則確定

        執行階段

        • 創建執行上下文
        • 執行函數代碼
        • 垃圾回收

        作用域在函數定義時就已經確定了,而不是在函數調用時確定,但執行上下文是函數執行之前創建的。

        總結

        • 作用域就是一套規則,用于確定在哪里找以及怎么找到某個變量。

        • 詞法作用域在你寫代碼的時候就確定了。JavaScript是基于詞法作用域的語言,通過變量定義的位置就能知道變量的作用域。ES6引入的let和const聲明的變量在塊級作用域中。

        • 聲明提升是指聲明會被視為存在與其所出現的作用域的整個范圍內。

        • 查找變量的時候會先從內部的作用域開始查找,如果沒找到,就往上一級進行查找,依次類推。

        • 作用域在函數定義時就已經確定了,執行上下文是函數執行之前創建的。

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 99热成人精品热久久669| 人精品影院| 国产精品成人精品久久久| 亚洲中文字幕久久精品无码喷水| 国产精品麻豆欧美日韩ww| 99精品国产在热久久| 亚洲精品国精品久久99热一| 精品久久久久久无码中文字幕| 亚洲欧美日韩精品| 国产亚洲一区二区精品| 国产综合色在线精品| 无码人妻精品一区二区三区99仓本| 精品亚洲欧美中文字幕在线看 | 久久亚洲精品成人AV| 亚洲国产精品专区在线观看| 精品国产热久久久福利| 国产成人精品一区在线| 日韩一级精品视频在线观看| 精品国产日产一区二区三区| 国产成人精品一区二区三区免费| 亚洲AV无码久久精品狠狠爱浪潮| 日韩三级精品| 久久精品亚洲乱码伦伦中文| 国产在线精品国自产拍影院| 国产国产成人久久精品| 91不卡在线精品国产| 99九九精品免费视频观看| 88久久精品无码一区二区毛片| 亚洲国产精品久久久久婷婷软件 | 午夜DY888国产精品影院| 中文字幕精品一区二区精品| 午夜精品久久久久久| 午夜成人精品福利网站在线观看| 日本精品一区二区三区在线视频一 | 色欲精品国产一区二区三区AV | 日韩精品极品视频在线观看免费| 无码人妻精品中文字幕免费 | 久久久精品2019免费观看| 日韩精品无码久久久久久 | 亚洲精品乱码久久久久久蜜桃不卡| 亚洲欧美国产∧v精品综合网 |