1、网页其实是一棵树

2、JS 如何操作这棵树
- 浏览器往window上加一个 document 即可
- JS用 document 操作网页
- 这就是 Document Object Model 文档对象模型
- 记住一个事实:DOM 很难用
3、使用 JS 操作这棵 DOM 树
1、获取元素,也叫标签
- 有很多API:
window.idxxx或者直接idxxx
document.getElementById('idxxx')document.getElementsByTagName('div')[0]document.getElementsByClassName('div')[0]document.querySelector('#idxxx')document.querySelectorAll('.red')[0]
- 这么多,用哪一个呢?
- 工作中用
querySelector和querySelectorAll - 兼容IE才用 getElement(s)ByXXX()
- 工作中用
2、获取特定元素
-
获取 html 元素
- document.documentElement

- document.documentElement
-
获取 head 元素
- document.head

- document.head
-
获取body元素
- document.body

- document.body
-
获取窗口(窗口不是元素)
- window
-
获取所有元素
- document.all
- 这个 document.all 是个奇葩,只有在 ie 浏览器内为 true ,是第6个 falsy 值

3、获取到的元素是个啥?
-
显然是一个对象,需要搞清楚它的原型
-
用 console.dir(div1) 查看原型链
- 第一层原型:HTMLDivElement.prototype(所有 div 共有的属性)
- 第二层原型:HTMLElement.prototype(所有HTML标签共有的属性)
- 第三层原型:Element.prototype (包括所有XML、HTML标签的共有属性)
- 第四层原型:Node.prototype (节点共有的属性,包括 XML 标签文本注释,HTML 标签文本注释)
- 第五层原型:EventTarget.prototype (其中最重要的函数属性是 addEventListener() )
- 第六层原型:Object.prototype
-
节点 Node 包括以下几种:
- MDN有完整描述,x.nodeType 得到一个数字
- 数字 1 表示元素 Element ,也叫标签 Tag
- 3 表示文本 Text
- 8 表示注释 Comment
- 9 表示文档 Document

4、节点的增删改查
1、增
-
创建一个标签节点
- let div1 = document.createElement('div')
- document.createElement('style')
- document.createElement('script')
- document.createElement('li')
-
创建一个文本节点
- text1 = document.createTextNode('你好')
-
标签里插入文本
- div1.appendChild(text1)
- div1.innerText = '你好' 或者 div1.textContent = '你好'
- 但是不能用div1.appendChild('你好')
- 示例:

-
插入页面中
- 创建的标签默认处于JS线程中
- 必须把它插入到 head 或者body 里面,它才会生效
document.body.appendChild(div1)- 或者
- 已经存在于页面内的元素.appendChild(div1)
-
关于 appendChild() 方法
- 代码:
- 页面中有 div#test1 和 div#test2
let div = document.createElement('div') test1.appendChild(div) test2.appendChild(div)- 请问最终 div 出现在哪里?test2 中
- 一个元素不能出现在两个地方,除非复制一份
-
怎么复制/克隆呢?
- let div2 = div1.cloneNode(deep)
- 其中的 deep 为可选元素,表示是否采用深度克隆
- 如果为 true ,则该节点的所有后代节点也都会被克隆
- 如果为 false ,则只克隆该节点本身
2、删
-
两种方法
- 旧方法:
div1.parentNode.removeChild(div1) - 新方法:
div1.remove()但存在兼容问题,不兼容IE - 这两种方法删除 div1 后,div1 只是在树中被删除,还是存在于 JS 的内存中
- 旧方法:
-
要彻底删除一个 node ?
div1.remover()
div1 = null
3、改
-
写标准属性
- 改class: div.className = 'red' (但是会覆盖之前设置的 className)
div1.className = 'red' //<div class='red'> </div> div1.className = 'blue' //<div class='blue'> </div> div1.className += ' red' //<div class='blue red'></div> div1.classList.add('green') //<div class='blue red green'></div>- 改 style:
div1.style = 'width:100px'(会覆盖之前的所有 style 属性) - 改 style 的一部分:
div1.style.width = '100px' - 注意大小写,如:
div1.style.backgroundColor = 'white' - 改 data-* 属性:
div.dataset.* = 'wbh'
-
读标准属性
div.classList/a.hrefdiv.getAttribute('class')/a.getAttribute('href')- 两种方法都可以,第二种更加保险,但值可能会有些不同
div1.classList //["blue", "red", "greenfk", value: "blue red greenfk"] a.href //若路径为相对路径,该方法会自动用网址补全该路径 div.getAttribute('class') //"blue red greenfk" a.getAttribute('href') //为标签内 href 的原值

-
改事件处理函数
- div1.onclick 默认为null
- 默认点击 div1 不会有任何事发生
- 但是如果把 div1.onclick 改为一个函数 fn
- 那么点击 div1 的时候,浏览器就会调用这个函数
- 并且是这样调用的 fn.call(div1,event)
- div 会被当作 this
- event 则包含了点击事件的所有信息,如坐标
test.onclick = function(x){ console.log(this) console.log(x) } //test.onclick.call(this,event),其中this指的就是test,event也就是x,第一个参数 -
改内容
- 改文本内容
div.innerText = 'xxx' div.textContent = 'xxx' //两者几乎没有区别- 改 HTML 内容
div.innerHTML = '<strong>重要内容</strong>'- 改标签
div.innderHTML = '' //先清空 div.appendChild(div2) //再加内容- 改爸爸,想要找一个新爸爸?
newParent.appendChild(div) //直接这样就可以了,直接从原来的地方消失
4、查
- 查爸爸
node.parentNode或者node.parentElement

-
查爷爷
node.parentNode.parentNode
-
查子代
- node.childNodes 或者 node.children
- 注意,node.childNodes 会获取到标签中的空格
- 示例:
<ul id="test"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> console.log(test.childNodes.length) // 9<ul id="test"><li>1</li><li>2</li><li>3</li><li>4</li></ul> console.log(test.childNodes.length) // 4<ul id="test"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> console.log(test.children.length) // 4 -
查兄弟姐妹
node.parentElement.children然后排除自己
let all = div1.parentElement.children let siblings = [] for(let i=0;i<all.length;i++){ if(all[i] != div1){ siblings.push(all[i]) } } -
查看老大
node.firstChild或者node.children[0]
-
查看老幺
node.lastChild
-
查看上一个哥哥/姐姐
node.previousSibling- 如果要避开文本节点,
node.previousElementSibling
-
遍历一个 div 里面的所有元素
travel = (node,fn) =>{
fn(node)
if(node.children){
for(let i=0;i<node.children.length;i++){
travel(node.children[i],fn)
}
}
}
travel(div1,(node) => console.log(node))
5、DOM操作是跨线程的
跨线程操作
- 各线程各司其职
- JS 引擎不能操作页面,只能操作JS
- 渲染引擎不能操作 JS ,只能操作页面
- 那么
document.body.appendChild(div1) - 这句 JS 是如何改变页面的呢?
- 跨线程通信
- 当浏览器发现 JS 在 body 里面加了一个 div1 对象
- 浏览器就会通知渲染引擎在页面里也新增一个 div 对象
- 新增的 div 元素所有属性都照抄 div1 对象
插入新标签的完整过程
- 在 div1 放入页面之前
- 对 div1 的所有操作都属于 JS 线程内的操作
- 把 div1 放入页面之时
- 浏览器会发现 JS 的意图
- 就会通知 渲染线程在页面中渲染 div1 对应的元素
- 把 div1 放入页面之后
- 对 div1 的操作都有可能触发重新渲染
- div1.id = 'newId' 可能会触发重新渲染,如id绑定了样式
- div1.title = 'new' 可能会触发重新渲染
- 如果对 div1 进行多次操作,浏览器可能会合并成一次操作,也可能不会
- 示例:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS Bin</title> <style> .start{ border:1px solid red; width:10px; height:100px; transition: width 1s; } .end{ width:200px; } </style> </head> <body> <div id="test"></div> <script> test.classList.add('start') test.clientWidth //获取宽度,这句话看似无用,实则会触发重新渲染,如果没有这句话,渲染会直接合并上下两个操作 test.classList.add('end') </script> </body> </html>
属性同步
- 标准属性
- 对 div1 的标准属性的修改,会被浏览器同步到页面中
- 比如 id、className、title等
- data-* 属性
- 同上
- 非标准属性
- 对非标准属性的修改,只会停留在 JS线程中
- 不会同步到页面里
- 比如
<div x="test"></div>的 x 属性
- 启示:
- 如果需要自定义属性,又想要把它同步到页面中
- 请使用 data- 作为前缀