舉個例子:數組
function foo() { console.log('foo函數執行了'); function bar() { console.log('bar函數執行了'); } bar(); } foo();
代碼執行時,執行棧如何變化緩存
建立全局上下文G_EC
,併入棧。閉包
執行棧:app
G_EC
執行foo函數,建立foo
的執行上下文foo_EC
, 併入棧。模塊化
執行棧:函數
foo_ECG_ECthis
執行console.log
函數,建立console.log
函數的執行上下文clg_EC
, 併入棧。spa
執行棧:code
clg_ECfoo_EC模塊化開發
G_EC
console.log
執行完畢,打印出 'foo函數執行了',銷燬clg_EC
,出棧。
執行棧:
foo_ECG_EC
執行bar
函數,建立bar
的執行上下文bar_EC
, 併入棧。
執行棧:
bar_ECfoo_EC
G_EC
執行console.log
函數,建立console.log
函數的執行上下文clg_EC
, 併入棧。
執行棧:
clg_ECbar_EC
foo_EC
G_EC
注意:這裏的clg_EC
是一個全新的上下文,和上一個不同,函數每次調用都生成一個新的獨一無二的執行上下文。
console.log
執行完畢,打印出 'bar函數執行了',銷燬clg_EC
,出棧。
執行棧:
bar_ECfoo_EC
G_EC
bar
函數執行完畢,銷燬bar_EC
, 出棧。
執行棧:
foo_ECG_EC
foo
函數執行完畢,銷燬foo_EC
, 出棧。
執行棧:
G_EC
所有代碼執行完畢,銷燬G_EC
, 出棧。
執行棧:
空
//用代碼表示一個執行上下文 EC = { VO = {...}, SC = [...], this = {...} }
[[scope]]
屬性,指向一個數組,數組中保存的是除本身的執行上下文之外的其餘執行上下文;不影響執行做上下文的理解,可跳過。
this
的指向問題:
全局環境中,this
指向window
對象
console.log(this); //window
函數中的this
,指向window
(嚴格模式中指向undefined
)
function foo(){ console.log(this); //window } foo();
使用call
、apply
調用,this
指向call
/apply
的第一個參數
var obj = {a: 1}; function foo(){ console.log(this); //obj } foo.call(obj); foo.apply(obj);
調用對象中的函數,使用obj.fun
方式調用,this
指向obj
var obj = { a: 1, foo: function(){ console.log(this); } }; obj.foo(); //this指向obj var bar = obj.foo; bar(); //至關於將函數放在全局中執行,this指向window
在一個函數的VO建立時,js引擎作的事情
遇到同名屬性則覆蓋
舉個例子:
function foo(a, b){ console.log(bar); //ƒ bar(){} console.log(a); //2 function bar(){} var a = 1; } foo(2, 3);
按照建立步驟生成foo函數的VO——foo_VO
//1. 肯定形參的值,一開始有兩個形參的值 foo_VO = { arguments: {...}, //arguments一開始就會在 a: undefined, b: undefined, } //2. 變量聲明提高, 內部有一個變量聲明a,當前VO對象已經有a屬性,因此不變 foo_VO = { arguments: {...}, a: undefined, b: undefined, } //3. 將實參的值賦給形參,執行foo(2, 3)時傳入了實參2, 3,分別賦值給a, b foo_VO = { arguments: {...}, a: 2, b: 3, } //4. 函數聲明提高, 有一個函數foo,將foo函數做爲VO的屬性 foo_VO = { arguments: {...}, a: 2, b: 3, foo: function(){} }
因此最後foo函數產生的VO對象就是
foo_VO = { arguments: {...}, a: 2, b: 3, foo: function(){} }
由於VO是在函數運行前建立的,函數在運行的時候就能夠在當前VO中查找變量,因此這就解釋了爲何console.log
放在函數的最前面也能夠打印a
和foo
的值。
SC = 上一層執行上下文棧的AO + 上一層執行上下文棧的SC
舉個例子:
function foo() { function bar() { function baz(){ } } }
全局執行上下文,一開始的SC爲空
g_EC = { SC: [], AO: {...}, this: {...}, }
foo函數執行上下文,其中SC = 全局上下文的AO + 全局上下文的SC
foo_EC = { SC: [g_EC.AO], //[g_EC.AO, ...g_EC.SC] AO: {...}, this: {...}, }
bar函數執行上下文,其中SC = foo函數執行上下文的AO + foo函數執行上下文的SC
bar_EC = { SC: [foo_SC.AO, g_EC.AO], //[foo_SC.AO, ...foo_SC.SC], AO: {...}, this: {...}, }
baz函數執行上下文,其中SC = bar函數執行上下文的AO + bar函數執行上下文的SC
baz_EC = { SC: [bar_SC.AO, foo_SC.AO, g_EC.AO], //[bar_SC.AO, ...bar_SC.SC], AO: {...}, this: {...}, }
做用域圖解:
函數運行時,查找變量,會先查找本身的AO。若是沒有,再依次沿着SC的第0項、第1項... 日後找。找到SC的最後一項都沒有找到就會報錯:
Uncaught ReferenceError: xxx is not defined
當一個函數中的函數被保存到該函數的外部就會造成閉包。
function foo(){ var a = 1; return function bar(){ return a; } } var baz = foo(); var qux = baz(); console.log(qux); //1
foo
在運行的時候
foo_EC = { SC: [GO], AO: { a: 1, ... }, this: {...}, }
當foo
運行完畢後會銷燬本身的執行上下文,其中的AO也被銷燬
可是因爲bar
被保存到了外部, 也就是baz
中,而bar
的做用域SC中有foo
的AO,因此這就解釋了爲何造成閉包時,外部的函數能夠使用函數內部的變量。
baz_EC = { SC: [foo_EC.AO, GO], AO: {...}, this: {...}, }
由於bar
函數被保存到全局做用域中,其中的foo
的AO一直存在,沒法被銷燬,會形成內存泄露;在使用完畢後應該去掉原來的閉包
baz = null
閉包的做用: