JS的解析與執行過程

轉載自:https://www.cnblogs.com/foodoir/p/5977950.html

全局中的解析和執行過程

預處理:創建一個詞法環境(LexicalEnvironment,在後面簡寫爲LE),掃描JS中的用聲明的方式聲明的函數,用var定義的變量並將它們加到預處理階段的詞法環境中去。

一、全局環境中如何理解預處理

比如說下面的這段代碼:

var a = 1;//用var定義的變量,以賦值
var b;//用var定義的變量,未賦值
c = 3;//未定義,直接賦值
function d(){//用聲明的方式聲明的函數
    console.log('hello');
}
var e = function(){//函數表達式
    console.log('world');
}

在預處理時它創建的詞法作用域可以這樣表示:

LE{        //此時的LE相當於window
    a:undefined
    b:undefined
    沒有c
    d:對函數的一個引用
    沒有e
}

強調:1、預處理的函數必須是JS中用聲明的方式聲明的函數(不是函數表達式),看例子:

d();
e();
function d(){//用聲明的方式聲明的函數
    console.log('hello');
}
var e = function(){//函數表達式
    console.log('world');
}

輸出結果分別是:hello;報錯e is not a function
2、是用var定義的變量,看例子:

console.log(a);//undefined
console.log(b);//undefined
console.log(c);//報錯:c is not defined
var a = 1;
var b;
c = 3;

 

二、命名衝突的處理

來看下面的代碼:你覺得輸出結果是什麼?

console.log(f);
var f = 1;
function f(){
    console.log('foodoir');
}
console.log(f);
function f(){
    console.log('foodoir');
}
var f = 1;
console.log(f);
var f = 1;
var f = 2;
console.log(f);
function f(){
    console.log('foodoir');
}
function f(){
    console.log('hello world');
}

你可能跟我開始一樣,覺得輸出的是foodoir,這樣你就錯了,你應該繼續看看前面講的預處理的問題。第一段代碼輸出的結果應該是:function f(){console.log('foodoir');}。

看到第二段代碼,你可能想都沒想就回答結果是1,並且你還告訴原因說javascript裏面的函數沒有傳統意義的重載。是的javascript裏面的函數是沒有重載,但是第二段代碼的運行結果仍然是:function f(){console.log('foodoir');}。(原因後面作解釋)

如果你還覺得第三段代碼的結果是2或者是1,那麼我建議你回到前面看看關於預處理的例子。第三段的結果爲:undefined。

第四段代碼的結果爲function f(){console.log('hello world');}

原因:處理函數聲明有衝突時,會覆蓋;處理變量聲明有衝突時,會忽略。在既有函數聲明又有變量聲明的時候,你可以跟我一樣像CSS中的權重那樣去理解,函數聲明的權重總是高一些,所以最終結果往往是指向函數聲明的引用。

三、全局函數的執行

來看下面的例子:

 1 console.log(a);
 2 console.log(b);
 3 console.log(c);
 4 console.log(d);
 5 var a = 1;
 6 b = 2;
 7 console.log(b);
 8 function c(){
 9     console.log('c');
10 }
11 
12 var d = function(){
13     console.log('d');
14 }
15 console.log(d);

1、我們先分析全局預處理的情況,結果如下:

LE{
    a:undefined
    沒有b
    c:對函數的一個引用
    d:undefined
}

此時,我們可以得到前四行代碼得到的結果分別爲:

 undefined
  報錯
  function c(){console.log('c');
  undefined

2、當執行完預處理後,代碼開始一步步被解析(將第二行報錯的代碼註釋掉)

在第6行代碼執行完,LE中的a的值變爲1;

LE{
    a:1
    沒有b
    c:對函數的一個引用
    d:undefined
}

第7行代碼執行完,LE中就有了b的值(且b的值爲2,此時b的值直接變爲全局變量);

LE{
    a:1
    b:2
    c:對函數的一個引用
    d:undefined
}

第10行代碼執行完,

LE{
    a:1
    b:2
    c:指向函數
    d:undefined
}

第14行代碼執行完,此時

LE{
    a:1
    b:2
    c:指向函數
    d:指向函數
}

關於b變爲全局變量的例子,我們在控制檯中輸入window.b,可以得到b的結果爲2。

結果如圖:



補充:運用詞法的作用域,我們可以很好的解釋一個帶多個參數的函數只傳遞一個參數的例子。

function f(a,b){
    
}
f(1);

它的詞法作用域可以這樣解釋:

LE{
    a:1
    b:undefined
}

函數中的解析和執行過程

函數中的解析和執行過程的區別不是很大,但是函數中有個arguments我們需要注意一下,我們來看下面的例子:

function f(a,b){
    alert(a);
    alert(b);
    
    var b = 100;
    function a(){}
}
f(1,2);

我們先來分析函數的預處理,它和全局的預處理類似,它的詞法結構如下:

LE{
    b:2
    a:指向函數的引用
    arguments:2
}
//arguments,調用函數時實際調用的參數個數

再結合之前的那句話:處理函數聲明有衝突時,會覆蓋;處理變量聲明時有衝突,會忽略。
故結果分別爲:function a(){}和2

當傳入的參數值有一個時:

function f(a,b){
    alert(a);
    alert(b);
    
    var b = 100;
    function a(){}
}
f(1);

這個時候的詞法結構如下:

LE{
    b:undefined
    a:對函數的一個引用
    arguments:1
}

故結果分別爲:function a(){}和undefined
還有一個需要注意的地方有:如果沒有用var聲明的變量,會變成最外部LE的成員,即全局變量

function a(){
    function b(){
        g = 12;
    }
    b();
}
a();
console.log(g);//12

控制檯結果:

有了前面的基礎,我們就可以對JS的作用域和作用域鏈進行深入的瞭解了。

關於JS作用域和作用域鏈

console.log(a);//undefined
console.log(b);//undefined
console.log(c);//c is not defined
console.log(d);//d is not defined

var a = 1;
if(false){
    var b = 2;
}else{
    c = 3;
}
function f(){
    var d = 4;
}

有了前面的基礎我們很容易就可以得到前三個的結果,但是對於第四個卻很是有疑問,這個時候,你就有必要看一看關於javascript作用域的相關知識了。
  在編程語言中,作用域一般可以分爲四類:塊級作用域、函數作用域、動態作用域、詞法作用域(也稱靜態作用域)

塊級作用域

在其它C類語言中,用大括號括起來的部分被稱爲作用域,但是在javascript並沒有塊級作用域,來看下面一個例子:

for(var i=0;i<3;i++){
    //
}
console.log(i);

它的結果爲3,原因:執行完for循環後,此時的i的值爲3,在後面仍有效

函數作用域

沒有純粹的函數的作用域

動態作用域

來看下面的例子:

function f(){
    alert(x);
}
function f1(){
    var x = 1;
    f();
}
function f2(){
    var x = 1;
    f();
}
f1();
f2();

如果說存在動態作用域,那麼結果應該是分別爲1、2,但是最終結果並不是我們想要的,它的結果爲:x is not defined。所以javascript也沒有動態作用域

詞法作用域(也稱靜態作用域)

我們可以在函數最前面聲明一個x=100

var x=100;
function f(){
    alert(x);
}
function f1(){
    var x = 1;
    f();
}
function f2(){
    var x = 1;
    f();
}
f1();
f2();

結果爲分別彈出兩次100。說明javascript的作用域爲靜態作用域 ,分析:

function f(){
    alert(x);
}
// f [[scope]]  == LE ==  window
//創建一個作用域對象f [[scope]],它等於創建它時候的詞法環境LE(據前面的知識我們又可以知道此時的詞法環境等於window)

function f1(){
    var x = 1;
    f();//真正執行的時候(一步一步往上找)LE  ->f.[[scope]]  ==  window
}

在詞法解析階段,就已經確定了相關的作用域。作用域還會形成一個相關的鏈條,我們稱之爲作用域鏈。來看下面的例子:

function f(){
    alert(x);
}
// f [[scope]]  == LE ==  window
//創建一個作用域對象f [[scope]],它等於創建它時候的詞法環境LE(據前面的知識我們又可以知道此時的詞法環境等於window)

function f1(){
    var x = 1;
    f();//真正執行的時候(一步一步往上找)LE  ->f.[[scope]]  ==  window
}

最終結果爲:100
來看一個經典的例子:

//定義全局變量color,對於全局都適用,即在任何地方都可以使用全局變量color
var color = "red";

function changeColor(){
    //在changeColor()函數內部定義局部變量anotherColor,只在函數changeColor()裏面有效
    var anotherColor = "blue";
    
    function swapColor(){
        //在swapColor()函數內部定義局部變量tempColor,只在函數swapColor()裏面有效
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
        
        //這裏可以訪問color、anotherColor和tempColor
        console.log(color);                //blue
        console.log(anotherColor);        //red
        console.log(tempColor);            //blue
    }
    
    swapColor();
    //這裏只能訪問color,不能訪問anotherColor、tempColor
    console.log(color);                //blue
    console.log(anotherColor);        //anotherColor is not defined
    console.log(tempColor);            //tempColor is not defined
}

changeColor();
//這裏只能訪問color
console.log(color);                //blue
console.log(anotherColor);        //anotherColor is not defined
console.log(tempColor);            //tempColor is not defined

還需要注意的是:new Function的情況又不一樣

var x= 123;
function f(){
    var x = 100;
    //g.[[scope]]  == window
    var g = new Function("","alert(x)");
    g();
}
f();
//結果爲:123

小結:

以f1{ f2{ x}}爲例,想得到x,首先會在函數裏面的詞法環境裏面去找,還沒找到去父級函數的詞法環境裏面去找……一直到window對象裏面去找。
這時候,問題來了。。。。

問題1:到這裏看來如果有多個函數都想要一個變量,每次都要寫一個好麻煩啊,我們有什麼方法可以偷懶沒?

方法:將變量設置爲全局變量

問題2:不是說要減少全局用量的使用麼?因爲在我們做大項目的時候難免要引入多個JS庫,變量間的命名可能會有衝突,且出錯後不易查找,這個時候我們該怎麼辦呢?

方法:將變量設置在一個打的function中,比如下面這樣:

function(){
    var a = 1;
    var b = 2;
    function f(){
        alert(a);
    }
}

 



問題3:照你的這種方法我們在外面又訪問不到了,怎麼辦?

方法:我們使用匿名函數的方法,示例如下:

(function(){
    var a = 1,
        b = 2;
    function f(){
        alert(a);
    }
    window.f = f;
})();
f();
//結果爲:1

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章