最近在讀凱爾辛普森的《你不知道的JavaScript》,感受挺有意思的,在理解做用域以後,看到了一個有意思的東西:欺騙詞法做用域。編程
首先來看看做用域是什麼吧。編程語言
簡而言之就是一套儲存變量並規定如何訪問並修改變量的規則,是幾乎全部編程語言最基本的功能之一。
做爲JavaScript引擎的首席檢察官,他也會被本身人給騙了。函數
原理:JavaScript中的eval(str)函數能夠接受一個字符串爲參數,並將字符串內容視爲好像在書寫時就存在於eval()函數所在位置的代碼。spa
function foo(str,a){ eval(str); //欺騙代碼 console.log(a,b); } var b = 2; foo("var b = 3;" , 1);
猜猜運行結果是什麼呢?code
是1, 3blog
乍一看,foo()中console.log(a,b)裏的b會會去外層訪問咱們定義的全局變量b,做用域也是這麼想的,因此他只是一如往常的去檢查。ip
可是在執行eval(..)以後的代碼時,引擎並不「知道」或「在乎」前面的代碼是以動態形式插入進來。作用域
eval(..)調用中的"var b = 3; "這段代碼會被看成原本就在那裏同樣來處理。因爲那段代碼聲明瞭一個新的變量b,所以它對已經存在的foo(..)的詞法做用域進行了修改。事實上,和前面提到的原理同樣,這段代碼實際上在foo(..)內部建立了一個變量b,並遮蔽了外部(全局)做用域中的同名變量。字符串
當console.log(..)被執行時,會在foo(..)的內部同時找到a和b可是永遠也沒法找到外部的b。所以會輸出「1, 3」而不是正常狀況下會輸出的「1, 2」。it
注意
嚴格模式的程序中,eval(..)在運行時有其本身的詞法做用域,意味着其中的聲明沒法修改所在的做用域。
以下: function foo(str) { "use strict"; eval(str); console.log(a);
// ReferenceError: a is not defined } foo("var a = 2");`
其中eval(str);運行時沒法修改所在的做用域了,此時a便沒法找到。
JavaScript中還有其餘一些功能效果和eval(..)很類似。setTimeout(..)和setInterval(..)的第一個參數能夠是字符串,字符串的內容能夠被解釋爲一段動態生成的函數代碼。這些功能已通過時且並不被提倡。不要使用它們!
詞法做用域的"欺騙",eval()只是其中一個,更多詳情推薦看看凱爾辛普森的《你不知道的JavaScript》,他會帶你深刻的瞭解JavaScript。