JS HTML DOM

HTML DOM (文档对象模型)

当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。

HTML DOM 定义了用于 HTML 的一系列标准的对象,以及访问和处理 HTML 文档的标准方法。通过 DOM,你可以访问所有的 HTML 元素,连同它们所包含的文本和属性。

HTML DOM 独立于平台和编程语言。它可被任何编程语言诸如 Java、JavaScript 和 VBScript 使用。

HTML DOM 模型被构造为对象的树:

查找 HTML 元素

通常,通过 JavaScript,您需要操作 HTML 元素。

为了做到这件事情,您必须首先找到该元素。有三种方法来做这件事:

  • 通过 id 找到 HTML 元素 document.getElementById("myBtn")
  • 通过标签名找到 HTML 元素
  • 通过类名找到 HTML 元素

传统写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回ID为'test'的节点:
let test = document.getElementById('test');

// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
let trs = document.getElementById('test-table').getElementsByTagName('tr');

// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
let reds = document.getElementById('test-div').getElementsByClassName('red');

// 获取节点test下的所有直属子节点:
let cs = test.children;

// 获取节点test下第一个、最后一个子节点:
let first = test.firstElementChild;
let last = test.lastElementChild;

第二种方法是使用 querySelector()querySelectorAll(),需要了解 selector 语法,然后使用条件来获取节点,更加方便:

1
2
3
4
5
6
7
8
9
10
11
// 通过 querySelector 获取ID为 q1 的节点:
let q1 = document.querySelector('#q1');

// 通过 querySelectorAll 获取 q1 节点内的符合条件的所有 p 节点:
let ps = q1.querySelectorAll('div.highlighted > p');

// <div class="c-red c-green"> 下的所有 p 节点
document.querySelectorAll('.c-red.c-green > p');

// 在 test-div 内,找到最后一个直接子元素中带有 c-green 类的 div,再取它的最后一个直接子 <p>
let haskell = document.querySelector("#test-div > .c-green:last-child > p:last-child");

注意:低版本的 IE<8不支持querySelectorquerySelectorAll。IE8 仅有限支持。

若非特殊情况,我们建议首选:querySelector() 和 querySelectorAll() 它们是最灵活、最强大的方法,因为它们可以使用任何 CSS 选择器。 querySelectorAll() 返回的 NodeList 通常比 getElementsByTagName() 和 getElementsByClassName() 返回的 HTMLCollection 更易于使用。 它们是现代 Web 开发的标准做法。

次选:getElementById() 如果你需要通过 id 获取单个元素,并且需要极致的性能,那么 getElementById() 仍然是一个不错的选择。

尽量避免:getElementsByTagName() 和 getElementsByClassName() 除非你 specifically 需要“活”的 HTMLCollection,或者你需要兼容非常旧的浏览器(IE8 及更早版本),否则通常应该优先使用 querySelectorAll()。


getElementById() 返回对拥有指定 id 的第一个对象的引用。
getElementsByName() 返回带有指定名称的对象集合
getElementsByTagName() 返回带有指定标签名的对象集合

document.getElementsByClassName

document.querySelector() 方法返回文档中与指定选择器或选择器组匹配的第一个 html 元素Element。 如果找不到匹配项,则返回 null

查找第一个匹配 class 属性的 html 元素 这个例子中,会返回当前文档中第一个类名为 “myclass” 的元素:

1
document.querySelector(".myclass");

获取匹配列表

要获取文档中所有<p>元素的NodeList

1
var matches = document.querySelectorAll("p");

此示例返回文档中所有<div>元素的列表,其中
class 包含 “note” 或 “alert”:

1
var matches = document.querySelectorAll("div.note, div.alert");

在这里,我们得到一个 <p> 元素的列表,其直接父元素是一个 class
"highlighted"div,并且位于
ID 为 "test" 的容器内。

1
2
var container = document.querySelector("#test");
var matches = container.querySelectorAll("div.highlighted > p");

此示例使用属性选择器返回文档中属性名为"data-src"iframe元素列表:

1
var matches = document.querySelectorAll("iframe[data-src]");

这里,属性选择器用于返回 ID
"userlist" 的列表中包含值为 "1""data-active" 属性的元素

1
2
var container = document.querySelector("#userlist");
var matches = container.querySelectorAll("li[data-active='1']");

文档本身是文档节点 所有 HTML 元素是元素节点 所有 HTML 属性是属性节点 HTML
元素内的文本是文本节点 注释是注释节点

HTML DOM Attribute 对象

在 HTML DOM (文档对象模型)中,每个部分都是节点:

1
<input id="xbb" type="text" value="lalalla"></input>
1
2
var haha = document.getElementById("xbb");
alert(haha.getAttributeNode("value").value);

创建 HTML 元素并插入

在文档对象模型 (DOM) 中,每个节点都是一个对象。DOM 节点有三个重要的属性,分别是:

  • nodeName : 节点的名称
  • nodeValue :节点的值
  • nodeType :节点的类型

appendChild

有两个办法可以插入新的节点。一个是使用 appendChild,把一个子节点添加到父节点的最后一个子节点。例如:

1
2
3
4
5
6
7
8
// 添加新元素,您必须首先创建该元素(元素节点)
var para = document.createElement("p")
var node = document.createTextNode("This is new.")
para.appendChild(node)

// 然后向一个已存在的元素追加该元素
var element = document.getElementById("div1")
element.appendChild(para)

注:如果我们插入的 js 节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。

我们在学习了动态创建一个节点然后添加到 DOM 树后,可以实现很多功能。举个例子,下面的代码动态创建了一个<style>节点,然后把它添加到<head>节点的末尾,这样就动态地给文档添加了新的CSS定义:

1
2
3
4
let d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);

可以在 Chrome 的控制台执行上述代码,观察页面样式的变化。

insertBefore

如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement);,子节点会插入到referenceElement之前。

删除已有的 HTML 元素

1
2
3
4
5
6
7
8
9
<div id="div1">
<p id="p1">This is a paragraph.</p>
<p id="p2">This is another paragraph.</p>
</div>
<script>
const parent = document.getElementById("div1")
const child = document.getElementById("p1")
parent.removeChild(child)
</script>

注意:当你遍历一个父节点的子节点并进行删除操作时,子节点变化时会实时更新。

改变 HTML 内容

改变 HTML 输出流

JavaScript 能够创建动态的 HTML 内容:
在 JavaScript 中,document.write()  可用于直接向 HTML 输出流写内容。如果您在文档加载后使用该方法,会覆盖整个文档。

修改 HTML 内容的最简单的方法是使用  innerHTML 属性

改变 HTML 属性

拿到一个 DOM 节点后,我们可以对它进行更新。

可以直接修改节点的文本,方法有两种:

一种是修改 innerHTML 属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过 HTML 片段修改 DOM 节点内部的子树:

innerHTML 时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到的,要注意对字符编码来避免XSS攻击。

第二种是修改 innerTexttextContent 属性,这样可以自动对字符串进行 HTML 编码,保证无法设置任何HTML标签:

1
2
3
4
5
6
// 获取<p id="p-id">...</p>
let p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id">&lt;script&gt;alert("Hi")&lt;/script&gt;</p>

两者的区别在于读取属性时,innerText 不返回隐藏元素的文本,而 textContent 返回所有文本。另外注意IE<9不支持 textContent

改变 HTML 样式

修改 CSS 也是经常需要的操作。DOM 节点的 style 属性对应所有的CSS,可以直接获取或设置。因为CSS允许 font-size 这样的名称,但它并非JavaScript 有效的属性名,所以需要在JavaScript中改写为驼峰式命名 fontSize

1
2
3
4
5
6
// 获取<p id="p-id">...</p>
let p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';

HTML DOM 事件

HTML 事件的例子:

  • 当用户点击鼠标时
  • 当网页已加载时
  • 当图像已加载时
  • 当鼠标移动到元素上时
  • 当输入字段被改变时
  • 当提交 HTML 表单时
  • 当用户触发按键时

使用 HTML DOM 来分配事件

HTML DOM 允许您使用 JavaScript 来向 HTML 元素分配事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>W3Cschool教程(w3cschool.cn)</title></head>

<head></head>

<body>
<p>点击按钮执行
<em>displayDate()</em>函数.</p>
<button id="myBtn">点这里</button>
<script>document.getElementById("myBtn").onclick = function() {
displayDate()
};
function displayDate() {
document.getElementById("demo").innerHTML = Date();
}</script>
<p id="demo"></p>
</body>

</html>

除了

1
2
3
document.getElementById("myBtn").onclick = function () {
displayDate()
}

我们可用写在行内,但不推荐:<button onclick="displayDate()">点我</button>

HTML DOM EventListener

addEventListener() 方法

addEventListener() 方法添加的事件句柄不会覆盖已存在的事件句柄。
你可以向一个元素添加多个事件句柄。
你可以向同个元素添加多个同类型的事件句柄,如:两个 “click” 事件。
你可以向任何 DOM 对象添加事件监听,不仅仅是 HTML 元素。如: window 对象。
当你使用 addEventListener() 方法时, JavaScript 从 HTML 标记中分离开来,可读性更强, 在没有控制 HTML 标记时也可以添加事件监听

语法

1
element.addEventListener(event, function, useCapture);

第一个参数是事件的类型 (如 “click” 或 “mousedown”).
第二个参数是事件触发后调用的函数。
第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。

注意:不要使用 “on” 前缀。 例如,使用 “click” ,而不是使用 “onclick”。

事件冒泡或事件捕获?
事件传递有两种方式:冒泡与捕获。

事件传递定义了元素事件触发的顺序。 如果你将 <p> 元素插入到 <div> 元素中,用户点击 <p> 元素, 哪个元素的 “click” 事件先被触发呢?

在冒泡中,内部元素的事件会先被触发,然后再触发外部元素,即: <p> 元素的点击事件先触发,然后会触发 <div> 元素的点击事件。

在捕获中,外部元素的事件会先被触发,然后才会触发内部元素的事件,即: <div> 元素的点击事件先触发 ,然后再触发 <p> 元素的点击事件。

默认值为 false, 即冒泡传递,当值为 true 时, 事件使用捕获传递。

removeEventListener() 方法

removeEventListener() 方法移除由 addEventListener() 方法添加的事件句柄: