一、概念
将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。
1、内容树
任何HTML文档 (或者说任何SGML文档或者 XML 文档) 是一个树状结构。 比如,以下的文档和树状结构是相似的
<html>
<head>
<title>My Document</title>
</head>
<body>
<h1>Header</h1>
<p>Paragraph</p>
</body>
</html>
当Mozilla解析文档的时候,它首先构建一个内容树然后用它来显示这个文档。
用于描述树状结构的术语通常出现在DOM Level 1的核心中。 我上面画的每一个方块都是这个树的一个节点。 节点上面的线条表示父子关系: 上面的父节点, 而位于下方的是子节点. 位于一个父节点下面的两个子节点是相邻的。 类似地,我们可以指代祖先和后代。
2、使用选择器定位DOM元素
Selectors API提供了通过与一组选择器匹配来快速轻松地从DOM检索 Element节点的方法。这比以前的技术要快得多,其中有必要使用JavaScript代码中的循环来定位您需要查找的特定项目。
- NodeSelector 接口
此规范向实现 Document, DocumentFragment, 或 Element 接口的任何对象添加了两种新方法:
querySelector
返回节点子树内与之相匹配的第一个 Element 节点。如果没有匹配的节点,则返回null。
var baseElement = document.querySelector("p");
document.getElementById("output").innerHTML =
(baseElement.querySelector("div span").innerHTML);
querySelectorAll
返回一个NodeList 包含节点子树内所有与之相匹配的Element节点,如果没有相匹配的,则返回一个空节点列表。
var el = document.querySelector('#test');
var matches = el.querySelectorAll('div.highlighted > p');
3、事件及DOM
主要描述了事件(Event)接口本身以及DOM节点中的事件注册接口、事件监听接口
注册事件监听器
这里有3种方法来为一个DOM元素注册事件回调。
(1)EventTarget.addEventListener
// 假设 myButton 是一个按钮
myButton.addEventListener('click', greet, false);
function greet(event) {
// 打印并查看event对象
// 打印arguments,以防忽略了其他参数
console.log('greet: ' + arguments);
alert('Hello world');
}
(2)HTML 属性
<button onclick="alert('Hello world!')">
应该尽量避免这种书写方式,这将使HTML变大并减少可读性。考虑到内容/结构及行为不能很好的分开,这将造成bug很难被找到。
(3)DOM 元素属性
// 假设 myButton 是一个按钮
myButton.onclick = function(event){alert('Hello world');};
这种方式的问题是每个事件及每个元素只能被设置一个回调。
二、接口
1、Attr
该类型使用对象来表示一个DOM元素的属性。在大多数DOM方法中,你可能会直接通过字符串的方式获取属性值(例如Element.getAttribute()),但是一些函数(例如Element.getAttributeNode())或通过迭代器访问时则返回Attr类型。
(1)name 该属性的名称
(2)namespaceURI 表示该属性的命名空间URIDOMString,如果该元素不在命名空间中,则返回null。
(3)localName 表示该属性的命名空间限定的本地名称DOMString。
2、Document
接口表示任何在浏览器中载入的网页,并作为网页内容的入口,也就是DOM 树。DOM 树包含了像 、
这样的元素,以及大量其他元素。它向网页文档本身提供了全局操作功能,能解决如何获取页面的 URL ,如何在文档中创建一个新的元素这样的问题。
1. 属性
(1)body:返回当前文档中的元素或者元素.
var aNewBodyElement = document.createElement("body");
aNewBodyElement.id = "newBodyElement";
document.body = aNewBodyElement;
alert(document.body.id); // "newBodyElement"
(2)Document.characterSet 只读属性返回当前文档的字符编码。该字符编码是用于渲染此文档的字符集,可能与该页面指定的编码不同。
<button onclick="alert(document.characterSet);">查看字符集</button>
//返回当前文档的字符集,比如"ISO-8859-1" 或者 "UTF-8"
(3)ParentNode.childElementCount 只读属性返回一个无符号长整型数字,表示给定元素的子元素数。
var foo = document.getElementById("foo");
if (foo.childElementCount > 0) {
// do something
}
(4)ParentNode.children 返回 一个Node的子elements ,是一个动态更新的 HTMLCollection。
// parg是一个指向<p>元素的对象引用
if (parg.childElementCount)
// 检查这个<p>元素是否有子元素
// 译者注:childElementCount有兼容性问题
{
var children = parg.children;
for (var i = 0; i < children.length; i++)
{
// 通过children[i]来获取每个子元素
// 注意:List是一个live的HTMLCollection对象,在这里添加或删除parg的子元素节点,都会立即改变List的值.
};
};
(5)Document.dir 代表了文档的文字朝向,是从左到右(默认)还是从右到左。‘rtl’(right to left)代表从右到左,‘ltr’(left to right)代表从左到右。
console.log(document.dir);// "" (译者添加)
document.dir = "ltr"//(默认);
document.dir = "rtl";
dirStr = document.dir;
document.dir = dirStr;
(6)Document.documentElement 是一个会返回文档对象(document)的根元素的只读属性(如HTML文档的 元素)。
const rootElement = document.documentElement;
const firstTier = rootElement.childNodes;
// firstTier 是由根元素的所有子节点组成的一个 NodeList
for (let i = 0; i < firstTier.length; i++) {
// 使用根节点的每个子节点
// 如 firstTier[i]
}
(7)document.head 返回当前文档中的 元素。如果有多个 元素,则返回第一个。
// HTML部分源码为: <head id="my-document-head">
var aHead = document.head;
alert(aHead.id); // "my-document-head";
alert( document.head === document.querySelector("head") ); // true
(8)document.images 接口的只读属性images返回当前文档中所有 image 元素的集合.
var ilist = document.images;
for(var i = 0; i < ilist.length; i++) {
if(ilist[i].src == "banner.gif") {
// 发现了banner图片
}
}
(9)document.links 属性返回一个文档中所有具有 href 属性值的 元素与 元素的集合。
var links = document.links;
for(var i = 0; i < links.length; i++) {
var linkHref = document.createTextNode(links[i].href);
var lineBreak = document.createElement("br");
document.body.appendChild(linkHref);
document.body.appendChild(lineBreak);
}
(10)Document.readyState 属性描述了document 的加载状态。当该属性值发生变化时,会在 document 对象上触发 readystatechange 事件。
一个文档的 readyState 可以是以下之一:
loading(正在加载)
document 仍在加载。
interactive(可交互)
文档已被解析,"正在加载"状态结束,但是诸如图像,样式表和框架之类的子资源仍在加载。
complete(完成)
文档和所有子资源已完成加载。表示 load 状态的事件即将被触发。
switch (document.readyState) {
case "loading":
// 表示文档还在加载中,即处于“正在加载”状态。
break;
case "interactive":
// 文档已经结束了“正在加载”状态,DOM元素可以被访问。
// 但是像图像,样式表和框架等资源依然还在加载。
var span = document.createElement("span");
span.textContent = "A <span> element.";
document.body.appendChild(span);
break;
case "complete":
// 页面所有内容都已被完全加载.
let CSS_rule = document.styleSheets[0].cssRules[0].cssText;
console.log(`The first CSS rule is: ${CSS_rule }`);
break;
}
document.addEventListener('readystatechange', (event) => {
log.textContent = log.textContent + `readystate: ${document.readyState}\n`;
});
2、方法
(1)ParentNode.append 方法在 ParentNode的最后一个子节点之后插入一组 Node 对象或 DOMString 对象。被插入的 DOMString 对象等价为 Text 节点。
var parent = document.createElement("div");
var p = document.createElement("p");
parent.append(p);
console.log(parent.childNodes); // NodeList [ <p> ]
(2)Document.createAttribute() 方法创建并返回一个新的属性节点。这个对象创建一个实现了 Attr 接口的节点。这个方式下DOM不限制节点能够添加的属性种类。
var node = document.getElementById("div1");
var a = document.createAttribute("my_attrib");
a.value = "newVal";
node.setAttributeNode(a);
console.log(node.getAttribute("my_attrib")); // "newVal"
(3)document.createDocumentFragment(); 创建一个新的空白的文档片段( DocumentFragment)。因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
var element = document.getElementById('ul'); // assuming ul exists
var fragment = document.createDocumentFragment();
var browsers = ['Firefox', 'Chrome', 'Opera',
'Safari', 'Internet Explorer'];
browsers.forEach(function(browser) {
var li = document.createElement('li');
li.textContent = browser;
fragment.appendChild(li);
});
element.appendChild(fragment);
(4)Document.createElement() 方法用于创建一个由标签名称 tagName 指定的 HTML 元素。如果用户代理无法识别 tagName,则会生成一个未知 HTML 元素 HTMLUnknownElement。
document.body.onload = addElement;
function addElement () {
// 创建一个新的 div 元素
let newDiv = document.createElement("div");
// 给它一些内容
let newContent = document.createTextNode("Hi there and greetings!");
// 添加文本节点 到这个新的 div 元素
newDiv.appendChild(newContent);
// 将这个新的元素和它的文本添加到 DOM 中
let currentDiv = document.getElementById("div1");
document.body.insertBefore(newDiv, currentDiv);
}
(5)document.createTextNode 创建一个新的文本节点。这个方法可以用来转义 HTML 字符。
const p1 = document.getElementById("p1"),
buttons = document.body.querySelectorAll(":scope > button");
function addTextNode(text) {
p1.appendChild( document.createTextNode(text) );
}
buttons.forEach(button =>
button.addEventListener("click", () =>
addTextNode(button.value)
)
);
(6)document.getElementById 返回一个匹配特定 ID的元素. 由于元素的 ID 在大部分情况下要求是独一无二的,这个方法自然而然地成为了一个高效查找特定元素的方法。
var elem = document.getElementById('para');
elem.style.color = newColor;
(7) document.getElementsByClassName(names); 返回一个包含了所有指定类名的子元素的类数组对象。当在document对象上调用时,会搜索整个DOM文档,包含根节点。你也可以在任意元素上调用getElementsByClassName() 方法,它将返回的是以当前元素为根节点,所有指定类名的子元素。
var testElements = document.getElementsByClassName('test');
var testDivs = Array.prototype.filter.call(testElements, function(testElement){
return testElement.nodeName === 'DIV';
});
(8)Document.hasFocus() 方法返回一个 Boolean,表明当前文档或者当前文档内的节点是否获得了焦点。该方法可以用来判断当前文档中的活动元素是否获得了焦点。
var info = document.getElementById("message");
if (document.hasFocus()) {
info.innerHTML = "该页面获得了焦点.";
}
else {
info.innerHTML = "该页面没有获得焦点.";
}
(9)ParentNode.prepend 方法可以在父节点的第一个子节点之前插入一系列Node对象或者DOMString对象。DOMString会被当作Text节点对待(也就是说插入的不是HTML代码)。
var parent = document.createElement("div");
var p = document.createElement("p");
var span = document.createElement("span");
parent.append(p);
parent.prepend(span);
console.log(parent.childNodes); // NodeList [ <span>, <p> ]
3、事件
(1)DOMContentLoaded 事件 当纯HTML被完全加载以及解析时,DOMContentLoaded 事件会被触发,而不必等待样式表,图片或者子框架完成加载。
document.addEventListener('DOMContentLoaded', (event) => {
console.log('DOM fully loaded and parsed'); // 译者注:"DOM完全加载以及解析"
});
(2)readystatechange 当文档的 readyState 属性发生改变时,会触发 readystatechange 事件。
document.addEventListener('readystatechange', (event) => {
log.textContent = log.textContent + `readystate: ${document.readyState}\n`;
});
(3)selectionchange 事件在文档上的当前文本选择被改变时触发
document.addEventListener("selectionchange", () => {
console.log(document.getSelection());
});
(4)selectstart 事件在用户开始一个新的选择时候触发。如果事件被取消,选择将不被触发。
document.addEventListener("selectstart", function() {
console.log('Selection started');
}, false);
(5)copy 事件在用户复制文本时触发
document.addEventListener('copy', (event) => {
console.log('copy action initiated')
});
(6)cut 剪切事件
document.addEventListener('cut', (event) => {
console.log('cut action initiated')
});
3、DocumentFragment
文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。
const list = document.querySelector('#list');
const fruits = ['Apple', 'Orange', 'Banana', 'Melon'];
const fragment = document.createDocumentFragment();
fruits.forEach(fruit => {
const li = document.createElement('li');
li.innerHTML = fruit;
fragment.appendChild(li);
});
list.appendChild(fragment);
4、DOMTokenList
DOMTokenList 接口表示一组空格分隔的标记(tokens)。如由 Element.classList、HTMLLinkElement.relList、HTMLAnchorElement.relList 或 HTMLAreaElement.relList 返回的一组值。
(1)Element.classList
是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。相比将 element.className 作为以空格分隔的字符串来使用,classList 是一种更方便的访问元素的类列表的方法。
const div = document.createElement('div');
div.className = 'foo';
// 初始状态:<div class="foo"></div>
console.log(div.outerHTML);
// 使用 classList API 移除、添加类值
div.classList.remove("foo");
div.classList.add("anotherclass");
// <div class="anotherclass"></div>
console.log(div.outerHTML);
// 如果 visible 类值已存在,则移除它,否则添加它
div.classList.toggle("visible");
// add/remove visible, depending on test conditional, i less than 10
div.classList.toggle("visible", i < 10 );
console.log(div.classList.contains("foo"));
// 添加或移除多个类值
div.classList.add("foo", "bar", "baz");
div.classList.remove("foo", "bar", "baz");
// 使用展开语法添加或移除多个类值
const cls = ["foo", "bar"];
div.classList.add(...cls);
div.classList.remove(...cls);
// 将类值 "foo" 替换成 "bar"
div.classList.replace("foo", "bar");
(2)HTMLLinkElement.relList
是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。相比将 element.link作为以空格分隔的字符串来使用,relList是一种更方便的访问元素的类列表的方法。
var links = document.getElementsByTagName("link");
var length = links.length;
for (var i = 0; i < length; i++) {
var list = links[i].relList;
var listLength = list.length;
console.log("New link found.");
for (var j = 0; j < listLength; j++) {
console.log(list[j]);
}
}
(3)HTMLAnchorElement.relList
是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。相比将 element.a作为以空格分隔的字符串来使用,relList是一种更方便的访问元素的类列表的方法。
var anchors = document.getElementsByTagName("a");
var length = anchors.length;
for (var i = 0; i < length; i++) {
var list = anchors[i].relList;
var listLength = list.length;
console.log("New anchor node found with", listLength, "link types in relList.");
for (var j = 0; j < listLength; j++) {
console.log(list[j]);
}
}
(4)HTMLAreaElement.relList
是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。相比将 element.area作为以空格分隔的字符串来使用,relList是一种更方便的访问元素的类列表的方法。
var areas = document.getElementsByTagName("area");
var length = areas.length;
for (var i = 0; i < length; i++) {
var list = areas[i].relList;
var listLength = list.length;
console.log("New area found.");
for (var j = 0; j < listLength; j++) {
console.log(list[j]);
}
}