National Yang Ming Chiao Tung University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

陽明交大 AQI 69 24°C PM2.5 26
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 02

在網頁中使用 script

script 標籤、載入順序、defer 與第一次操作網頁
Interaction 15 minute(s)

Learning Objectives

  • 用 script 標籤在網頁中放入 JavaScript,分辨內嵌程式與外部 .js 檔
  • 說明 DOM 是網頁的樹狀結構,JavaScript 透過 document 存取網頁
  • 說明 script 放的位置為什麼會影響能不能取得元素,並用 defer 或 DOMContentLoaded 解決
  • 用 createElement、setAttribute 與 getElementById 動態新增並取得網頁元素

script 標籤:把 JavaScript 放進網頁

瀏覽器讀到 HTML 裡的 <script> 標籤,就會執行裡面的 JavaScript。有兩種寫法:

<!-- 1. 內嵌:程式直接寫在標籤裡 -->
<script>
  console.log('Hello');
</script>

<!-- 2. 外部檔案:程式寫在 app.js,用 src 指定路徑 -->
<script src="js/app.js"></script>

實際的專案大多用外部檔案:

  • HTML 和 JavaScript 分開,各自比較好維護
  • 很多頁面可以共用同一支 .js 檔
  • 瀏覽器會快取(cache)下載過的檔案,換頁時不用重新下載

兩個要注意的地方:有 src 的 <script>,標籤裡面寫的程式會被忽略,兩種寫法不要混用; </script> 結束標籤不能省略,不能寫成 <script src="app.js" />。

你在舊教材可能會看到 document.write('…') 直接把文字寫進網頁。它在網頁載入完之後呼叫會把整頁內容清掉,現在已經不建議使用; 這一課後面會用 createElement 新增內容。

DOM:網頁的樹狀結構

瀏覽器讀進 HTML 之後,會建立一棵由節點組成的樹,叫做 DOM(Document Object Model,文件物件模型)。 DOM 是 HTML 文件的程式介面:JavaScript 不是去改 HTML 原始碼,而是讀取、修改這棵樹,樹一改,畫面就跟著改。

<html>
  <head><title>我的網頁</title></head>
  <body>
    <h1>標題</h1>
    <p id="intro">一段文字</p>
  </body>
</html>
document
└── html
    ├── head
    │   └── title ── 「我的網頁」
    └── body
        ├── h1 ── 「標題」
        └── p#intro ── 「一段文字」
  • document 是整棵樹的根,JavaScript 存取網頁都從它開始,例如 document.getElementById('intro') 取得 id 為 intro 的元素
  • 找不到符合的元素時,getElementById 回傳 null
  • DOM 是瀏覽器都要遵守的標準,和程式語言無關;JavaScript 是最常用來操作 DOM 的語言

DOM 的完整操作(各種選取方法、新增、刪除元素)在 j15 介紹,這一課只先看一個重要的問題:script 什麼時候執行。

script 放的位置會影響結果

瀏覽器由上而下解析 HTML,一邊讀一邊建立 DOM。遇到一般的 <script> 時,會停下來先執行它,執行完才繼續往下讀。

所以,如果 <script> 寫在某個元素的前面,執行的當下那個元素還沒被建立,getElementById 就會得到 null; 接著寫 null.textContent = '…' 就會出現 TypeError: Cannot set properties of null。這是初學者最常遇到的錯誤之一。

解決方法有三種:

  1. 把 <script> 放在 </body> 的前面,也就是所有元素的後面
  2. 外部檔案加上 defer:<script src="app.js" defer></script>,可以放在 <head> 裡(下一段說明)
  3. 等網頁讀完再執行:把程式包在 document.addEventListener('DOMContentLoaded', () => { … }) 裡面。 DOMContentLoaded 事件在整份 HTML 解析完、DOM 建立好時觸發(事件在 j19 詳細介紹)

下面的沙箱裡,HTML 分頁有兩段內嵌的 <script>,JavaScript 分頁的程式則會放在 body 的最後面執行。

互動練習:script 的位置與 null

先看主控台:第一段 script 寫在 <p> 前面,取得的是 null;第二段在 <p> 後面,取得了 <p#title>。這一題要改的是 HTML 分頁(JavaScript 分頁是唯讀的,用來對照)。每次改完按「執行」,注意主控台三行輸出的順序。

defer 與 async

外部檔案的 <script src> 可以加上兩個屬性,改變下載與執行的時機:

寫法 下載 執行時機 多個檔案的執行順序
<script src="a.js"> 停下解析 HTML,先下載 下載完立刻執行,執行完才繼續解析 依照在 HTML 裡的順序
<script src="a.js" defer> 和解析 HTML 同時進行 HTML 全部解析完之後、DOMContentLoaded 之前 依照在 HTML 裡的順序
<script src="a.js" async> 和解析 HTML 同時進行 下載完就立刻執行(會暫停解析) 不保證,誰先下載完誰先執行
  • 需要操作 DOM、或檔案之間有先後依賴的程式(例如先載入函式庫再載入自己的程式),用 defer
  • 和頁面內容無關、獨立運作的程式(例如流量統計),可以用 async
  • defer、async 只對有 src 的外部檔案有效,寫在內嵌的 <script> 上沒有作用
  • <script type="module" src="main.js">(ES 模組,j29 介紹)預設就是 defer 的行為

所以現在常見的寫法是把 <script src="app.js" defer></script> 放在 <head> 裡:瀏覽器可以早一點開始下載,又能等 DOM 建立好才執行。

想一想:script 的位置與載入方式

1.

下面的 HTML,主控台會印出什麼?

<script>
  console.log(document.getElementById('msg'));
</script>
<p id="msg">Hi</p>
2.

寫了 <script src="app.js">console.log('A');</script>,app.js 裡是 console.log('B');,主控台會印出什麼?

3.

<head> 裡依序有 <script src="lib.js" defer> 和 <script src="app.js" defer>,app.js 會用到 lib.js 的函式。下列哪一個描述正確?

4.

下列哪一種寫法,defer 沒有任何作用?

第 1 次作答

第一次動手改網頁:新增標籤與屬性

用 JavaScript 新增一個標籤分成三步:建立元素 → 放入內容 → 放進 DOM 樹。

方法 作用
document.createElement('h1') 建立一個 <h1> 元素(還不在網頁上)
document.createTextNode('Hello') 建立一個文字節點
父元素.appendChild(子節點) 把子節點放到父元素的最後面,例如 document.body.appendChild(h1)
元素.setAttribute('id', 'red') 設定屬性,相當於 HTML 的 <h1 id="red">
元素.getAttribute('id') 讀取屬性的值;沒有這個屬性時回傳 null
元素.hasAttribute('id') 有沒有這個屬性,回傳 true 或 false

HTML 標籤常見的屬性:所有元素都能用的全域屬性,例如 id(同一頁唯一)、class(和 CSS 搭配)、style(行內樣式)、hidden(隱藏元素); 表單元素的 name、value;<form> 的 action、method。

取得元素之後,也可以直接讀寫一些屬性:el.id、el.className、el.tagName(大寫的標籤名稱)、el.style.color = 'red'。 投影片寫的 createElement("h1").setAttribute(…) 連在一起,得到的是 setAttribute 的回傳值 undefined,不是元素;要先把元素存進變數,再分開呼叫。

互動練習:動態新增標籤與屬性

投影片 LAB02:起始程式已經建立了 <h1> 並放入文字,但網頁上還看不到它。依序完成任務,每次改完按「執行」,看右邊的 DOM 樹多了什麼。

想一想:新增與取得元素

1.

執行 const h = document.createElement('h2'); h.textContent = '新標題'; 之後,網頁上看得到「新標題」嗎?

2.

<input id="name" type="text">,執行 document.getElementById('name').getAttribute('value') 會得到什麼?

3.

頁面上有三個 id="h1" 的元素(id 重複了),document.getElementById('h1') 會得到什麼?

第 1 次作答

重點精華回顧

  • <script> 可以內嵌程式,也可以用 src 載入外部 .js 檔;有 src 時標籤裡的程式會被忽略。
  • DOM 是網頁的樹狀結構,document 是根;JavaScript 透過 DOM 讀取與修改網頁。
  • 瀏覽器由上而下解析 HTML,一般的 script 會立刻執行;寫在元素前面的 script 取得元素會得到 null。
  • 解決方法:script 放在 body 最後、外部檔案加 defer、或在 DOMContentLoaded 之後再執行。
  • defer 等 HTML 解析完依序執行;async 下載完就執行、不保證順序;兩者都只對外部檔案有效。
  • createElement 建立元素、appendChild 放進樹裡、setAttribute/getAttribute/hasAttribute 處理屬性。
這一課完成了!
Next lesson:變數宣告:var、let、const
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。