標籤:style blog java 使用 io strong ar art
今天又被一段javascript代碼神奇到了
function test() { console.log(this, eval(‘this‘), (0, eval)(‘this‘)); };var a = {};test.apply(a);
在現代瀏覽器的console裡跑上邊的代碼,結果應該類似是下邊這樣
Object {} Object {} Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
this和eval(‘this‘)的結果是Object {},也就是a。而(0, eval)(‘this‘)的結果就變成了window,也就是全域環境裡的this。
為什麼呢?
解釋這段代碼其實需要梳理兩個概念
1. this是什麼
2. eval的scope
Javascript方程的四種調用模式
首先簡單介紹下這部分知識,我是從JavaScript: The Good Parts裡讀的,這書真是看過的都說好。
JavaScript裡調用函數時除了函式宣告裡的參數外還會外加一個參數this,而this的值取決於調用函數的方式。
四種調用方式分別是:
1. 方法調用(Method Invocation Pattern):函數為一個對象的屬性,比如myObj.functionName(param),this為該對象(myObj)
2. 函數調用(Function Invocation Pattern):像functionName(param)這樣的調用模式,this為全域對象
3. 建構函式調用(Constructor Invoation Pattern): 比較特殊的一種,就是new ObjConstructor(param)這類的,this為建立的那個新對象
4. apply調用(Apply Invocation Pattern):也就是我們一開始的代碼的test.apply(a)裡這種,this為apply的參數數組裡的第一個,在這裡也就是a
弄懂了這些,我們知道test.apply(a)調用的test裡,this應該就是a。但是為什麼最後一個(0, eval)(‘this‘)輸出的不是a呢?
ECMAScript 5裡對eval的規範
首先引用幾條百度來的ECMAScript5.1規範中文版
10.4.2 進入eval代碼
當控制流程進入 eval 代碼 的執行環境時,執行以下步驟:
- 如果沒有調用環境,或者 eval 代碼 並非通過直接調用(15.1.2.1.1)eval 函數進行評估的,則
- 按(10.4.1.1)描述的初始化全域執行環境的方案,以 eval 代碼 作為 C 來初始化執行環境。
- 否則
- 將 this 綁定設定為當前執行環境下的 this 綁定。
- 將詞法環境設定為當前執行環境下的 詞法環境 。
- 將變數環境設定為當前執行環境下的變數環境。
- 如果 eval 代碼 是 strict 模式下的代碼 ,則
- 令 strictVarEnv 為以詞法環境為參數調用 NewDeclarativeEnvironment 得到的結果。
- 設定詞法環境為 strictVarEnv。
- 設定變數環境為 strictVarEnv。
- 按 10.5 描述的方案,使用 eval 代碼 執行定義綁定初始化步驟。
15.1.2.1.1 直接調用eval
一個 eval 函數的直接調用是表示為符合以下兩個條件的 CallExpression:
解釋執行 CallExpression 中的 MemberExpression 的結果是個 引用 ,這個引用擁有一個 環境記錄項 作為其基值,並且這個引用的名稱是 "eval"。
以這個 引用 作為參數調用 GetValue 抽象操作的結果是 15.1.2.1 定義的標準內建函數。
10.4.1.1 初始化全域執行環境
以下步驟描述 ECMA 指令碼的全域執行環境 C 的建立過程:
- 將變數環境設定為 全域環境 。
- 將詞法環境設定為 全域環境 。
- 將 this 綁定設定為 全域對象 。
不考慮strict 模式的情況,那麼簡單說,就是ES5裡執行eval分直接調用和非直接調用兩種情況。
在直接調用的情況下,eval的執行環境就是調用它的caller的執行環境。
在非直接調用的情況下,eval的執行環境則是全域環境。
判定是否直接調用eval的依據對代碼的文法解析。
比如最簡單的,eval(‘this‘)就是一個CallExpression,裡邊eval是MemberExpression。按照直接調用的要求,MemberExpression的結果必須是個名稱為eval的引用,我們這裡的eval完全符合要求。所以eval(‘this‘)為直接調用。
再比如(0, eval)(‘this‘)的MemberExpression是(0, eval),JS裡逗號操作符會調用內建的GetValue,所以(0, eval)的結果並不是引用,不符合直接調用的要求,所以(0, eval)(‘this‘)是一個非直接調用。
相對的,(eval)並沒有逗號操作符調用GetValue,所以它的結果還是一個引用,所以(eval)(‘this‘)是直接調用。這也就解釋了一開始的代碼的輸出結果。