W3C DOM 簡介


W3C 聯合各瀏覽器廠商制訂了標準物件模型,試圖讓各瀏覽器廠商遵合此一模型進行實作,以解決各瀏覽器間物件模型不一致的問題,在新的物件模型中,也對文件操作的功能加以擴充。

簡單來說,在 DOM 的標準下,一份文件中所有的標籤定義,包括文字,都是一個物件,這些物件以文件定義的結構,形成了一個樹狀結構。例如:

<html>
    <head>
        <title>首頁</title>
    </head>
    <body>
        <h1>Hello!World!</h1>
        <a href="Gossip/index.html">學習筆記</a>
    </body>
</html>

這份 HTML 文件,會形成以下樹狀的物件結構:

document                             (Document)
       |-html                        (HTMLHtmlElement)
            |-head                   (HTMLHeadElement)
            |    |-title             (HTMLTitleElement)
            |          |-首頁        (Text)
            |
            |body                    (HTMLBodyElement)
                 |-h1                (HTMLHeadingElement
                 |  |-Hello!World!   (Text)
                 |
                 |-a                 (HTMLAnchorElement)
                   |-學習筆記         (Text)

上圖右邊的括號,表示每個物件的型態。注意,document 代表整個文件,而不代表 html 標籤節點,你可以使用 document.childNodes[0] 取得 html 標籤 DOM 元素,childNodes 表示取得子節點,取回的會是 NodeList 物件,是個類陣列物件,可使用索引值來指定取得某個子節點。

方便的 document.documentElement 也可用來取得 html 標籤 DOM 元素。如果想取得 body 標籤 DOM 元素,也可以透過 document.body 來取得。注意,文字也會形成樹狀結構中的元素。

儘管你在上面看到的元素形態,有許多都帶有 HTML 字眼,但 DOM 並非專屬於 HTML 的物件模型,DOM API 分為兩部份,一個是核心 DOM API,一個是 HTML DOM API。

核心 API 是一個獨立的規範,可以任何語言實現,可操作的對象是基於 XML 的任何文件,你可以在〈XML DOM Tutorial〉找到 DOM 核心 API 的相關資料。

HTML API 是 核心 API 的延伸,專門操作 HTML,各種物件對應的形態,通常會有個 HTML 字眼在前頭,你可以在〈JavaScript and HTML DOM Reference〉找到 HTML DOM API 的相關資料。

核心 API 文件中所有內容都視為節點,包括文件本身,再依類型區分出不同的形態:

Node
   |Document
   |Element
   |Text
   |Attr
   ...

Document 代表整份文件,Element 是所有標籤(也是節點),Text 代表文字元素(也是節點)。

Level 0 DOM 在 window 物件上有 navigatorlocationframesscreenhistory 等與瀏覽器相關的物件,與文件相關的物件,實際上只有 document,功能也有限,這個部份納入了 DOM 標準,成為了 DOM 的子集,由於這些舊式 API 都是專屬於 HTML,所以你在〈Document〉的文件上看不到相關操作,而必須在〈The HTML DOM Document Object〉這個專屬於 HTML 的 DOM API 文件上才可以找到。

同樣地,Element 僅定義核心 DOM API 中元素的操作,而 HTMLHeadElement 等元素也有一些專屬 HTML 的 API,這在〈Element〉文件中找不到,而必須在〈The HTML DOM Document Object〉等相關類型中尋找。

每個節點都會有 nodeNamenodeType 特性,前者可以取得節點的名稱,後者可以取得節點型態常數,這個常數用來查找對應的型態名稱,常數與型態名稱的對照可在〈HTML DOM nodeType Property
Element Object
〉文件中找到。

舉例來說,可以搭配 JavaScript,如下顯示出一個網頁的節點與型態:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>首頁</title>
    </head>
    <body>
        <h1>Hello!World!</h1>
        <a href="https://openhome.cc/Gossip/">學習筆記</a>
        <div id="console"></div>

<script type="text/javascript">
    let typeNames = new Map([
        [Node.ELEMENT_NODE, 'ELEMENT_NODE'],
        [Node.ATTRIBUTE_NODE, 'ATTRIBUTE_NODE'],
        [Node.TEXT_NODE, 'TEXT_NODE'],
        [Node.CDATA_SECTION_NODE, 'CDATA_SECTION_NODE'],
        [Node.ENTITY_REFERENCE_NODE, 'ENTITY_REFERENCE_NODE'],
        [Node.ENTITY_NODE, 'ENTITY_NODE'],
        [Node.PROCESSING_INSTRUCTION_NODE, 'PROCESSING_INSTRUCTION_NODE'],
        [Node.COMMENT_NODE, 'COMMENT_NODE'],
        [Node.DOCUMENT_NODE, 'DOCUMENT_NODE'],
        [Node.DOCUMENT_TYPE_NODE, 'DOCUMENT_TYPE_NODE'],
        [Node.DOCUMENT_FRAGMENT_NODE, 'DOCUMENT_FRAGMENT_NODE'],
        [Node.NOTATION_NODE, 'NOTATION_NODE']
    ]);

    function subNodesOf(parent, indent = '  ') {
        let nodes = parent.childNodes;
        let nodeDesc = Array.from(nodes).reduce((nodeDesc, node) => {
            let nodeName = node.nodeName;
            let typeName = typeNames.get(node.nodeType);
            return nodeDesc + `${indent} ${nodeName} ${typeName} <br>` + 
                   subNodesOf(node, `  ${indent}`);
        }, '');

        return nodeDesc;
    }

    let nodeName = document.nodeName;
    let typeName = typeNames.get(document.nodeType);
    document.getElementById('console').innerHTML = 
        `${nodeName} ${typeName} <br>` + subNodesOf(document);

</script>

    </body>
</html>

按此觀看結果