首頁  >  文章  >  web前端  >  JavaScript執行順序詳細介紹_基礎知識

JavaScript執行順序詳細介紹_基礎知識

WBOY
WBOY原創
2016-05-16 17:10:501447瀏覽

先前從JavaScript引擎的解析機制來探索JavaScript的工作原理,以下我們以更形象化的範例來說明JavaScript程式碼在頁面中的執行順序。如果說,JavaScript引擎的工作機制比較深奧是因為它屬於底層行為,那麼JavaScript程式碼執行順序就比較形象了,因為我們可以直觀感覺到這種執行順序,當然JavaScript程式碼的執行順序是比較複雜的,所以在深入JavaScript語言之前也有必要對其進行剖析。
1.1  按HTML文檔流順序執行JavaScript程式碼
首先,讀者應該清楚,HTML文檔在瀏覽器中的解析過程是這樣的:瀏覽器是按著文檔流從上到下逐步解析頁面結構和資訊的。 JavaScript程式碼作為嵌入的腳本應該也算做HTML文件的組成部分,所以JavaScript程式碼在載入時的執行順序也是根據腳本標籤<script>的出現順序來決定的。例如,瀏覽下面的文件頁面,你會看到程式碼是從上到下逐步解析。 </script>

複製程式碼 程式碼如下:

<script><BR>alert("頂部腳本"alert("頂部腳本"alert("頂部腳本"alert("頂部腳本"alert("頂部腳本"alert("頂部腳本"alert(" );<BR></script>

<script><BR>alert("頭部腳本");<BR></script>
<br><br>
<script><BR>alert("頁面腳本");<BR></script>

<script><BR>alert("底部腳本");<BR></script>

如果透過腳本標籤<script>的src屬性導入外部JavaScript文件腳本,那麼它也將按照其語句出現的順序來執行,而且執行過程是文件裝載的一部分。不會因為是外部JavaScript檔案而延期執行。例如,把上面文件中的頭部和主體區域的腳本移到外部JavaScript檔案中,然後透過src屬性匯入。繼續預覽頁面文檔,你會看到相同的執行順序。 <BR><div class="codetitle"><span><a style="CURSOR: pointer" data="39983" class="copybut" id="copybut39983" onclick="doCopy('code39983')"><U>複製程式碼 程式碼如下:<div class="codebody" id="code39983"><BR><script></script>

alert("頂部腳本");

<script></script>

alert("底部腳本");

1.2  預編譯與執行順序的關係

在Javascript中,function才是Javascript的第一型。當我們寫下一段函數時,其實不過是建立了一個function類型的實體。
就像我們可以寫成這樣的形式:

複製程式碼 程式碼如下:

程式碼如下:

functionHello()
{
alert("Hello");
}
Hello();
varHello = function()
{
alert("Hello") ;
}
Hello();

其實都是一樣的。 但是當我們對其中的函數進行修改時,會發現很奇怪的問題。 複製程式碼
程式碼如下:


🎜>

 functionHello() {        
alert("Hello");  
}       
Hello( 
}       
Hello();   

我們會看到這樣的結果:連續輸出了兩次Hello World。
而非我們想像中的Hello和Hello World。
這是因為Javascript並非完全的按順序解釋執行,而是在解釋之前會對Javascript進行一次“預編譯”,在預編譯的過程中,會把定義式的函數優先執行,也會把所有var變數創建,預設值為undefined,以提高程式的執行效率。
也就是說上面的一段程式碼其實被JS引擎預先編譯成這樣的形式:
複製程式碼 程式碼如下:

       
varHello = function() {         
Hello = function() {
           alert("Hello World"); 
      }     
Hello(); >我們可以透過上面的程式碼很清楚地看到,其實函數也是數據,也是變量,我們也可以對「函數「進行賦值(重賦值)。
當然,我們為了防止這樣的情況,也可以這樣:



複製代碼


代碼如下:alert("Hello World");    
} 🎜>
這樣,程式被分成了兩段,JS引擎也就不會把他們放在一起了。   

當JavaScript引擎解析腳本時,它會在預編譯期對所有宣告的變數和函數進行處理。

做如下處理:

1. 在執行前會進行類似「預編譯」的操作:首先會建立一個目前執行環境下的活動對象,並將那些以var申明的變數設為活動對象的屬性,但是此時這些變數的賦值都是undefined,並將那些以function定義的函數也加入為活動物件的屬性,而且它們的值正是函數的定義。

2. 在解釋執行階段,遇到變數需要解析時,會先從目前執行環境的活動物件中查找,如果沒有找到且執行環境的擁有者有prototype屬性時則會從prototype鏈中尋找,否則將會依照作用域鏈來尋找。遇到var a = ...這樣的語句時會給對應的變數賦值(注意:變數的賦值是在解釋執行階段完成的,如果在這之前使用變量,它的值會是undefined) 所以,就會出現當JavaScript解釋器執行下面腳本時不會報錯:




複製碼

複製碼

代碼如下:

      // 回傳值undefined

var a =1; alert(a);                            //使用值與值 1
由於變數宣告是在預編譯期被處理的,所以在執行期間對於所有程式碼來說,都是可見的。但是,你也會看到,執行上面程式碼,提示的值是undefined,而不是1。這是因為,變數初始化過程發生在執行期,而不是預編譯期。在執行期,JavaScript解釋器是按著程式碼先後順序進行解析的,如果在前面程式碼行中沒有為變數賦值,則JavaScript解釋器會使用預設值undefined。由於在第二行中為變數a賦值了,所以在第三行程式碼中會提示變數a的值為1,而不是undefined。

同理,下面範例在函數宣告前呼叫函數也是合法的,並且能夠被正確解析,所以傳回值為1。

複製碼

代碼如下:

             // 呼叫函數,並傳回值1

function f(){

    alert(1);

}

但是,如果如下定義函數,則JavaScript解釋器會提示語法錯誤。
複製碼

複製碼

代碼如下:


             // 呼叫函數,並回傳語法錯誤

var f = function(){

    alert(1);

}


複製代碼

代碼如下:

/*在預編譯過程中func是window環境下的活動物件中的屬性,值是函數,覆蓋了undefined值*/

alert(func); //function func(){alert("hello!")}

var func = "this is a variable"

function func(){

alert("hello!")

}

/*在執行過程中遇到了var重新賦值為"this is a variable"*/

alert(func);  //this is a variable



複製代碼


代碼如下:


alert(name);  //JSF }

func();

alert(name);

//feng

雖然變數和函數宣告可以在文件任意位置,但是良好的習慣應該是在所有JavaScript程式碼之前宣告全域變數和函數,並對變數進行初始化賦值。在函數內部也是先宣告變量,然後再引用。

1.3  以區塊執行JavaScript程式碼 所謂程式碼區塊就是使用<script>標籤分隔的程式碼段。例如,下面兩個<script>標籤分別代表兩個JavaScript程式碼區塊。 </script>

複製程式碼

程式碼如下:

<script></script>

// JavaScript程式碼區塊1

var a =1;

<script></script>

// JavaScript程式碼區塊2

function f(){

    alert(1); }

JavaScript解釋器在執行腳本時,是按區塊來執行的。通俗地說,就是瀏覽器在解析HTML文件流程時,如果遇到一個<script>標籤,則JavaScript解釋器會等到這個程式碼區塊都載入完後,先對程式碼區塊進行預編譯,然後再執行。執行完畢後,瀏覽器會繼續解析下面的HTML文件流,同時JavaScript解釋器也準備好處理下一個程式碼區塊。 </script>

由於JavaScript是按區塊執行的,所以如果在一個JavaScript區塊中呼叫後面區塊中宣告的變數或函數就會提示語法錯誤。例如,當JavaScript解釋器執行下面程式碼時就會提示語法錯誤,顯示變數a未定義,而物件f找不到。

複製程式碼 程式碼如下:

<script> <P>// JavaScript程式碼區塊1 <P>alert(a); <P>f(); <P></script>

<script></script>

// JavaScript程式碼區塊2

var a =1;

function f(){

    alert(1);

}


雖然說,JavaScript是按區塊執行的,但是不同區塊都屬於同一個全域作用域,也就是說,區塊之間的變數和函數是可以共享的。

1.4  利用事件機制改變JavaScript執行順序

由於JavaScript是按區塊處理程式碼,同時又遵循HTML文件流程的解析順序,所以在上面範例中會看到這樣的語法錯誤。但是當文檔流加載完畢,如果再次訪問就不會出現這樣的錯誤。例如,把存取第2塊程式碼中的變數和函數的程式碼放在頁面初始化事件函數中,就不會出現語法錯誤了。

複製程式碼 程式碼如下:

<script> <P>// JavaScript程式碼區塊1 <P>window.onload = function(){        // 頁面初始化事件處理函數 <P>    alert(a); <P>    f(); <P>} <P></script>

<script></script>

// JavaScript程式碼區塊2

var a =1;

function f(){

    alert(1);

}


為了安全起見,我們一般在頁面初始化完畢之後才允許JavaScript程式碼執行,這樣可以避免網速對JavaScript執行的影響,同時也避開了HTML文檔流對於JavaScript執行的限制。

注意

如果在一個頁面中存在多個windows.onload事件處理函數,則只有最後一個才是有效的,為了解決這個問題,可以把所有腳本或呼叫函數都放在同一個onload事件處理函數中,例如:

複製程式碼 程式碼如下:

window.onload = function(>

window.onload = function(>

window.onload = function(>

window.onload = function(>

window.onload = function(){

    f1();

    f2();

    f3();

}

而且透過這種方式可以改變函數的執行順序,方法是:簡單地調整onload事件處理函數中呼叫函數的排列順序。

除了頁面初始化事件外,我們還可以透過各種互動事件來改變JavaScript程式碼的執行順序,如滑鼠事件、鍵盤事件及時脈觸發器等方法,詳細講解請參閱第14章的內容。

1.5  JavaScript輸出腳本的執行順序 在JavaScript開發中,常會使用document物件的write()方法輸出JavaScript腳本。那麼這些動態輸出的腳本是如何執行的呢?例如:

複製程式碼

程式碼如下:

document.write('

document.write('f();');

document.write('function f(){'); document.write('alert(1);'); document.write('}'); document.write('');
執行上面程式碼,我們會發現:document.write()方法先把輸出的腳本字串寫入到腳本所在的文件位置,瀏覽器在解析完document.write()所在文件內容後,繼續解析document.write()輸出的內容,然後才依序解析後面的HTML文件。也就是說,JavaScript腳本輸出的程式碼字串會在輸出後馬上執行。

請注意,使用document.write()方法輸出的JavaScript腳本字串必須放在同時被輸出的<script>標籤中,否則JavaScript解釋器因為無法識別這些合法的JavaScript程式碼,而作為普通的字串顯示在頁面文件中。例如,下面的程式碼就會把JavaScript程式碼顯示出來,而不是執行它。 </script>

複製程式碼 程式碼如下:

document.write('f();');

document.write('function f(){');

document.write('alert(1);');

document.write(');');

但是,透過document.write()方法輸出腳本並執行也存在一定的風險,因為不同JavaScript引擎對其執行順序不同,同時不同瀏覽器在解析時也會出現Bug。

Ø 問題一,找不到透過document.write()方法匯入的外部JavaScript檔案中宣告的變數或函數。例如,看下面範例程式碼。

複製程式碼 程式碼如下:

document.write('');

document.write('

document.write('alert(n);');  // IE提示找不到變數n

document.write('');

alert(n 1);                          // 所有瀏覽器所提示且找不到變數於n

外部JavaScript文件(test.js)的代碼如下:

複製代碼 代碼如下:

var n = 1;

分別在不同瀏覽器中進行測試,會發現提示語法錯誤,找不到變數n。也就是說,如果在JavaScript程式碼區塊中存取本程式碼區塊中使用document.write()方法輸出的腳本中匯入的外部JavaScript檔案所包含的變量,則會顯示語法錯誤。同時,如果在IE瀏覽器中,不僅在腳本中,而且在輸出的腳本中也會提示找不到輸出的導入外部JavaScript文件的變數(表述有點長和繞,不懂的讀者可以嘗試運行上面代碼即可明白)。

Ø 問題二,不同JavaScript引擎對輸出的外部導入腳本的執行順序略有不同。例如,看下面範例程式碼。

複製代碼 代碼如下:

');

document.write('

document.write('alert(2);')

document.write('alert(n 2);');

document.write('');

alert(n 3);



外部JavaScript檔案(test1.js)的程式碼如下所示。
複製程式碼 程式碼如下:

var n = 1;

alert(n);

在IE瀏覽器中的執行順序如圖1-6所示。

JavaScript執行順序詳細介紹_基礎知識

圖1-6  IE 7瀏覽器的執行順序和提示的語法錯誤

在符合DOM標準的瀏覽器中的執行順序與IE瀏覽器不同,且沒有語法錯誤,如圖1-7所示的是在Firefox 3.0瀏覽器中的執行順序。

JavaScript執行順序詳細介紹_基礎知識

圖1-7  Firefox 3瀏覽器的執行順序和提示的語法錯誤

解決不同瀏覽器存在的不同執行順序,以及可能存在Bug。我們可以把凡是使用輸出腳本導入的外部文件,都放在獨立的程式碼區塊中,這樣根據上面介紹的JavaScript程式碼區塊執行順序,就可以避免這個問題。例如,針對上面範例,可以這樣設計:

複製代碼 代碼如下:

');

document.write('

document.write('alert(2);') ; // 提示2

document.write('alert(n 2);'); // 提示3

document.write('');

alert(n 3); // 提示4

alert(n 4); // 提示5

這樣在不同瀏覽器中都能夠依序執行上面程式碼,且輸出順序都是1、2、3、4和5。有問題的原因是:輸出導入的腳本與目前JavaScript程式碼區塊之間的矛盾。如果單獨輸出就不會發生衝突了。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn