一、什么是DOM
- DOM 是 Document Object Model(文档对象模型)的缩写。
- DOM 是 W3C(万维网联盟)的标准。
- DOM 定义了访问 HTML 和 XML 文档的标准:“W3C 文档对象模型 (DOM) 是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。”
- W3C DOM 标准被分为 3 个不同的部分:
①核心 DOM - 针对任何结构化文档的标准模型
②XML DOM - 针对 XML 文档的标准模型
③HTML DOM - 针对 HTML 文档的标准模型
HTML DOM
- 把HTML网页抽象成一个JS里的叫documents的对象,这样我们就可以对所有HTML元素进行操作
- HTML DOM 是:
①HTML 的标准对象模型
②HTML 的标准编程接口
③W3C 标准
-
HTML DOM 定义了所有 HTML 元素的对象和属性,以及访问它们的方法。
-
换言之,HTML DOM 是关于如何获取、修改、添加或删除 HTML 元素的标准。
二、HTML DOM节点Node
- 在 HTML DOM 中,所有事物都是节点。
- 常见节点
- 元素Element(标签Tag)节点--1------详见三、四
- 文本Text节点--3
- 注释Comment节点--8
- 文档Document节点--9
x.nodeType可以查看x是哪种节点,返回数字。- HTML DOM 将 HTML 文档视作树结构。这种结构被称为节点树:

- 我们常用父(parent)、子(child)和同胞(sibling)等术语来描述这些关系。父节点拥有子节点。同级的子节点被称为同胞(兄弟或姐妹)。

三、如何获取元素
(一)获取任意元素
1、偷偷用的最简便方法
window.idid- 但是如果id为保留字,比如parent,就不能用了。只能用下面的了
2、兼容IE才会用的方法
document.getElementByld('id')document.getElementsByTagName('标签名')[0](因为得到的是所有的这个标签,所以是个数组,你要自己选择需要该数组中的第几个)document.getElementsByClassName('class名')[0](同上)
3、工作中用的方法
document.querySelector('80%情况下css选择器咋写就咋写')document.querySelectorAll('.red')[0]
(二)获取特定元素
- 获取根元素html元素:
document.documentElement - 获取head元素:
document.head - 获取body元素:
document.body - 获取窗口(不是一个元素):
window - 获取所有元素:
document.all
document.all是第六个false值,但只有在布尔值判断时才会,平常各种功能还是正常使用的 *用以下代码判断是不是IE浏览器,从而执行对应的代码
if(document.all){
console.log('这是IE浏览器,后面的代码只可在IE浏览器里执行')
}
else {console.log('这是其他浏览器,后面的代码只可在其他浏览器执行')}
- 起初,IE发明了
document.all,所以如果document.all存在,就可以认为这个是IE浏览器,然后就可以执行IE专属代码。 - 后来,所有浏览器都抄了IE的
document.all。为了区分开IE浏览器和其他浏览器,就规定在其他浏览器中document.all为false,这样才会执行自己的代码,而非IE的代码
四、元素是个对象--看看一个div对象的原型链
console.dir(div1)看原型链
- 自身属性:
className、id、style等等 - 第一层原型
HTMLDivElement.prototype: 这里面是所有div共有的属性,不用细看 - 第二层原型
HTMLElement.prototype: 这里面是所有HTML标签共有的属性,不用细看 - 第三层原型
Element.prototype: 这里面是所有XML、HTML 标签的共有属性,你不会以为浏览器 只能展示HTML吧 - 第四层原型
Node.prototype: 这里面是所有节点共有的属性,节点包括XML标签文本注释、 HTML标签文本注释等等 - 第五层原型
EventTarget.prototype: 里面最重要的函数属性是addEventListener - 最后一层原型就是
Object.prototype了

五、节点Node的增删改查
(一)增节点
1、创建一个标签节点
document.createElement('标签名')let div1 = document.createElement('div')- 你创建的标签默认处于JS线程中,内存中
2、创建一个文本节点
text1 = document.createTextNode("你好")
3、appendChild() 方法
- appendChild() 方法可向节点的子节点列表的末尾添加新的子节点。
node.appendChild(node)- 相同的子节点不可以出现在两个地方:如果同时把一个子节点又插入节点1,又插入节点2,那该子节点最终会在节点2中(只能对一个人认爸爸,那个人就是最后一个叫他爸爸的人)
- 除非你把这个子节点复制一份
div2 = div1.cloneNode(),如果传递给它的参数是 true,它还将递归复制当前节点的所有子孙节点。否则,它只复制当前节点。
4、标签里面插入文本
div1. appendChild(text1)必须加文本节点的名字,不可以加文本的内容div1.innerText='你好'(Node原型提供)div1.textContent ='你好'(Element原型提供)
5、把新创建的标签插入页面中
- 必须把我们刚刚创建在js线程中的标签插到已在页面里的标签里面,它才会生效
- head或者body
document.body.appendChild(div1) - 或者
已在页面中的元素.appendChild(div1)
(二)删节点
- 旧方法:
parentNode.removeChild(childNode)
找到要删除的节点的父节点,用父节点的removeChild删除
- 新方法:
childNode.remove()直接删除 - 如果一个node被移出页面(DOM树),那么它还可以再次回到页面中(
appendChild())。因为他只是从页面中被删除了,他还在js引擎线程中
(三)改
1、改节点的标准属性
(1)改class
div.className= 'red blue'(全覆盖)div.className += 'black'(增加)div.classList.add('black')(增加)
(2)改style
div.style = 'width: 100px; color: blue;'(全覆盖)- 改style的一部分:
div.style.width = '200px' - 大小写:
div.style.backgroundColor = 'white'
(3)改data-*属性: div.dataset.x = 'frank'(没人用了)
//在html里给元素加上属性data-xxx='yyy'
<div id="day2-meal-expense"
data-drink="coffee"
data-food="sushi"
data-meal="lunch">¥20.12</div>
//要想获取某个data-*属性的值,可以像下面这样使用dataset对象:
var expenseday2 = document.getElementById('day2-meal-expense');
var typeOfDrink = expenseday2.dataset.drink; //'coffee'
(4)读标准属性
div.classList/a.href(会给你把路径补充完整)div.getAttribute('class')/a.getAttribute('href')(原封不动的给你)- 两种方法都可以,但值可能稍微有些不同
2、改节点的事件处理函数
- div.onclick默认为null
- 默认点击div不会有任何事情发生
- 但是如果你把div.onclick改为一个函数fn,那么点击div的时候,浏览器就会调用这个函数
- 并且是这样调用的fn.call(div, event)
- div会被当做this
- event则包含了点击事件的所有信息,如坐标
div.addEventListener是div.onclick的升级版
3、改子节点
(1)改文本内容
div.innerText = 'xxx'div.textContent= 'xxx'- 两者几乎没有区别
(2)改HTML内容(这不也是增加?)
div.innerHTML = '<strong>重要内容</strong>'
(3)改标签
div.innerHTML = ''//先清空div.appendChild(div2)//再加内容
4、改父节点
- 想找个新爸爸,让新爸爸把自己加进去就完事了
newParent.appendChild(div)- 直接这样就可以了,直接从原来的地方消失
(四)查节点
1. 查自己
node
2. 查爸爸
node.parentNodenode.parentElement
3. 查爷爷
node.parentNode.parentNode
4. 查子代
(1)所有子代
node.childNodes(会把你不想要的空格也当成一个文本子节点)
node.children(这个不会,只是元素节点,优先使用)- 当子代变化时,两者也会实时变化
document.querySelectorAll('.red')[0]不会实时更新
(2)一个子代
node.children[0]- 查看老大
node.firstChild - 查看老幺
node.lastChild
5. 查兄弟姐妹
(1)查所有兄弟姐妹
node.parentNode.childNodes但自己也在里面,要排除自己和所有文本节点node.parentNode.children但自己也在里面,要排除自己- 排除自己:要用for循环遍历所有子节点,然后把自己排除出去
let siblings = []
let arr = div2.parentElement.children //找出所有子节点形成一个数组
for(let i=0;i<c.length;i++){ //遍历数组中每个元素
if(arr[i] !== div2){ //如果不是自己
sibling.push(arr[i]) //就加入到新数组中
}
}
(2)查特定的兄弟姐妹
- 查看上一个哥哥/姐姐
node.previousSibling(节点兄弟,可能为文本节点) - 查看上一个哥哥/姐姐
node.previousElementSibling(元素兄弟) - 查看下一个弟弟/妹妹
node.nextSibling(节点兄弟,可能为文本节点) -
- 查看下一个弟弟/妹妹
node.nextElementSibling(元素兄弟)
- 查看下一个弟弟/妹妹
6、查看一个节点里所有的元素(儿子、孙子、曾孙子,,,)
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))
六、DOM操作是跨线程的
1. 各线程各司其职
- JS引擎不能操作页面,只能操作JS
- 渲染引擎不能操作JS,只能操作页面
2. 跨线程通信
document.body.appendChild(div1)---这是一句跨线程的DOM操作- 当浏览器发现JS在body里面加了个div1对象
- 浏览器就会通知渲染引擎在页面里也新增一个div元素
- 新增的div元素所有属性都照抄div1对象

3、插入新标签的完整过程
(1)在div1放入页面之前
- 你对div1所有的操作都属于JS线程内的操作
let div1 = document.createElement('div')
div1.textContent = 'hi'
(2)把div1放入页面之时
-
document.body.appendChild(div1) -
浏览器会发现JS的意图,就会通知渲染线程在页面中渲染div1对应的元素
(3)把div1放入页面之后
-
你对div1的操作都有可能会触发重新渲染
-
div1.id = 'newid'可能会重新渲染,也可能不会 -
div1.title= 'new'可能会重新渲染,也可能不会 -
如果你连续对div1多次操作,浏览器可能会合并成一 次操作,也可能不会(之前在动画里提到过)
属性同步
- 标准属性
- 对div1的标准属性的修改,会被浏览器同步到页面中
- 比如id、className、 title 等
- data-*属性 同上
- 非标准属性
- 对非标准属性的修改,则只会停留在JS线程中,不会同步到页面里
- 比如x属性,示例代码
- 启示
- 如果你有自定义属性,又想被同步到页面中,请使用
- data-作为前缀

Property V.S. Attribute
- property属性:JS线程中div1对象的所有属性,叫做div1的property
- attribute也是属性:渲染引擎中div1元素的属性,叫做attribute
- 区别
- 大部分时候,同名的property和attribute值相等
- 但如果不是标准属性(把div1放入页面之后只有标准属性会同步),那么它俩只会在一开始时相等(把div1放入页面之时,全过去)
- 但注意attribute只支持字符串,而property支持字符串、布尔等类型