DOM 编程 Note-FrontEnd-22

211 阅读6分钟

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
  1. 第一层原型 HTMLDivElement.prototype
  2. 第二层原型 HTMLElement.prototype
  3. 第三层原型 Element.prototype
  4. 第四层原型 Node.prototype
  5. 第五层原型 EventTarget.prototype
  6. 最后一层原型就是 Object.prototype 了

div 完整原型链:自身属性和共有属性

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 值相等,但如果不是标椎属性,那么他俩只会在一开始时对等

「资料来源:©饥人谷」