理解JavaScript作用域

作用域也就是變量對象能被其他對象訪問的作用範圍

主要包括以下幾個概念:

1、全局作用域(Global Scope)與局部作用域(Local Scope)

2、靜態(詞法)作用域(Lexical Scope)與動態作用域(Dynamic Scope)

3、塊級作用域(Block Scope)

4、作用域鏈(Scope Chain)

理解JavaScript作用域

現在來具體舉例說明下這幾個區別:

1、全局作用域,指在全局的範圍都能訪問到。例如:

let a = 1;

(function foo() {

b = 2;

}())

window.c = 3;

console.log(a, b, c);

其中a,b,c能被全局訪問到。a定義在當前全局環境,b沒有關鍵詞聲明也是全局,c定義在window全局對象下。

2、局部作用域,也就是函數作用域,只有在函數內部才能訪問到

var a = 1;

(function foo() {

var a = 2;

var b = 2;

}())

console.log(a, typeof b);

從外部能訪問到a,但是外部的a,值為1,而b就是undefined。

理解JavaScript作用域

局部作用域能訪問全局的作用域,子函數能訪問父函數的作用域,但父函數不能訪問子函數的作用域。如果要想外部訪問內部作用域,那就得利用閉包了,相當於利用函數複製了一個封閉的空間出來,供外部調用。

3、詞法作用域,也就是作用域在變量定義時決定,範圍在所定義的位置,因為JS解析引擎在提取代碼進行詞法分析生成語法樹時就決定了。動態作用域則是程序在運行階段動態來確定變量的作用域範圍,也就是從調用方來查找,而不是定義的環境。JS的call與apply,bind可以動態地改變執行上下文,可以改變變量的執行上下文this,相當於修改了對象作用域的範圍,但不是修改作用域。

理解JavaScript作用域

var a = 1;

var a = 2;

function foo() {

console.log(a);

}

function change() {

var a = 3;

foo();

}

change();

變量a在定義時的作用範圍內,所以打印仍是2,foo裡面a作用範圍在定義時的foo局部作用域內。如果是動態作用域則打印3。一般編程語言都是靜態作用域,例如C、Java、Python、JS等,但如shell腳本或velocity模板語言是動態作用域。

4、塊級作用域,也就是變量作用範圍在一個塊內,一般是花括號標識。大多數語言都是塊級作用域域。JS直到ES6推出了後,通過let關鍵字聲明的變量作用域在塊內。

{

var a = 1;

let b = 1;

}

console.log(a, b);

這裡a將打印1,b就會報錯,因為變量未定義而調用。

for (var i = 0; i < 5; i++) {

setTimeout(function() {

console.log(i);

}, 0);

}

這裡會打印5次5,因為沒有塊級作用域,後面的覆蓋了前面的,這裡一般要用閉包才能打印出1到5來。

for (let i = 0; i < 5; i++) {

setTimeout(function() {

console.log(i);

}, 0);

}

而如果換成let,則打印1到5,雖然同樣是超時打印,這是因為變量的作用域不同。至於let與var的其他不同,以及var帶來的變量提升等問題,就不在這裡贅述了。

5、作用域鏈,JS的代碼在執行時會針對執行代碼塊(global, function和eval)創建執行上下文(Execution Context, 包括this, variable object,[[scope]]等),執行上下文中的變量對象會從子函數(是一個執行上下文)逐級往上到父函數(也是一個執行上下文)中查找該變量,一直到全局。函數通過一個[[scope]]屬性供其他的其他對象來鏈接查找。這裡注意不要將變量逐級往上查找與變量作用域本身混淆。變量逐級查找是基於作用域鏈向上查找該變量,而變量作用域是該變量的作用範圍,這個範圍是聲明時所在的位置。同時也不要跟this混淆(this表示對象本身,執行上下文也會創建一個this,用來代表這個執行環境,但並不等於這個上下文),函數的this可以看作是函數的指針,指向函數的調用方,它可以被動態修改。

var a = 'a';

function outer() {

var b = 'b';

function child(b) {

a = '1';

b = '2';

function inner() {

console.log('inner:', 'a=' + a, 'b=' + b);

}

inner(b);

}

child(b);

console.log('outer:', 'a=' + a, 'b=' + b);

}

outer();

輸出:

inner: a=1 b=2

outer: a=1 b=b

inner結果原因:a變量在inner裡面沒有,向上查找child,child裡沒有,再向上找到outer,依然沒有,直到最外層找到a。a在執行child()時被重新重新賦值為1。b是向上在child作用域中找中。

outer結果原因:a在outer作用域下沒有,向上才找到a,但a在執行child()時已經被賦值為1了,b因為傳遞的是值而非引用,因此child()執行,b被修改時並非修改的outer下的b,而是child(b)形參定義的b變量。

理解JavaScript作用域

以上就把作用域說完了,很簡單吧。好了,來一個例子,如果你能說清原理,那就說明理解了。

function foo(b) {

var a = b;

return {

a: a,

change: function(b) {

console.log(a, b, this.a);

a = b + this.a;

},

output: function() {

console.log(a, this.a, b);

}

}

}

var x = foo(1);

console.log('change before:')

console.log(x.a); // 1;

x.output(); // 1, 1, 1

x.a = 3;

x.change(2); // 1, 2, 3

console.log('after change:');

console.log(x.a); // 3

x.output(); // 5, 3, 1

關於這個答案講解可以放在以後的章節中。


分享到:


相關文章: