DOM 编程的相关知识,内容包括什么是 DOM、获取元素、获取特定元素、节点与元素、节点的增删改查、属性同步。
一、什么是 DOM
网页其实是一棵树,JS 用 document 操作网页,这种思想就是 Document Object Model -- 文档对象模型,简称 DOM
记住一个事实:DOM 很难用,DOM 自带的功能非常反人类
如果你觉得 DOM 很傻,不要怀疑自己,你的觉得是对的
二、获取元素
获取元素,也叫标签,先说下什么是 API,API 就是提供的所有函数。
有很多 API 能够获取元素:
window.idxxx 或者直接 idxxx // 获取 id 元素最方便
document.getElementById('idxxx') // 当 id 与全局属性冲突,用这个
document.getElementsByTagName('div')[0] // 里面有 's',会取到所有 'div',所以要有下标 []
document.getElementsByClassName('red')[0]
document.querySelector('#idxxx') // 括号里面 CSS 怎么写,你就怎么写,它沿用了 CSS 语法
document.querySelectorAll('.red')[0]
用哪个?
工作中用 querySelector 和 querySelectorAll
做 demo 直接用 idxxx,千万别让人发现
要兼容 IE 时才用 getElement(s)ByXXX
三、获取特定元素
1. 获取 html 元素
document.documentElement
document.documentElement.tagName // 回应:"HTML"
为什么小写标签返回大写?这就是 DOM 反人类的地方
2. 获取 head 元素
document.head
3. 获取 body 元素
document.body
4. 获取窗口(窗口不是元素)
window
// 虽然不是元素,但我们也可以获取它嘛
// 有什么用呢?比如监听 window 的 onclick 事件
window.onclick = ()={console.log('hi')}
5. 获取所有元素
document.all
// 这个 document.all 是个奇葩,第 6 个 falsy 值,是 ie 浏览器发明的
if(document.all){console.log('ie 浏览器')} // 只能 ie 里运行
else {console.log('其他浏览器')} // 只能在非 ie 运行
6. 看看 div 的原型
获取到的元素是个啥?显然是一个对象,我们需要搞清它的原型
抓一只 div 对象来看看, console.dir(div1) 看原型链
__proto__: HTMLDivElement
// 这里,Chrome 显示错了
__proto__ === HTMLDivElement // false
__proto__ === HTMLDivElement.prototype // true
- 第一层原型 HTMLDivElement.prototype
- 第二层原型 HTMLElement.prototype
- 第三层原型 Element.prototype
- 第四层原型 Node.prototype
- 第五层原型 EventTarget.prototype
- 最后一层原型就是 Object.prototype 了
div 完整原型链:自身属性和共有属性

四、节点与元素
节点 Node 包括以下几种,NDN 有完整描述,x.nodeType 得到一个数字 → 点击这里阅读
- 1 表示元素 Element,也叫标签 Tag
- 3 表示文本 Text
- 8 表示注释 Comment
- 9 表示文档 Document
- 11 表示文档片段 DocumentFragment
- 记住 1 和 2 即可
五、节点的增
1. 创建一个标签节点
let div1 = document.createElement('div')
document.createElement('style')
document.createElement('script')
document.createElement('li')
2. 创建一个文本节点
text1 = document.createTextNode('你好')
3. 标签里面插入文本
div.appendChild(text1) // 这是 Node 里提供的接口
div1.innerText = '你好' 或者 div1.textContent = '你好' // 这是 Element 里提供的接口
但是不能用 div1.appendChild('你好') // 你不能混着用
4. 插入页面中
// 你创建的标签默认处于 JS 线程中
// 你必须把它插到 head 或者 body 里面,它才会生效
document.body.appendChild(div1) 或者 已在页面中的元素.appendChild(div1)
5. appendChild
let div = document.createElement('div')
test1.appendChild(div)
test2.appendChild(div)
// 问:最终 div 出现在哪里?
// 1. test1 里面 2. test2 里面 3. test1 里面和 test2 里面
// 答:test2 里面
一个元素不能出现在两个地方,除非复制一份
let div2 = div1.cloneNode()
六、节点的删
// 旧方法:
parentNode.removeChild(childNode)
// 新方法:
childNode.remove()
七、节点的改
1. 改属性
// 写标准属性
改 id: div.id = 'div2'
改 class:div.className = 'red blue'(全覆盖)
改 class:div.classList.add('red')
改 style:div.style = 'width: 100px; color: blue;'
改 style 的一部分:div.style.width = '200px'
大小写:div.style.backgroundColor = 'white'
改 date-* 属性:div.dataset.x = 'frank'
// class 要用 className,因为早期的 JS 对象是不能用它的保留字作为 key
// 注意大小写的问题,JS 不支持有中划线的 key,所有中划线隔开的用大写代替
// 读标准属性
div.classList / a.href
div.getAttribute('class') // a.getAttribute("href")
// 两种方法都可以,但值可能稍微有些不同
// console.log(test.getAttribute('href'))
这可以确保这是你想要的
2. 改事件处理函数
div.onclick 默认为 null
默认点击 div 不会有任何事情发生,但是如果你把 div.onclick 改为一个函数 fn,那么点击 div 的时候,浏览器就会调用这个函数
并且是这样调用的 fn.call(div,event),div 会被当做 this,event 则包含了点击事件的所有信息,如坐标
3. 改内容
改文本内容
div.innreText = 'xxx'
div.textContent = 'xxx'
// 两者几乎没有区别
改 HTML 内容
div.innerHTML = '<strong>重要内容</strong>'
// 这个方法如果有很多内容,2 万个字符或更多,有可能把浏览器卡爆
改标签
div.innerHTML = '' // 先清空
div.appendChild(div2) // 再加内容
改爸爸
newParent.appendChild(div)
八、节点的查
查爸爸
node.parentNode 或者 node.parentElement
查爷爷
node.parentNode.parentNode
查子代
node.childNodes 或者 node.children
// 一般使用 children
// 用 childNodes 查 它儿子 length 总共长度的时候,会把回车+空格合成空格一起算进去
// 当子代变化时,两者也会实时变化
查兄弟姐妹
node.parentNode.childNodes 还要排除自己
node.parentNode.children 还要排除自己
查看老大
node.firstChild
查看老幺
node.lastChild
查看上一个哥哥/姐姐
node.previousSibling
查看下一个弟弟/妹妹
node.nextSibling
遍历一个 div 里面的所有元素
travel = (node, fn)=>{
fn(node)
if(node.children){
for(let i=0;i<node.children.length;i++){
travle(node.children[i], fn)
}
}
}
travel(div1, (node)=>console.log(node))
九、属性同步
1. DOM 操作是跨线程的
浏览器分为渲染引擎和 JS 引擎,渲染引擎专门用来渲染 html 和 CSS,JS 引擎专门用来执行 JS,他们两个是不同的线程,互不相干。
2. 跨线程通信
JS 引擎不能操作页面,只能操作 JS,渲染引擎不能操作 JS,只能操作页面。
当浏览器发现 JS 在 body 里面加了个 div1 对象,浏览器就会通知渲染引擎在页面里也新增一个 div 元素,新增的 div 元素所有属性都会照抄 div1 对象
元素是元素,对象是对象,它们两个东西是不同的
3. 插入新标签的完整过程
在 div1 放入页面之前
你对 div1 所有的操作都属于 JS 线程内的操作
把 div1 放入页面之时
浏览器会发现 JS 的意图,就会通知渲染线程在页面中渲染 div1 对应的元素
把 div1 放入页面之后
你对 div1 的操作都有可能会触发重新渲染,如果你连续对 div1 多次操作,浏览器可能合并成一次操作,可能不会
4. 属性同步
标椎属性(比如 id、className、title 等): 对 div1 的标椎属性的修改,会被浏览器同步到页面中
date- 属性: 同上
非标准属性: 对非标准属性的修改,则只会停留在 JS 线程中,不会同步到页面里
启示: 如果你有自定义属性,又想被同步到页面中,请使用 data- 作为前缀
5. Property VS Attribute
property 属性: JS 线程中 div1 的所有属性,叫做 div1 的 property
attribute 也是属性: 渲染引擎中 div 对应标签的属性,叫做 attribute
区别: 大部分时候,同名的 property 和 attribute 值相等,但如果不是标椎属性,那么他俩只会在一开始时对等
「资料来源:©饥人谷」