javascript採用的是詞法做用域(靜態做用域) 與之相對的是動態做用域,如下的例子能夠很好地說明javascript
let a = 3
function foo() {
console.log(a);
}
function foowrapper() {
let a = 4
foo()
}
foowrapper() // 3而不是4
複製代碼
靜態做用域是與咱們慣性思惟衝突的,從上述例子能夠看出,咱們第一次看會習慣性往函數調用位置去找變量(動態做用域思惟),可是javascript使用的是靜態做用域,也就是代碼寫在哪裏,做用域就從那裏連接,而不是運行的位置進行做用域連接,這是整個做用域的核心概念html
規格 前端
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// 全部變量對象的列表
// 用於標識符查找
]
};
複製代碼
// [[Scope]]表示內部使用的抽象操做,用於描述語言規範自己
Scope = AO + [[Scope]]
複製代碼
在函數進入上下文(函數建立時)AO/VO,Scope定義以下:java
Scope = AO|VO + [[Scope]]
複製代碼
解析 活動對象是做用域數組的第一個對象,被添加到做用域的前端git
Scope = [AO].concat([[Scope]])
複製代碼
函數激活連接實例github
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
alert(x + y + z);
}
bar();
}
foo(); // 60
複製代碼
對此,咱們有以下的變量/活動對象,函數的的[[scope]]屬性以及上下文的做用域鏈:數組
全局上下文的變量對象是:app
globalContext.VO === Global = {
x: 10
foo: <reference to function> }; 複製代碼
在「foo」建立時,「foo」的[[scope]]屬性是:ecmascript
foo.[[Scope]] = [
globalContext.VO
];
複製代碼
在「foo」激活時(進入上下文),「foo」上下文的活動對象是:函數
fooContext.AO = {
y: 20,
bar: <reference to function> }; 複製代碼
「foo」上下文的做用域鏈爲:
fooContext.Scope = fooContext.AO + foo.[[Scope]]
fooContext.Scope = [
fooContext.AO,
globalContext.VO
];
複製代碼
內部函數「bar」建立時,其[[scope]]爲:
bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
];
複製代碼
在「bar」激活時,「bar」上下文的活動對象爲:
barContext.AO = {
z: 30
};
複製代碼
「bar」上下文的做用域鏈爲:
barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
barContext.Scope = [
barContext.AO,
fooContext.AO,
globalContext.VO
];
複製代碼
對「x」、「y」、「z」的標識符解析以下:
- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
-- globalContext.VO // found - 10
- "y"
-- barContext.AO // not found
-- fooContext.AO // found - 20
- "z"
-- barContext.AO // found - 30
複製代碼
var x = 10;
function foo() {
var y = 20;
function barFD() { // 函數聲明
alert(x);
alert(y);
}
var barFE = function () { // 函數表達式
alert(x);
alert(y);
};
var barFn = Function('alert(x); alert(y);');
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
}
foo();
複製代碼
正常來講,在函數建立時獲取函數的[[scope]]屬性,經過該屬性訪問到全部父上下文的變量,可是由函數構造函數建立的的函數,其[[scope]]老是惟一的全局對象屬性。
eval可以動態生成函數,且eval上下文與當前調用上下文的做用域鏈一致 示例
function foo(str, a) {
var c = 4;
eval( str ); // c = 4
console.log( a, b ); // a = 1 b = 3
}
var b = 2;
var c = 3;
foo( "var b = 3; console.log(c)", 1 );
複製代碼
這裏咱們使用eval打印了c,能夠看到結果是foo做用中的c,這裏判斷出eval不是像函數構造函數的[[Scope]]老是惟一的全局變量,而b變量可以在foo中訪問,也說明了eval與foo是享受了同一個做用域,固然嚴格模式下,eval會建立本身的做用域,不會影響到當前調用上下文的做用域,以下例:
function foo(str, a) {
'use strict'
var c = 4
eval(str) // 4
console.log(`foo-a ${a} foo-b ${b}`) // 1, 2 而不是 1 3
}
var b = 2
var c = 3
foo('var b = 3; console.log("eval-b",b,"eval-c",c)', 1)
複製代碼
這裏訪問b時,輸出了2而不是eval中的3,說明嚴格模式下,eval建立了本身的做用域,不會影響到當前調用的上下文做用域
它們將做用變量添加到做用域鏈最前端,做用域鏈修改以下:
Scope = withObject | catchObject + AO | VO +[[Scope]]
複製代碼
with實例
var x = 10, y = 10;
with ({x: 20}) {
var x = 30, y = 30;
console.log(x) // 30
console.log(y) // 30
}
console.log(x) // 10
console.log(y) // 30
複製代碼
過程解析:
catch實例
try {
...
} catch (ex) {
console.log(ex)
}
複製代碼
做用域鏈修改成:
var catchObject = {
ex: <exception object> }; Scope = catchObject + AO|VO + [[Scope]] 複製代碼
爲何會有二維做用域鏈呢,由於一個屬性在對象中沒有直接找到,查詢會在原型鏈中繼續。也就是搜索屬性
二維做用域鏈是指在做用域查找以後,再去原型鏈上查找,分爲一下兩種狀況
狀況1示例
function foo() {
console.log(x)
}
Object.prototype.x = 10;
foo(); // 10
複製代碼
過程解析:
狀況2示例
function foo() {
console.log(o.x)
}
let o = {
}
Object.prototype.x = 200
foo();
複製代碼
過程解析:
參考了顏海鏡大大翻譯的ES5.1規格,感謝他和湯姆大叔的無私奉獻,而後我才能發現一些東西別人幾年前就看透了,本身好菜呀(劃掉),這裏是湯姆大叔的原文,我推薦大看一下他的深刻js系列,雖然在2012寫的,可是放在如今仍然是教科書級別的乾貨,例子出於他的文章和《你不知道的javascript》
這篇文章昨晚寫到了1點半,今天又花了一早上來整理,深入的認識到願意寫、寫明白技術文章那些人的厲害,寫做過程當中也認識到自身水平不足,有點駕馭不住技術深度高的文章,嗯,如今一共寫了3篇文章,有三顆小星星,但願此次可以多一顆,更多更好啦,更多但願你們提出不一樣的意見,一塊兒學習。