byodian's blog

文档对象模型

DOM 和 JavaScript 的关系

JavaScript 通过 DOM 进入和控制文档,而 DOM 利用 Javascript 提供的核心类型和语法实现更多的功能。

一个完整的 JavaScript 实现应该由核心(ECMAScript)、DOM 和 BOM 组成。

Web 浏览器作为 ECMAScript 实现的宿主环境之一,不仅提供了基本的 ECMAScript 实现,同时也提供了核心语言之外的平台特定对象和功能(如浏览器 API、 window对象)。

DOM 对象的数据类型

DOM 节点层次

Document

表示任何在浏览器中载入的网页,并作为网页内容的入口,也就是 DOM 树。它向网页文档本身提供了全局操作功能,能解决如何获取页面的 URL ,如何在文档中创建一个新元素这样的问题。

Node

位于文档内的每个对象都是某种节点,在 HTML 文档中,一个对象可以是元素节点、文本节点或者是元素节点。

Element

HTML DOM API 的 HTMLElement 接口增强元素对象的能力。

NodeList

一个 nodeList 是一格元素列表

DOM 树

HTML 文档是由标签嵌套而成的树形结构,因此 DOM 可以将任何 HTML 描述成一个由多层节点构成的树形结构。根据文档对象模型(DOM),每个 HTML 标签都是一个对象。

节点类型有 12 种,但通常使用最多的只有以下四种节点:

  1. document - 文档的根节点
  2. 元素节点 - HTML 标签元素
  3. 文本节点 - 包含文本
  4. 注释 - 不会显示,可以读取

遍历 DOM

我们在使用 DOM 操作文档元素之前,需要先获取对应的 DOM 对象,把这个对象赋予一个变量,从而方便后续操作。

所有的 DOM 操作都是从 document 对象开始,这是 DOM 的主要入口,利用它可以进入任何节点。

documentElement 和 body

顶部的树节点可以通过 document 属性来使用

<html> = document.documentElement

<body> = document.body

<head> = document.head

子元素

firstChild 和 lastfirst 属性是访问第一个和最后一个子元素的快捷方式。

childNodes 是一个具有 length 属性,类似数组的可迭代对象,可使用 for .. of 语法或者 forEach(..) 方法来迭代它。

// for..of 语法
for (let node of document.body.childNodes) {
console.log(node);
}
// forEach(..) 方法document.body.childNodes.forEach(el => console.log(el));

注意: DOM 集合是只读的,修改节点信息有其他的方法;DOM 集合是实时变化的。

兄弟节点和父节点

在元素中遍历

上面介绍的 childNodesfirstChildlastChildnextSiblingpreviousSibling 属性遍历的是所有的节点,包括文本节点、元素节点和注释节点。

而对于很多任务来说,我们只需要操作元素节点,使用下面的属性可以制作元素节点中导航。

parentNode 和 parentElement 都获取元素的父节点,但有一个例外:

document.documentElement.parentNode; // document
documentdocument.documentElement.parentElement // null

the difference of parentNode and parentElement

不同结果的原因:parentNode 获取的是任何类型的父节点,parentElement 获取的是父元素节点,而 document 形式上作为

的父节点,其并不是一个元素节点,因此才会出现不同的结果。

getElement* 和 querySelector*

matches 和 closest

matches

elem.matches(css) 检查 elem 是否匹配给定的 CSS 选择器,返回 true 或者 false。

<a href="example.com/txt">example.com/txt</a>
<a href="byod.io">byod.io</a>
<script>
const lists = document.querySelectorAll('a');
for (let list of lists) {
if (list.matches('a[href$="txt"]')) {
console.log(list.textContent)
// 'example.com/txt'
}
}
</script>

elem.mathes(css) 常用于事件委托,判断 e.target 是否与给定的 CSS 选择器匹配

<div class="btn-dec">
<button class="search__btn">
<svg class="search__icon">
<use href="img/icons.svg#icon-magnifying-glass"></use>
</svg>
<span>Search</span>
</button>
<button class="sumbit__btn">
<svg class="sumbit__icon">
<use href="img/icons.svg#icon-magnifying"></use>
</svg>
<span>Search</span>
</button>
</div>

<script>document.querySelector('.btn').addEventListener('click', e => {
if (e.target.matches('.search__btn, .search__btn *')) {
// ...code
} else if (e.target.matches('.sumbit__btn, .sumbit__btn *')) {
// ...code
}
});
</script>

closest

elem.closest(css) 会查找与 CSS 选择器匹配的最接近的祖先,包括 elem 自身。

<div class="container">
<ul class="shopping">
<li class="list">list 1</li>
<li class="list">list 2</li>
</ul>
</div>
<script> const li = document.querySelector('.list'); console.log(li.closest('.shopping')); // <ul></script>

elem.closest(css) 也常用于事件委托,判断 e.target 是否与给定的 CSS 选择器匹配

Node

不同的节点或许有不同的属性。

Node tree

DOM 节点是正常的 JavaScript 对象,它们使用基于原型继承属性和方法。

在浏览器控制台使用 console.dir(elem) 可得到 DOM 对象。

console.log(tree) 展示元素的 DOM 树

属性

DOM 节点是正常的 JavaScript 对象。其中有很多内建的属性和方法,我们可以创建自己的属性和方法,也可以修改原型链上的方法。

内建属性

自建属性和方法

document.body.mydata = {
name: 'yo'
title: 'Imperator'
}

document.body.mydata.title // Imperator

document.body.sayTagName = function () {
console.log(this.tagName);
}

document.body.sayTagName(); // BODY

HTML attributes

在 HTML 中,标签可能存在 attributes,当浏览器解析 HTML 为标签创建 DOM 对象时,它会辨别标准 attributes 并为他们创建 DOM 属性。

获取 attributes 值

读取所有的 attributes

elem.attributes

小结

对于大部分场景,使用 DOM 属性是更合适的,只有当 DOM 属性不适合当前场景时,再参考 HTML Attributes。比如:

<a id="a" href="#hello">link</a>
<script>
// attribute
console.log(a.getAttribute('href')); // #hello

// property
console.log(a.href ); // full URL in the form http://site.com/page#hello
</script>
<div id="div" style="color:red;font-size:120%">Hello</div>

<script>
// string
console.log(div.getAttribute('style')); // color:red;font-size:120%

// object
console.log(div.style); // [object CSSStyleDeclaration]
console.log(div.style.color); // red
</script>

NodeList

Nodelist、NamedNodeMap、HTMLCollection

NodeList 对象是节点的集合。一般通过属性和方法返回,

NamedNodeMap 接口代表一个 Attr 对象集合。

HTMLCollection 返回元素元素节点的集合。以下属性和方法可以返回

Live NodeLists

这三个集合都是“动态的”。换句话说,每当文档结构发生变化时,它们都会得到更新。它们始终都会保存着最新、最准确的信息。

所有 NodeList 对象都是在访问 DOM 文档时实时运行的查询。

<div>example</div>
var divs = document.getElementsByTagName("div"),
i,
len,
div;

// NodeList [div] length = 1
console.log(document.querySelectorAll('div');

// HTMLCollection [div] length = 2
console.log(document.getElementsByTagName('div'));

// len = divs.length
// 缓存 DOM 查询结果
for (i = 0, len = divs.length; i < len; i++) {
div = document.createElement('div');
div.textContent = 'example';
document.body.appendChild(div);
}

the difference of HTMLCollection and NodeList

应该尽量减少访问 NodeList 的次数,因为每次访问 NodeList,都会运行一次基于文档的查询。所以,可以考虑将从 NodeList 中取得的值缓存起来。

Static NodeLists

document.querySelectorAll(selector) 方法返回一个静态的 NodeList 。在 DOM 中任何改变都不会影响此集合的内容。

Shadow DOM VS. Virtural DOM

  1. Shadow DOM is a browser technology designed primarily for scoping variables and CSS in web components.
  2. The Virtual DOM is a concept implemented by libraries in JavaScript on top of browser APIs.

HTMLTemplateElement

属性:content ,只读的 DocumentFragment

DocumentFragment

文档片段接口,一个没有父对象的最小文档对象。

Node.cloneNode()

复制调用该方法节点的副本,添加到文档之前,此拷贝节点不属于当前文档树的一部分,没有父节点。

CSSOM API

CSS Rule tree 需要对照DOM Tree 从右向左解析CSS 的 选择器。

CSS匹配 HTML 元素是一个相当复杂和有性能问题的事情。所以,你就会在很多地方看到:DOM 树要小,CSS 尽量用 id 和 class,千万不要过渡层叠下去(避免冗余的元素嵌套)。

Reflow 成本远大于 Repaint

下面的动作会增加成本

display: none 会触发 reflow,visibility:hidden 只会触发 repaint,位置没有变化。

reflow 的原因:

减少 reflow/repaint