DOM 指令碼簡介

在編寫網頁和應用程式時,最常見的操作之一就是以某種方式更改文件結構。這通常透過一組內建瀏覽器 API 來操作文件物件模型(DOM),以控制 HTML 和樣式資訊。在本文中,我們將向你介紹 DOM 指令碼

預備知識 瞭解 HTMLCSS 基礎,熟悉前面課程中介紹的 JavaScript 基礎。
學習成果
  • DOM 是什麼 — 瀏覽器將文件的 HTML 結構表示為物件層次結構的內部表示。
  • Web 瀏覽器在 JavaScript 中表示的重要部分 — NavigatorWindowDocument
  • DOM 節點在 DOM 樹中彼此相對存在的方式 — 根、父、子、同級和後代。
  • 獲取 DOM 節點的引用,建立新節點,新增和移除節點及屬性。
  • 使用 JavaScript 操作 CSS 樣式。

Web 瀏覽器重要組成部分

Web 瀏覽器是非常複雜的軟體,包含許多活動部件,其中許多都無法由 Web 開發人員使用 JavaScript 進行控制或操作。你可能會認為這些限制是壞事,但瀏覽器之所以受限是有充分理由的,主要圍繞安全性。想象一下,如果一個網站可以訪問你儲存的密碼或其他敏感資訊,並像你一樣登入網站會怎樣?

儘管存在限制,Web API 仍然為我們提供了許多功能,使我們能夠對網頁進行許多操作。在你的程式碼中,有一些非常明顯的部分會經常引用 — 請看下面的圖表,它表示瀏覽器中直接參與檢視網頁的主要部分

Important parts of web browser; the document is the web page. The window includes the entire document and also the tab. The navigator is the browser, which includes the window (which includes the document) and all other windows.

  • window 表示載入網頁的瀏覽器選項卡;這在 JavaScript 中由 Window 物件表示。使用此物件上可用的方法,你可以執行諸如返回視窗大小(參見 Window.innerWidth事件處理程式 附加到當前視窗等等操作。
  • navigator 表示瀏覽器在 Web 上的狀態和身份。在 JavaScript 中,這由 Navigator 物件表示。你可以使用此物件檢索使用者偏好語言、使用者網路攝像頭媒體流等資訊。
  • document(在瀏覽器中由 DOM 表示)是載入到視窗中的實際頁面,在 JavaScript 中由 Document 物件表示。你可以使用此物件返回和操作構成文件的 HTML 和 CSS 資訊,例如獲取 DOM 中元素的引用、更改其文字內容、對其應用新樣式、建立新元素並將其作為子元素新增到當前元素,甚至完全刪除它。

在本文中,我們將主要關注文件操作,但我們也會展示一些其他有用的部分。

文件物件模型

讓我們簡要回顧一下文件物件模型(DOM),我們之前在本課程中也介紹過它。每個瀏覽器選項卡中當前載入的文件都由一個 DOM 表示。這是一個由瀏覽器建立的“樹結構”表示,它使程式語言可以輕鬆訪問 HTML 結構 — 例如,瀏覽器本身使用它在渲染頁面時將樣式和其他資訊應用於正確的元素,而像你這樣的開發人員可以在頁面渲染後使用 JavaScript 操作 DOM。

注意:Scrimba 的 文件物件模型 MDN 學習夥伴 提供了一個方便的“DOM”術語及其含義的講解。

我們建立了一個示例頁面,位於 dom-example.html也可以線上檢視)。嘗試在瀏覽器中開啟它 — 這是一個非常簡單的頁面,包含一個 <section> 元素,其中有一個影像和一個帶連結的段落。HTML 原始碼如下所示

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>Simple DOM example</title>
  </head>
  <body>
    <section>
      <img
        src="dinosaur.png"
        alt="A red Tyrannosaurus Rex: A two legged dinosaur
        standing upright like a human, with small arms, and a
        large head with lots of sharp teeth." />
      <p>
        Here we will add a link to the
        <a href="https://www.mozilla.org/">Mozilla homepage</a>
      </p>
    </section>
  </body>
</html>

另一方面,DOM 看起來是這樣的

Tree structure representation of Document Object Model: The top node is the doctype and HTML element. Child nodes of the HTML include head and body. Each child element is a branch. All text, even white space, is shown as well.

注意:此 DOM 樹圖是使用 Ian Hickson 的 Live DOM viewer 建立的。

樹中的每個條目都稱為一個節點。你可以從上面的圖中看到,有些節點表示元素(標識為 HTMLHEADMETA 等),有些節點表示文字(標識為 #text)。還有其他型別的節點,但這些是你會遇到的主要型別。

節點也按其在樹中相對於其他節點的位置進行引用

  • 根節點:樹的頂部節點,在 HTML 中始終是 HTML 節點(SVG 和自定義 XML 等其他標記詞彙將具有不同的根元素)。
  • 子節點直接位於另一個節點內部的節點。例如,在上面的示例中,IMGSECTION 的子節點。
  • 後代節點任意位置位於另一個節點內部的節點。例如,在上面的示例中,IMGSECTION 的子節點,它也是一個後代節點。IMG 不是 BODY 的子節點,因為它在樹中位於 BODY 下兩級,但它是 BODY 的後代節點。
  • 父節點:內部包含另一個節點的節點。例如,在上面的示例中,BODYSECTION 的父節點。
  • 同級節點:在 DOM 樹中位於同一級別、同一父節點下的節點。例如,在上面的示例中,IMGP 是同級節點。

在操作 DOM 之前熟悉這些術語很有用,因為你會遇到許多程式碼術語都使用它們。你也會在 CSS 中遇到它們(例如,後代選擇器、子選擇器)。

進行一些基本的 DOM 操作

為了開始學習 DOM 操作,讓我們從一個實際示例開始。

  1. 獲取 dom-example.html 頁面 及其附帶的影像的本地副本。

  2. 在結束的 </body> 標籤之前新增一個 <script></script> 元素。

  3. 要操作 DOM 中的元素,首先需要選擇它並將其引用儲存在變數中。在你的指令碼元素中,新增以下行

    js
    const link = document.querySelector("a");
    
  4. 現在我們已經將元素引用儲存在一個變數中,我們可以開始使用它可用的屬性和方法來操作它(這些屬性和方法定義在像 HTMLAnchorElement 這樣的介面上,對於 <a> 元素,其更通用的父介面是 HTMLElement,以及代表 DOM 中所有節點的 Node)。首先,讓我們透過更新 Node.textContent 屬性的值來更改連結內部的文字。在上一行下面新增以下行

    js
    link.textContent = "Mozilla Developer Network";
    
  5. 我們還應該更改連結指向的 URL,這樣在點選時就不會跳轉到錯誤的地方。再次在底部新增以下行

    js
    link.href = "https://mdn.club.tw";
    

請注意,與 JavaScript 中的許多事情一樣,有許多方法可以選擇元素並將其引用儲存在變數中。Document.querySelector() 是推薦的現代方法。它很方便,因為它允許你使用 CSS 選擇器選擇元素。上面的 querySelector() 呼叫將匹配文件中出現的第一個 <a> 元素。如果你想匹配並對多個元素執行操作,可以使用 Document.querySelectorAll(),它會匹配文件中與選擇器匹配的每個元素,並將它們的引用儲存在一個類似 陣列 的物件中,稱為 NodeList

還有一些獲取元素引用的舊方法,例如

  • Document.getElementById(),它選擇具有給定 id 屬性值的元素,例如 <p id="myId">我的段落</p>。ID 作為引數傳遞給函式,即 const elementRef = document.getElementById('myId')
  • Document.getElementsByTagName(),它返回一個類似陣列的物件,其中包含頁面上給定型別的所有元素,例如 <p>s、<a>s 等。元素型別作為引數傳遞給函式,即 const elementRefArray = document.getElementsByTagName('p')

這兩種方法在舊瀏覽器中比 querySelector() 等現代方法效果更好,但不如現代方法方便。看看你還能找到什麼其他方法!

建立和放置新節點

上面只是讓你嚐到了可以做些什麼,但讓我們更進一步,看看如何建立新元素。

  1. 回到當前示例,讓我們從獲取 <section> 元素的引用開始 — 在你現有指令碼的底部新增以下程式碼(其他行也一樣)

    js
    const sect = document.querySelector("section");
    
  2. 現在我們使用 Document.createElement() 建立一個新段落,並像之前一樣為其新增一些文字內容

    js
    const para = document.createElement("p");
    para.textContent = "We hope you enjoyed the ride.";
    
  3. 你現在可以使用 Node.appendChild() 將新段落追加到部分的末尾

    js
    sect.appendChild(para);
    
  4. 最後,對於這部分,讓我們向連結所在的段落新增一個文字節點,以使句子圓滿。首先,我們將使用 Document.createTextNode() 建立文字節點

    js
    const text = document.createTextNode(
      " — the premier source for web development knowledge.",
    );
    
  5. 現在我們將獲取連結所在的段落的引用,並將文字節點附加到它

    js
    const linkPara = document.querySelector("p");
    linkPara.appendChild(text);
    

這就是將節點新增到 DOM 所需的大部分內容 — 在構建動態介面時,你會大量使用這些方法(我們稍後會看一些示例)。

移動和刪除元素

有時你可能希望移動節點,或者完全從 DOM 中刪除它們。這完全是可能的。

如果我們要將包含連結的段落移動到部分的底部,我們可以這樣做

js
sect.appendChild(linkPara);

這會將段落移動到部分的底部。你可能認為它會建立第二個副本,但事實並非如此 — linkPara 是該段落唯一副本的引用。如果你想建立副本並也新增它,你需要改用 Node.cloneNode()

刪除節點也相當簡單,至少當你擁有要刪除的節點及其父節點的引用時。在當前情況下,我們只使用 Node.removeChild(),像這樣

js
sect.removeChild(linkPara);

當你只想根據節點自身的引用來刪除節點時(這很常見),你可以使用 Element.remove()

js
linkPara.remove();

此方法在舊版瀏覽器中不受支援。它們沒有方法來告訴節點刪除自身,因此你必須執行以下操作

js
linkPara.parentNode.removeChild(linkPara);

嘗試將以上程式碼行新增到你的程式碼中。

操縱樣式

可以透過多種方式透過 JavaScript 操作 CSS 樣式。

首先,你可以使用 Document.styleSheets 獲取附加到文件的所有樣式表的列表,它返回一個包含 CSSStyleSheet 物件的類陣列物件。然後,你可以根據需要新增/刪除樣式。但是,我們不打算擴充套件這些功能,因為它們是一種有點過時且難以操作樣式的方法。有更容易的方法。

第一種方法是直接在你要動態設定樣式的元素上新增內聯樣式。這透過 HTMLElement.style 屬性完成,該屬性包含文件中每個元素的內聯樣式資訊。你可以設定此物件的屬性以直接更新元素樣式。

  1. 例如,嘗試將這些行新增到我們正在進行的示例中

    js
    para.style.color = "white";
    para.style.backgroundColor = "black";
    para.style.padding = "10px";
    para.style.width = "250px";
    para.style.textAlign = "center";
    
  2. 重新載入頁面,你會看到樣式已應用於段落。如果你在瀏覽器的頁面檢查器/DOM 檢查器中檢視該段落,你會看到這些行確實正在向文件新增內聯樣式

    html
    <p
      style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">
      We hope you enjoyed the ride.
    </p>
    

注意:請注意,CSS 樣式的 JavaScript 屬性版本使用小駝峰命名法,而 CSS 版本使用連字元(烤串式命名法)(例如,backgroundColorbackground-color)。確保不要將它們混淆,否則將無法正常工作。

還有另一種常見的動態操作文件樣式的方法,即將樣式寫入單獨的樣式表,並透過新增/刪除類名來引用這些樣式。

  1. 刪除你之前新增到 JavaScript 中的五行程式碼。

  2. 在你的 HTML <head> 中新增以下內容

    html
    <style>
      .highlight {
        color: white;
        background-color: black;
        padding: 10px;
        width: 250px;
        text-align: center;
      }
    </style>
    
  3. 要將此類名新增到你的元素,請使用元素的 classListadd() 方法

    js
    para.classList.add("highlight");
    
  4. 重新整理你的頁面,你會看到沒有任何變化 — CSS 仍然應用於段落,但這次是透過給它一個由我們的 CSS 規則選擇的類,而不是作為內聯 CSS 樣式。

選擇哪種方法取決於你;兩者都有其優點和缺點。第一種方法設定較少,適用於簡單的用途,而第二種方法更純粹(不混合 CSS 和 JavaScript,沒有內聯樣式,這被認為是一種不良做法)。當你開始構建更大、更復雜的應用程式時,你可能會更多地使用第二種方法,但這真的取決於你。

此時,我們還沒有真正做任何有用的事情!使用 JavaScript 建立靜態內容毫無意義 — 你不如直接將其寫入 HTML 而不使用 JavaScript。它比 HTML 更復雜,並且使用 JavaScript 建立內容還會帶來其他問題(例如無法被搜尋引擎讀取)。

在下一節中,我們將介紹 DOM API 的更實際用途。

注意:你可以在 GitHub 上找到我們 已完成的 dom-example.html 演示(也可以線上檢視)。

建立一個動態購物清單

在此練習中,我們希望你構建一個動態購物清單,允許你使用表單輸入和按鈕新增商品。在你在輸入欄位中鍵入商品並單擊按鈕或按 Enter 鍵後,應發生以下情況

  • 商品應出現在列表中。
  • 每個商品都應該有一個按鈕,可以按下該按鈕從列表中刪除該商品。
  • 每個商品旁邊都應該有一個按鈕,點選後將該商品從列表中刪除。
  • 輸入欄位應被清除並聚焦,為下一個商品輸入做好準備。

完成的演示將如下所示 — 在你構建它之前先嚐試一下!

要完成練習,請按照以下步驟操作,並確保列表的行為如上所述。

  1. 首先,下載我們 shopping-list.html 起始檔案的副本並將其複製到某個位置。你會看到它有一些最小的 CSS、一個帶標籤、輸入和按鈕的表單、一個空列表和一個 <script> 元素。你將把所有新增都放在指令碼中。
  2. 建立三個變數,分別引用列表 (<ul>)、<input><button> 元素。
  3. 建立一個 函式,它將在點選按鈕時執行。
  4. 在函式體內部,首先呼叫 preventDefault()。由於輸入欄位包含在表單元素中,按下 Enter 鍵將觸發表單提交。呼叫 preventDefault() 將阻止表單重新整理頁面,從而可以向列表中新增新專案。
  5. 接下來,將輸入的當前 儲存在一個變數中。
  6. 接下來,透過將其值設定為空字串("")來清除輸入元素。
  7. 建立三個新元素 — 一個列表項 (<li>)、一個 <span> 和一個 <button> — 並將它們儲存在變數中。
  8. 將 span 和 button 作為列表項的子元素追加。
  9. 將 span 的文字內容設定為你之前儲存的輸入值,並將 button 的文字內容設定為 Delete
  10. 將列表項追加到列表中。
  11. 將事件處理程式附加到 刪除 按鈕,以便在點選時,它會刪除整個列表項 (<li>...</li>)。
  12. 最後,使用 focus() 方法將焦點設定到輸入元素,使其準備好輸入下一個購物清單項。

總結

我們已經完成了對文件和 DOM 操作的學習。至此,你應該瞭解 Web 瀏覽器在控制文件和使用者 Web 體驗的其他方面的重要部分。最重要的是,你應該瞭解文件物件模型是什麼,以及如何操作它以建立有用的功能。

另見