js 面試的坑:變數提升

來源:互聯網
上載者:User

標籤:

全域中的解析和執行過程

預先處理:建立一個詞法環境(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);//undefinedconsole.log(b);//undefinedconsole.log(c);//報錯:c is not definedvar 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);//undefinedconsole.log(b);//undefinedconsole.log(c);//c is not definedconsole.log(d);//d is not definedvar 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(){    //f.scope == window    var x = 100;//f.LE == {x:100,g:函數}        var g = function(){//g.scope = f.LE                alert(x);    }    g();//在執行g的時候,先找g.scope,沒有的話再找f.LE,還沒有的話找f.scope……一直往上找window    }f();

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

//定義全域變數color,對於全域都適用,即在任何地方都可以使用全域變數colorvar 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();//這裡只能訪問colorconsole.log(color);                //blueconsole.log(anotherColor);        //anotherColor is not definedconsole.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

 

 

js 面試的坑:變數提升

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.