一、介紹 本篇繼上一篇 "深入理解js執行 單線程的JS" ,這次我們來深入瞭解js執行過程中的執行上下文。 本篇涉及到的名詞:預執行,執行上下文,變數對象,活動對象,作用域鏈,this等 二、預執行 在上一篇說到,在js代碼被執行,執行上下文會被壓進執行棧中,但是在此之前還有一步工作要做,就是創建 ...
一、介紹
本篇繼上一篇深入理解js執行--單線程的JS,這次我們來深入瞭解js執行過程中的執行上下文。
本篇涉及到的名詞:預執行,執行上下文,變數對象,活動對象,作用域鏈,this等
二、預執行
在上一篇說到,在js代碼被執行,執行上下文會被壓進執行棧中,但是在此之前還有一步工作要做,就是創建好執行上下文,因為創建好才能被壓進去啊。
創建執行上下文就是預執行過程: 接下來說說創建執行上下文的細節部分。
三、創建執行上下文
(1)執行上下文組成
執行上下文:也叫一個執行環境,有全局執行環境和函數執行環境兩種。每個執行環境中包含這三部分:變數對象/活動對象,作用域鏈,this的值
代碼模擬
//可以把執行上下文看作一個對象
exeContext = {
VO = [...], //VO代表變數對象,保存變數和函數聲明
scopeChain = [...]; //作用域鏈
thisValue = {...}; //this的值
}
創建執行上下文就是創建變數對象,作用域鏈和this過程
接下來就分別細說創建變數對象/活動對象,作用域鏈,this值的過程。
(2)變數對象(variable object)
變數對象中存儲了在上下文(環境)中定義的變數和函數聲明
創建變數對象(VO)時就是將各種變數和函數聲明進行提升的環節:
//用下麵代碼為例子
console.log(a);
console.log(b);
console.log(c);
console.log(d);
var a = 100;
b = 10;
function c(){};
var d = function(){};
上述代碼的變數對象:
//這裡用VO表示變數對象
VO = {
a = undefined; //有a,a使用var聲明,值會被賦值為undefined
//沒有b,因為b沒用var聲明
c = function c (){} //有c,c是函數聲明,並且c指向該函數
d = undefined; //有d,d用var聲明,值會被賦值為undefined
}
解說:執行上述代碼的時候,會創建一個全局執行上下文,上下文中包含上面變數對象,創建完執行上下文後,這個執行上下文才會被壓進執行棧中。開始執行後,因為js代碼一步一步被執行,後面賦值的代碼還沒被執行到,所以使用console.log函數列印各個變數的值是變數對象中的值。
在運行到第二行時會報錯(報錯後就不再執行了),因為沒有b(b is no defined)。把第二行註釋掉後,再執行各個結果就是VO裡面的對應的值。
講到這裡我想大家對變數對象理解了吧,以及對變數提升和函數提升有個深入瞭解。
(3)活動對象(activation object)
活動對象是在函數執行上下文裡面的,其實也是變數對象,只是它需要在函數被調用時才被激活,而且初始化arguments,激活後就是看做變數對象執行上面一樣的步驟。
//例子
function fn(name){
var age = 3;
console.log(name);
}
fn('ry');
當上面的函數fn被調用,就會創建一個執行上下文,同時活動對象被激活
//活動對象
AO = {
arguments : {0:'ry'}, //arguments的值初始化為傳入的參數
name : ry, //形參初始化為傳進來的值
age : undefined //var 聲明的age,賦值為undefined
}
活動對象其實也是變數對象,做著同樣的工作。其實不管變數還是活動對象,這裡都表明瞭,全局執行和函數執行時都有一個變數對象來儲存著該上下文(環境內)定義的變數和函數。
(4)作用域鏈(scope chain)
在創建執行上下文時還要創建一個重要的東西,就是作用域鏈。每個執行環境的作用域鏈由當前環境的變數對象及父級環境的作用域鏈構成。
創建作用域鏈過程:
//以本段代碼為例
function fn(a,b){
var x = 'string',
}
fn(1,2);
1.函數被調用前,初始化function fn,fn有個私有屬性[[scope]],它會被初始化為當前全局的作用域,fn.[[scope]="globalScope"。
2.調用函數fn(1,2),開始創建fn執行上下文,同時創建作用域鏈fn.scopeChain = [fn.[[scope]]],此時作用域鏈中有全局作用域。
3.fn活動對象AO被初始化後,把活動對象作為變數對象推到作用域鏈前端,此時fn.scopeChain = [fn.AO,fn.[[scope]]],構建完成,此時作用域鏈中有兩個值,一個當前活動對象,一個全局作用域。
fn的作用域鏈構建完成,作用域鏈中有兩個值,第一個是fn函數自身的活動對象,能訪問自身的變數,還有一個是全局作用域,所以fn能訪問外部的變數。這裡就說明瞭為什麼函數中能夠訪問函數外部的變數,因為有作用域鏈,在自身找不到就順著作用域鏈往上找。
(5)this的值
上面說過執行上下文有兩種,一個全局執行上下文,一個函數執行上下,下麵分別說說這兩種上下文的this。
a.全局執行上下文的this
指向window全局對象
b.函數執行上下文的this(主要講函數的this)
在《JavaScript權威指南》中有這麼幾句話:
1.this是關鍵字,不是變數,不是屬性名,js語法不允許給this賦值。
2.關鍵字this沒有作用域限制,嵌套的函數不會從調用它的函數中繼承this。
3.如果嵌套函數作為方法調用,其this指向調用它的對象。
4.如果嵌套函數作為函數調用,其this值是window(非嚴格模式),或undefined(嚴格模式下)。
解讀一下: 上面說的概括了this兩種值的情況:
1.函數直接作為某對象的方法被調用則函數的this指向該對象。
2.函數作為函數直接獨立調用(不是某對象的方法),或是函數中的函數,其this指向window。
我們看幾個慄子便可理解:
慄子1:(這個例子我相信都能理解)當函數被獨立運行時,其this的值指向window對象。
function a(){
console.log(this);
}
//獨立運行
a(); //window
慄子2:(函數中函數,這裡嵌套了個外圍函數)這裡也是指向window對象,也相當於函數作為函數調用,就是獨立運行。其實這個例子也說明閉包的this指向Window。
//外圍函數
function a(){
//b函數在裡面
function b(){
console.log(this);
}
//雖然在函數中,但b函數獨立運行,不是那個對象的方法
b();
}
a(); //window
慄子3:(再寫複雜點的話)x函數即使在對象裡面,但它是函數中的函數,也是作為函數運行,不是Object的方法。getName才是objcet的方法,所以getName的this指向object(在下個慄子有)。
//一個對象
var object = {
//getName是Object的方法
getName : function(){
//x是getName裡面的函數,它是作為函數調用的,this就是window啦
function x(){
console.log(this);
}
x();
}
}
object.getName(); //window
以上三個都是輸出window,下麵是this指向某個對象的情況。
慄子4:函數作為某個對象的方法被調用。
//一個對象
var object = {
name : "object",
//getName是Object的方法
getName : function(){
console.log(this === object);
}
}
object.getName(); //true , 說明this指向了object
這裡的getName中的this是指向objct對象的,因為getName是object的一個方法,它作為對象方法被調用。
慄子5:再來個慄子。
var name = "window";
var obj = {
name : "obj"
};
function fn (){
console.log(this.name);
}
//將fn通過call或bind或apply直接綁定給obj,從而成為obj的方法。
fn.call(obj); //obj
再總結一下this的值
全局執行上下文:this的值是window
函數執行上下文:this的值兩種:
1.函數中this指向某對象,因為函數作為對象的方法:怎麼看函數是對象的方法,一種是直接寫在對象裡面(不是嵌套在對象方法中的函數,不懂再看看慄子3),另一種是通過call等方法直接綁定在對象中。
2.函數中this指向window:函數獨立運行,不是對象的方法,函數中的函數(閉包),其this指向window。
四、總結整個js代碼執行過程
(1)JS執行過程
js代碼執行分成了兩部分:預執行和執行
- 預執行:創建好執行上下文,有兩種,一種是開始執行js代碼就創建全局的執行上下文,一種是當某個函數被調用時創建它自己的函數執行上下文。這裡也就是本節主要講的東西,創建執行上下文的三個重要成分。
- 執行:在執行棧中執行,棧頂的執行上下文獲得執行權,並按順序執行當前上下文中的代碼,執行完後彈棧銷毀上下文,執行權交給下一個棧頂執行上下文。
(2)放上圖示
某個執行上下文生命周期:
五、後話
整個js的執行過程就這樣了,一開始可能有點難理解,但看多幾遍就慢慢領會了。希望大家能夠理解。如果覺得寫得好,記得點贊,關註哦。
本文出自博客園:http://www.cnblogs.com/Ry-yuan/
作者:Ry(淵源遠願)
歡迎轉載,轉載請標明出處,保留該欄位。