以前在祼看ECMA262-5,在說到eval
的地方,死活看不明白爲何會有一節專門扯到Direct Call to Eval
:javascript
A direct call to the eval function is one that is expressed as a CallExpression that meets the following two conditions:html
The Reference that is the result of evaluating the MemberExpression in the CallExpression has an environment record as its base value and its reference name is "eval".java
The result of calling the abstract operation GetValue with that Reference as the argument is the standard built-in function defined in 15.1.2.1.express
當時以爲寫規範的那羣傢伙必定是有毛病,衆所周知,eval無非是接收一個字符串,把這個字符串當作代碼解釋執行。瀏覽器
這個問題我一直不明白,而後就放着。閉包
直到有一天.....函數
某一天,小夥伴們討論到說關於閉包變量的問題時,Y君指出,若是一個函數沒有引用到其所處閉包的a
變量,那這個a
變量所指向的空間將被釋放。工具
function getClosure() { var a = 1; var b = 2; return function inner() { debugger; console.log(b); } } var func = getClosure(); func();
正如上面代碼所示,當打開Chrome調試工具時到達debugger
語句時,咱們只能訪問到b
變量。試圖訪問a
是會拋出ReferenceError的。性能
可是這真的能證實a
引用已經被釋放了嗎?若是沒被釋放的話,還不讓在調試的時候訪問,這個設計就只能說是(嗶~)坑爹了,Y君補充道。測試
因而我寫了以下代碼作了測試:
function getClosure() { var a = 1; var b = 2; return function inner(val) { debugger; console.log(eval(val)); } } var func = getClosure(); func('a'); func('b');
在閉包中使用eval
,這樣引擎就不知道我在閉包中的會引用到什麼變量了。這一次到達debugger
語句時,驚奇地發現,a
和b
均可以直接引用到了。難道是由於eval
的緣由?引擎在發現閉包中有eval
以後,就不會回收閉包中的垃圾了?(由於它無從得知哪些變量會在未來被引用到)。
但若是引擎不知道未來會不會執行到eval呢?
function getClosure(){ var a = 1; var b = 2; return function inner(func, val) { debugger; console.log(func(val)); } } var f = getClosure(); f(eval, a);
把eval
當作函數傳進去,而後讓這個eval函數解釋執行傳入的val指向的變量。遺憾的是,到在debugger處,沒法訪問到a和b,執行到console.log(func(val))
拋了異常,表示找不到引用。
因而我纔回過神,彷佛以前閱讀過蛇精病般的什麼direct call to eval
的東西,跟這個相關?
在規範中指出,進調用eval函數時:
this指向
,詞法環境
以及變量環境
當作新的執行上下文的this指向
,詞法環境
以及變量環境
。global對象
,全局詞法環境
以及全局變量環境
當作新的執行上下文的this指向
,詞法環境
以及變量環境
。那什麼是直接調用呢?參照篇首。簡言之,有以下限制:
a. BaseValue必須是一個Environment Record
b. Reference Name必須是"eval"
以下例子:
var a = eval; a('hello'); //非直接調用,由於Reference Name是'a'而不是'eval' var f = {eval: eval}; f.eval("hello"); //非直接調用,由於BaseValue是f變量,而不是Environment Record eval('hello'); //直接調用 function test() { var eval = window.eval, x = window.eval; eval('hello'); //直接調用 x('hello'); //非直接調用 }
看到規律了吧?只要是祼的eval
調用就是direct call,前面不要有宿主對象,且函數名必定要是eval
字符串。
瞭解了什麼是直接eval調用後,若是我弄出這樣的代碼呢?
function getClosure() { var a = 1; var b = 2; var eval = function(x){ return x; } return function inner(val) { debugger; console.log(eval(val)); } } var func = getClosure(); func('a'); func('b');
在getClosure
中給一eval賦成一個徹底不相干的函數,進入到debugger時,能不能訪問到a
和b
這兩個變量呢?思考一下。
從eval能夠管窺出來,ES5的規範幾乎是從引擎現實者的角度來考慮問題。若是上下文中沒有eval的話,那麼閉包中的變量將被很好的釋放掉(並不徹底正確,參見A surprising JavaScript memory leak found at Meteor),由於引擎能夠檢測出哪些變量會被引用到,而哪些不會。
對於ES3的引擎而言,如IE6,IE7, IE8,不管閉包中是否包含eval,它們都不會釋放掉那些再也引用不到的變量。緣由在於,ES3規範中沒有對非直接eval
調用進行規範,以下代碼在IE6,7,8能經過,而在現代瀏覽器中會報錯:
function getClosure(){ var a = 1; var b = 2; return function (func, val) { alert(func(val)); } } var f = getClosure(); f(eval, 'a'); f(eval, 'b');