摘要:作用域有兩種主要工作模型詞法作用域和動態作用域。可能會有一些同學認為是,那就是沒有搞清楚詞法作用域的概念。在嚴格模式下,在運行時有自己的詞法作用域,意味著其中的聲明無法修改所在的作用域。
1. 兩種作用域
“作用域”我們知道是一套規則,用來管理引擎如何在當前作用域以及嵌套的子作用域中根據標識符名稱進行變量查找。
作用域有兩種主要工作模型:詞法作用域和動態作用域。
大多數語言采用的都是詞法作用域,少數語言采用動態作用域(例如 Bash 腳本),這里我們主要討論詞法作用域。
2. 詞法大部分標準語言編譯器的第一個工作階段叫作詞法化。
簡單地說,詞法作用域是由你在寫代碼時將變量和函數(塊)作用域寫在哪里來決定的。當然,也會有一些方法來動態修改作用域,后邊我會介紹。
舉個例子:
var a = 2; function foo1 () { console.log(a); } function foo2 () { var a = 10; foo1(); } foo2();
這里輸出結果是多少呢?
注意,這里結果打印的是 2。
可能會有一些同學認為是 10,那就是沒有搞清楚詞法作用域的概念。
前邊介紹了,詞法作用域只取決于代碼書寫時的位置,那么在這個例子中,函數 foo1 定義時的位置決定了它的作用域,通過下圖理解:
foo1 和 foo2 都是分別定義在全局作用域中的函數,它們是并列的,所以在 foo1 的作用域鏈中并不包含 foo2 的作用域,雖然在 foo2 中調用了 foo1,但是 foo1 對變量 a 進行 RHS 查詢時,在自己的作用域沒有找到,引擎會去 foo1 的上級作用域(也就是全局作用域)中查找,而并不會去 foo2 的作用域中查找,最終在全局作用域中找到 a 的值為 2。
總結來說,無論函數在哪里被調用,也無論它如何被調用,它的詞法作用域都只由函數被聲明時所處的位置決定。
3. 欺騙詞法JavaScript 中有 3 種方式可以用來“欺騙詞法”,動態改變作用域。
第一種: eval
JavaScript 中 eval(...) 函數可以接受一個字符串作為參數,并將其中的內容視為好像在書寫時就存在于程序中這個位置的代碼。
在執行 eval(...) 之后的代碼時,引擎并不知道或在意前面的代碼是以動態形式插入進來并對詞法作用域環境進行修改的,引擎只會像往常一樣正常進行詞法作用域的查找。
舉個例子:
function foo (str) { eval(str); // "欺騙"詞法 console.log(a); } var a = 2; foo("var a = 10;");
如大家所想,輸出結果為 10。
因為 eval("var a = 10;") 在 foo 的作用域中新創建了一個同名變量 a,引擎在 foo 作用域中對 a 進行 RHS 查詢,找到了新定義的 a,值為 10,所以不再向上查找全局作用域中的 a,所以導致輸出結果為 10,這就是 eval(...) 的作用。
在嚴格模式下,eval(...) 在運行時有自己的詞法作用域,意味著其中的聲明無法修改所在的作用域。
"use strict;" function foo (str) { eval(str); // eval() 有自己的作用域,所以并不會修改 foo 的詞法作用域 console.log(a); } var a = 2; foo("var a = 10;");
這里輸出結果為 2。
JavaScript 中還有一些功能和 eval(...) 類似的函數,例如 setTimeout(...) 和 setInterval(...) 的第一個參數可以是一個字符串,字符串的內容可以解釋為一段動態生成的代碼。這些功能已經過時并且不被提倡,最好不要使用它們。new Function(...) 函數的最后一個參數也可以接受代碼字符串,并將其轉化為動態生成的函數,也盡量避免使用。
在程序中動態生成代碼的使用場景非常罕見,因為它所帶來的好處無法抵消性能上的損失。
第二種: with
with 通常被當做重復引用同一個對象中的多個屬性的快捷方式,可以不需要重復引用對象本身。
舉個例子:
var obj = { a: 2, b: 3 }; with (obj) { console.log(a); // 2 console.log(b); // 3 c = 4; }; console.log(c); // 4, c 被泄露到全局作用域上
如上所示,我們對 c 進行 LHS 查詢,因為在 with 引入的新作用域中沒有找到 c,所以向上一級作用域(這里是全局作用域)查找,也沒有找到,在非嚴格模式下,在全局對象中新建了一個屬性 c 并賦值為 4。
with 可以將一個沒有或有多個屬性的對象處理為一個完全隔離的詞法作用域,因此這個對象的屬性也會被處理為定義在這個作用域中的詞法標識符。
盡管 with 塊可以將一個對象處理為詞法作用域,但是這個塊內部正常的 var 聲明并不會限制在這個塊作用域中,而是被添加到 with 所處的函數作用域中。
嚴格模式下,with 被完全禁止使用。
"use strict"; var obj = { a: 2, b: 3 }; with (obj) { console.log(a); console.log(b); c = 4; }; console.log(c);
第三種: try...catch
try...catch 可以測試代碼中的錯誤。try 部分包含需要運行的代碼,而 catch 部分包含錯誤發生時運行的代碼。
舉個例子:
try { foo(); } catch (err) { console.log(err); var a = 2; // 打印出 "ReferenceError: foo is not defined at:2:4" } console.log(a); // 2
當 try 中的代碼出現錯誤時,就會進入 catch 塊,此時會把異常對象添加到作用域鏈的最前端,類似于 with 一樣,catch 中定義的局部變量也都會添加到包含 try...catch 的函數作用域(或全局作用域)中。
4. 性能JavaScript 引擎會在編譯階段進行數項性能優化。其中有些優化依賴于能夠根據代碼的詞法進行靜態分析,并預先確定所有變量和函數定義的位置,才能在執行過程中快速找到標識符。
但如果引擎在代碼中發現了 eval(...)、with 和 try...catch ,它只能簡單的假設關于標識符位置的判斷都是無效的,因為無法在詞法分析階段明確知道 eval(...) 會接受到什么代碼,這些代碼會如何對作用域進行修改,也無法知道傳遞給 with 用來創建新詞法作用域的對象的內容到底是什么。
最悲觀的情況是如果出現了這些動態添加作用域的代碼,所有的優化可能都是無意義的,因此最簡單的做法就是完全不進行任何優化。
如果代碼中大量使用 eval(...) 和 with,那么運行起來一定會變得非常緩慢。
5. 結論很多時候我們對代碼的分析出錯,就是源于對詞法作用域的忽略,所以讓我們重新審視代碼,繼續努力!
歡迎關注我的公眾號文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81599.html
摘要:權威指南第六版關于閉包的說明采用詞法作用域,也就是說函數的執行依賴于變量的作用域,這個作用域是在函數定義時決定的,而不是函數調用時決定的。閉包這個術語的來源指函數變量可以被隱藏于作用域鏈之內,因此看起來是函數將變量包裹了起來。 最近打算換工作,所以參加了幾次面試(國內比較知名的幾家互聯網公司)。在面試的過程中每當被問起閉包,我都會說閉包是作用域的問題?令人驚訝的是幾乎無一例外的當我提到...
摘要:內部的稱為內部函數或閉包函數。過度使用閉包會導致性能下降。,閉包函數分為定義時,和運行時。循環會先運行完畢,此時,閉包函數并沒有運行。閉包只能取得外部函數中的最后一個值。事件綁定種的匿名函數也是閉包函數。而對象中的閉包函數,指向。 閉包概念解釋: 閉包(也叫詞法閉包或者函數閉包)。 在一個函數parent內聲明另一個函數child,形成了嵌套。函數child使用了函數parent的參數...
摘要:的抽象語法樹中可能如下圖所示代碼生成將轉換為可執行代碼的過程被稱為代碼生成。如果是,編譯器會忽略該聲明,繼續進行編譯,否則它會要求在當前作用域的集合中聲明一個新的變量,并命名為。 在學習 javascript 的過程中,我們第一步最應該了解和掌握的就是作用域,與之相關還有程序是怎么編譯的,變量是怎么查找的,js 引擎是什么,引擎和作用域的關系又是什么,這些是 javascript 這門...
摘要:此時,中定義的局部變量就被保存在內存中。所以當執行的時候,其真正的作用域是運行時的作用域運行時作用域詞法作用域所以第一次調用時,由于是,所以返回而第二次返回是。因此在使用閉包時,需要非常注意內存泄漏的問題。 說起閉包,相信寫前端的同學都知道,而且相信在實際的項目中或多或少都已經用到了閉包。那到底什么才是閉包,閉包又是怎么產生的呢? 1. 什么是閉包在阮老師的文章中提到: 閉包就是能夠讀...
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。即使這個時間周期內,小明取得多次滿分。創建作用域鏈在執行期上下文的創建階段,作用域鏈是在變量對象之后創建的。這種一層一層的關系,就是作用域鏈。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答...
閱讀 2892·2019-08-30 15:55
閱讀 1994·2019-08-30 14:02
閱讀 1232·2019-08-29 15:23
閱讀 1001·2019-08-29 11:27
閱讀 457·2019-08-26 11:43
閱讀 3184·2019-08-26 10:32
閱讀 1249·2019-08-23 14:41
閱讀 3296·2019-08-23 14:41