首先,咱們通常被"執行上下文","執行上下文環境","上下文環境","執行上下文棧"這些名詞搞混。那我們一一來揭秘這些名字的含義。
這一塊一直比較晦澀難懂,還是需要仔細去斟酌斟酌。
什么是執行上下文(也叫做“執行上下文環境”,“上下文環境”)?
咱們還是先看代碼。
console.log(a) // undefined var a = 100fn('bella') // 'bella' 20 function fn(name) {age = 20console.log(name, age)var age }console.log(b); // 這里報錯 // Uncaught ReferenceError: b is not defined
第一個console輸出 undefined,說明瀏覽器在執行console.log(a)的時候,已經知道a的存在的,但是不知道a的值。
第二個fn("bella")輸出 "bella" 20,說明瀏覽器在執行的時候已經知道fn函數了,并且執行了
第三個console報錯,b is noe defined。說明沒有找到b
那么可以看出來,瀏覽器在執行之前做了一些準備工作。
那做了些什么準備工作:
- 全局上下文環境: 變量定義,函數聲明
- 函數上下文環境(函數內部):變量定義,函數聲明,this,arguments
下面這個例子很多地方都用來講解執行上下文和上下文棧。
1 // 這是一個壓棧出棧的過程--執行上下文棧 2 let a = 10; // 1、進入全局上下文環境 3 let fn; 4 let bar = function(x) { 5 let b = 5 6 fn(x + b) // 3、進入fn函數上下文環境 7 }; 8 fn = function(y) { 9 let c = 5 10 console.log(y + c) 11 } 12 13 bar(10) // 2、 進入bar函數上下文
?這里引出了執行上下文棧的概念,上下文棧就是壓棧和出棧的過程。
1.在代碼執行之前,首先創建全局上下文環境
// 全局上下文環境 a: undefinedfn: undefinedbar: undefined,
this: window
2.然后執行代碼,在代碼執行到12之前,全局上下文中的變量在執行中被賦值
// 全局上下文環境a: 10fn: functionbar: function,
this: window
然后執行13行代碼,調用bar函數,會創建一個新的執行上下文環境。并將這個bar上下文環境壓棧,并設置為活動狀態
// bar函數上下文環境 b: undefinedx: 10arguments: [10]
this: window
3.然后執行到第6行代碼,調用fn的時候,會創建一個新的執行上下文。并將這個fn上下文環境壓棧,并設置為活動狀態。
// fn函數上下文環境 c: undefined y: 15 arguments: [15]
this: window
4.fn執行完畢后,調用fn函數生成的fn上下文環境出棧,銷毀。然后bar出棧銷毀。然后全局上下文出棧銷毀
?
?
理解完了執行上下文,再看看this
相信都知道這句話,誰調用函數,this就指向誰。那么我們理解下this:
var a = {name: 'A',fn: function() {console.log(this.name)}}a.fn() // this === a a.fn.call({name: 'B'}) // this === {name: 'B'}var fn1 = a.fnfn1() // this === window
this: this的值只有在執行的時候才能確認,定義的時候不能確認。因為this是執行上下文的一部分,而執行上下文需要再代碼執行之前確定。
- 作為構造函數執行,構造函數中
- 作為對象屬性執行,上述代碼中a.fn()
- 作為普通函數執行,上述代碼中fn1()
- 用于call apply bind,上述代碼中a.fn.call()
作用域
作用域鏈
let a = 100
function fn() {let b = 20function bar() {console.log(a + b) // a是自由變量}return bar
}let x = fn(), b = 200
x()
1 function F1() { 2 var a = 100 3 return function () { 4 console.log(a) 5 } 6 } 7 var f1 = F1() 8 var a = 200 9 f1()
?
- 函數作為返回值,上面的例子就是
- 函數作為參數傳遞
?