知識點:
1、執行上下文 & 作用域鏈 & 變量提升
2、this 的七種使用場景
3、作用域與閉包:什么是閉包,優缺點是什么,使用場景有哪些
一、執行上下文(execution context)
1、Javascript 中代碼的運行環境分為以下三種:
-
全局環境 - 這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境。
-
函數環境 - 當執行一個函數時,運行函數體中的代碼。
-
Eval - 在Eval函數內運行的代碼。
JavaScript是一個單線程語言,這意味着在瀏覽器中同時只能做一件事情。JavaScript代碼的整個執行過程,分為兩個階段,代碼編譯階段與代碼執行階段。
編譯階段由編譯器完成,將代碼翻譯成可執行代碼,這個階段作用域規則會確定。執行階段由引擎完成,主要任務是執行可執行代碼,執行上下文在這個階段創建。
當 JavaScript 解釋器初始執行代碼,它首先默認進入全局上下文。每次調用一個函數將會創建一個新的執行上下文。
JavaScript引擎會以棧的方式來處理它們,這個棧,我們稱其為函數調用棧(call stack)。棧底永遠都是全局上下文,而棧頂就是當前正在執行的上下文。
2、執行上下文是用於跟蹤代碼的運行情況,其特征如下:
(1)一段代碼塊對應一個執行上下文,被封裝成函數的代碼被視作一段代碼塊,或者“全局作用域”也被視作一段代碼塊。
(2)當程序運行,進入到某段代碼塊時,一個新的執行上下文被創建,並被放入一個 stack 中。當程序運行到這段代碼塊結尾后,對應的執行上下文被彈出 stack。
(3)當程序在某段代碼塊中運行到某個點需要轉到了另一個代碼塊時(調用了另一個函數),那么當前的可執行上下文的狀態會被置為掛起,然后生成一個新的可執行上下文放入 stack 的頂部。
(4)stack 最頂部的可執行上下文被稱為 running execution context。當頂部的可執行上下文被彈出后,上一個掛起的可執行上下文繼續執行。
3、執行上下文的建立過程
每當調用一個函數時,一個新的執行上下文就會被創建出來。然而,在javascript引擎內部,這個上下文的創建過程具體分為兩個階段:
(1)建立階段:(發生在 一個函數被調用時 && 其函數體內的代碼執行前)
A 生成變量對象:創建 arguments 對象 --> 檢查function函數聲明創建屬性 --> 檢查var變量聲明創建屬性;(這就是變量提升的原因)
B 建立作用域鏈ScopeChain(作用域鏈本質上是一個指向變量對象的指針列表,它只引用不包含實際變量對象)
C 確定this的值(所以,this的指向,是在函數被調用的時候確定的,也就是執行上下文被創建時確定的。)
(2)代碼執行階段:
變量賦值,函數引用,執行其它代碼
注:a、變量對象和活動對象:他們其實都是同一個對象,只是處於執行上下文的不同生命周期。只有處於函數調用棧棧頂的執行上下文中的變量對象,才會變成活動對象。
b、變量提升:執行上下文 創建階段 先進行function函數聲明,再進行var變量聲明,執行上下文 執行階段 再對var 變量進行賦值。
二、this的5種使用場景
1、全局對象中的this:指向window
2、函數中的this
如果調用者函數,被某一個對象所擁有,那么該函數在調用時,內部的this指向該對象。
如果函數獨立調用,那么該函數內部的this,則指向undefined。但是在非嚴格模式中,當this指向undefined時,它會被自動指向全局對象。
3、使用call,apply顯示指定this
JavaScript內部提供了一種機制,讓我們可以自行手動設置this的指向。它們就是call與apply。
fn.call(obj);// fn並非屬於對象obj的方法,但是通過call,我們將fn內部的this綁定為obj,因此就可以使用this.xxx訪問obj的xxx屬性了。
call與applay的不同:第二個參數,都是向將要執行的函數傳遞參數。其中call以一個一個的形式傳遞,apply以數組的形式傳遞。
4、構造函數與原型方法上的this
通過new操作符調用構造函數,會經歷以下4個階段。
(1)創建一個新的對象;
(2)將構造函數的this指向這個新對象;(obj.__proto__ = Fn.prototype;)
(3)指向構造函數的代碼,為這個對象添加屬性,方法等;
(4)返回新對象。
因此,當new操作符調用構造函數時,this其實指向的是這個新創建的對象,最后又將新的對象返回出來,被實例對象接收。因此,我們可以說,這個時候,構造函數的this,指向了新的實例對象。
5、箭頭函數中的this
三、閉包
通過閉包,我們可以在其他的執行上下文中,訪問到函數的內部變量。
閉包被保存在了全局變量中,但是閉包的作用域鏈並不會發生任何改變。在閉包中,能訪問到的變量,仍然是作用域鏈上能夠查詢到的變量。
常應用於 模塊化與柯里化。
缺點是內存泄露。