現代的HTML網頁常會連結大量的「腳本(Script)」來讓網頁擁有各式各樣的功能。腳本功能固然強大,但若使用不當,很容易造成網頁的載入時間變長,影響使用者體驗。



script標籤放置位置的影響

網頁瀏覽器在開啟HTML網頁的時候,會一邊解析HTML語法,一邊執行解析HTML後所得到的腳本。例如以下網頁,會逐漸在畫面上秀出123,每個數字之間都有一個alert函數擋著。

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
        <script>alert('Hello');</script>
    </head>
    <body>
        1
        <script>
            alert('Nah');
        </script>
        2
        <script>
           alert('Nah');
        </script>
        3
    </body>
</html>

也就是說,如果有在腳本中作「尋找元素」的動作的話,就要注意腳本的位置是否是置於該元素出現之後。例如以下網頁,數字3無法被腳本修改為33

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <div id="a">
            1
        </div>
        <div id="b">
            2
        </div>
        <script>
           document.getElementById('a').innerHTML = '11';
           document.getElementById('b').innerHTML = '22';
           document.getElementById('c').innerHTML = '33';
        </script>
        <div id="c">
            3
        </div>
    </body>
</html>

script標籤可以搭配src屬性,讓腳本能從成獨立的檔案讀入。例如以上網頁,可以修改如下:

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <div id="a">
            1
        </div>
        <div id="b">
            2
        </div>
        <script src="index.js"></script>
        <div id="c">
            3
        </div>
    </body>
</html>
document.getElementById('a').innerHTML = '11';
document.getElementById('b').innerHTML = '22';
document.getElementById('c').innerHTML = '33';

以上網頁中,當網頁瀏覽器從src讀取外部的腳本時,它會先暫停當下HTML文件的解析,等到外部的遠端腳本下載好並執行完之後,才會繼續解析HTML。流程如下圖:

html-script-async-defer

defer屬性

script標籤加上defer屬性,網頁瀏覽器在從src讀取外部的腳本時,會繼續HTML文件的解析,直到HTML解析完後,才會回頭按照出現順序執行這些被加上defer屬性的腳本,如果該腳本此時還沒被讀取完就會繼續等待它被讀取完才會執行它。流程如下圖:

html-script-async-defer

例如以上網頁,可以再修改如下:

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <div id="a">
            1
        </div>
        <div id="b">
            2
        </div>
        <script src="index.js" defer></script>
        <div id="c">
            3
        </div>
    </body>
</html>

修改之後,以上網頁所使用的JavaScript腳本就不會有數字3無法被修改為33的問題了!

這邊要注意的是,defer屬性只能和src一同使用。如果是想要讓HTML內嵌的腳本擁有defer屬性的話是沒有效果的哦!

async屬性

script標籤加上async屬性,網頁瀏覽器在從src讀取外部的腳本時,會繼續HTML文件的解析,一旦外部的腳本讀取完,它會立刻被執行,執行腳本時會暫停解析HTML。流程如下圖:

html-script-async-defer

例如以上網頁,可以再修改如下:

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <div id="a">
            1
        </div>
        <div id="b">
            2
        </div>
        <script src="index.js" async></script>
        <div id="c">
            3
        </div>
    </body>
</html>

修改之後,以上網頁的數字3就不一定會被修改為33了!要看那個id為cdiv元素是否能夠搶在腳本執行前被解析建立出來。

defer + async屬性?

script標籤可以同時加上deferasync屬性。一般來說,這樣寫的話只會有async屬性的效果,但這樣可以讓不支援async屬性的舊版網頁瀏覽器依然能使該腳本擁有defer屬性的功能,使得網頁在開啟時不至於太慢。

所以哪個方式好?要加哪個屬性?還是都不加?

單以網頁載入速度來看,當然是defer屬性最為優秀。只不過當我們把所有外部腳本都加上defer屬性時,那麼網頁在載入完成之後,才會去執行這些腳本,若這些腳本會直接影響到畫面的話,那麼使用者會先看到爛掉的網頁畫面,並在網頁載入完成且腳本執行完之後,網頁畫面才能正常呈現。

也就是說,我們在選擇要替腳本加上哪個屬性時,應該先從defer屬性開始考慮,如果它直接會影響到畫面,改用async屬性會比較好一點。不過async屬性的腳本並沒有順序性,所以還是得視情況與無async屬性和defer屬性的腳本搭配使用。