在框架盛行的年代,还有多少人记得在没有框架时我们如何控制 dom 的行为呢?作者本人也一直忽视了这方面的学习,直到面试问到这个问题,下决心好好认识认识这个 dom api。
node、document 和 element
在学习 dom api 时对这三者还是挺混乱的。理一下他们之间的关系。
node
node 是一个接口,像 document 和 element 都是继承这个接口的。这个接口提供了 dom 节点的获取和操作方法。
node 有许多类型,下图列出了一些 node 的类型码。由图可见 element 的类型码为 1,文本节点类型码为 3,注释节点类型码为 8,document 的类型码为 9。
这让我想到了 vue.js 中出现多次的代码:if (child.nodeType === 3) { ... }
其实就是判断当前节点是否为文本节点。
element
从上面的内容可知,element 就是一个特殊的 node(nodeType == 1),其实 element 就是 HTML 各类标签,如
document
同理,document 也是一个特殊的 node,它与 element 的不同之处在于 document 通常是 DOM 节点,即包含有 head 和 body 元素的一个 node。
参考:Difference between Node object and Element object? - Stack Overflow
api 学习
首先我发现一点:所有 dom 操作的起点都是使用 document 去获取各种类型的 node (集合)然后再去执行各类 dom 操作的行为!
由于 document 操作的 api 真的很多,所以我选取我想了解的部分学习了。在这里我学习 DOM API 的目的是:
学习
document
对象操作 dom 的方式,拥有脱离框架(jquery、vue等)来操作 dom 的能力。
获取节点
- Document.documentElement 返回 document 的直属后代元素。
- Document.activeElement 返回当前正在操作的元素
- Document.body 返回当前文档的
<body>
元素。与此类似的还有 Document.head 和 Document.scripts 两个属性返回当前文档的<head>
和<script>
元素。 - Document.getElementByClassName() 返回有给定样式名的元素列表
- Document.getElementByTagName() 返回有给定标签名的元素列表
- document.getElementById() 返回一个对识别元素的对象引用
- document.querySelector() 返回文档中第一个匹配指定选择器的元素
- document.querySelectorAll() 返回一个匹配指定选择器的元素节点列表
- Node.childNodes 返回一个包含了该节点所有子节点的实时的
NodeList
是“实时的”意思是,如果该节点的子节点发生了变化,NodeList
对象就会自动更新。 - Node.firstChild & Node.lastChild 返回该节点的第一个子节点或最后一个子节点,如果该节点没有子节点则返回 null。
- Node.previousSibling & Node.nextSibling 返回与该节点同级的上一个或下一个节点,如果没有返回null。
- Node.ownerDocument 返回这个元素属于的
Document
对象 。 - Node.parentNode 返回一个当前结点
Node
的父节点 。 - Node.parentElement 返回一个当前节点的父节点
Element
。
操作节点
- Document.createComment() 创建一个新的注释节点并返回它
- Document.createDocumentFragment() 创建一个新的文档片段
- Document.createElement() 用给定的标签名创建一个新的元素。
- Document.createTextNode() 创建一个文字节点
- Document.write() 向文档中写入内容(与之有类似功能的是 Document.writeln() 不同之处在于后面多了个换行符。)
- Element.innerHTML 设置或返回元素的内容
- Node.textContent 获取或设置一个标签内所有子结点及其后代的文本内容。
- Node.appendChild() 向元素添加新的子节点,作为最后一个子节点。
- Node.cloneNode() 克隆元素(方法中传参为deep,如果deep为true则深拷贝。)
- Node.insertBefore() 在指定已有节点前插入新节点(没有
insertAfter
方法。可以使用insertBefore
方法和nextSibling
来模拟它。) - Node.normalize() 合并元素中相邻文本节点
- Node.removeChild() 从元素中移除子节点
- Node.replaceChild() 替换元素中的子节点
其中 Document 的 createXXX
方法还有一些其他不常用的,如需使用请查阅 MDN。
其他常用属性和方法
- Element.classList 返回元素的 class 集合。
- EventTaget.addEventListener() 注册监听事件
- Node.nodeType 返回该节点的类型码
- Node.nodeValue 返回或设置当前节点的值。
- Node.compareDocumentPosition() 比较当前节点与任意文档中的另一个节点的位置关系。
- Node.contains() 传入的节点是否为该节点的后代节点。
- Node.hasChildNodes() 是否拥有子节点
- Node.isEqualNode() 检查两个元素是否相等
- Node.isSameNode() 检查两个元素是否为相同的节点
以上内容均参考了 MDN 上的内容:
- Node https://developer.mozilla.org/zh-CN/docs/Web/API/Node
- Element https://developer.mozilla.org/zh-CN/docs/Web/API/Element
- Document https://developer.mozilla.org/zh-CN/docs/Web/API/Document
写个操作 DOM 的例子
接下来就使用这些 API 来进行一些 DOM操作。
获取各个位置的节点。
这里写了个小demo:
1 | <div id="container"> |
最后返回结果如图:
由于在 chrome 中空格、换行算是文本节点。所以获取最后元素的时候总是会获取到那些文本节点上去。这个要注意的。所以我在代码中使用 nodeType == 1
来区分是否为元素。
在上面例子中查找了各种关系的元素,解决日常元素获取问题应该不难了。
实践创建node、插入node和删除node。
1 | <div> |
以上代码实现了在各个位置插入元素和元素的删除替换,点击此处查看运行结果。
简单实现 v-for、v-text、v-html、v-on 和 v-model 这些功能。
好吧,作为一个 Vue.js 爱好者,绕不开的想到了 Vue.js 操作 DOM 的一些功能。这里就试着简单实现下(不涉及 Virtual DOM,只是单纯的 DOM 修改)。
如果对 Vue 命令不了解可以去官网看看这些指令的用法。
1 | <html> |
点击此处看效果。最后结果如图:
简单实现了 Vue.js 指令的这些功能,其实在 Vue.js 源码中也是用了这些 dom 操作的 api 来做的。
更多 Vue 源码中的 DOM 操作可以看下我的 《Vue.js 源码学习六 —— VNode虚拟DOM学习》这篇文章中。
最后
无论什么框架,其实都是万变不离其宗。最终都是用最基础的 API 来实现的各种功能。所以学好基础知识是非常重要的~
PS:还是 MDN 靠谱,w3school 的资料虽然也挺多,但是感觉不是很靠谱……以后查资料尽量去 MDN 英文网站去查(中文网站翻译有些问题)。