DOM API

只要是学前端的同学,肯定都听说过 DOM

什么是 DOM

在 JS 中,常常使用 document.xxx 来对页面进行操作。那实际操作的是什么呢?

在开发者工具中运行

1
2
let div=document.querySelector('#container')
console.log(div)

就可以看到打印出了一个 id 为 container 的元素

这个元素就是 DOM 元素

然后去 Elements 中查看,可以发现这些元素呈现层次关系,这个关系正是树形结构

所以文档结构就是 DOM 树,全称 文档对象模型树(Document Object Model)

通过打印原型链,可以发现以下原型关系

Object -> EventTarget -> Node -> Element -> HTMLElement -> HTMLDivElement

显然,每个 DOM 元素都有自己的属性,但属性原则上属于 DOM 元素的一部分,对属性的操作等效于对元素的操作,故而在 Update 中统一描述

Retrieve

查自己

首先要获取,才能做各种操作

对于普通的 DOM 元素,一般有如下 API

  1. document.getElementById(‘xxx’),获取 id 为 xxx 的元素,包括 IE,全都支持
  2. window.xxx(或直接使用 xxx),获取 id 为 xxx 的元素,除了 IE,全都支持
  3. document.getElementsByTagName(‘div’),获取所有标签名为 div 的元素,返回伪数组
  4. document.getElementsByClassName(‘yyy’),获取所有 CSS 类名为 yyy 的元素,返回伪数组
  5. document.querySelector(selector),获取第一个满足传入的 CSS 选择器的元素
  6. document.querySelectorAll(selector),获取所有满足传入的 CSS 选择器的元素,返回伪数组

对于特殊的元素,一般有以下 API

  1. document.documentElement,获取 html 根元素
  2. document.head,获取 head 元素
  3. document.body,获取 body 元素
  4. window,获取窗口(注意窗口不是元素
  5. document.all,获取所有元素

在使用 document.all 的时候需要注意,虽然现在所有浏览器都支持这个查询了,但因为历史遗留原因,虽然能获取到值,但转化为布尔值的时候,在非 IE 的浏览器上都返回 false,只有在 IE 上才返回 true

查祖先

显然除了根结点,每个结点有且仅有一个父结点

那么可以通过 div.parentNode 不断向上查找

查子代

可以通过 div.childNodesdiv.children 来查找

需要注意的是,div.childNodes 返回的是 NodeList,单位类型是 Node,是伪数组,直接 concat 空数组的话不能正常展开,需要用空数组 concat 一个 Array,from 才行

div.children 返回的是 ElementCollection,单位类型均为 Element 的派生类型,与数组操作时注意点同上

并且,div.childNode 返回值包括文本结点等不直接显示的结点,而 div.children 没有这个问题

子代中对特定元素也有专有的方法

  1. 查看第一个子结点,div.firstChild
  2. 查看最后一个子结点,div.lastChild

查兄弟

可以通过先查父结点,再查父结点的子结点来做到,但要排除自己才是兄弟结点

同样,兄弟也有专有方法

  1. 查看相邻的上一个兄弟,div.previousSibling
  2. 查看相邻的下一个兄弟,div.nextSibling

Create

通常使用

1
document.createElement([tagName])

来创建一个指定标签元素

1
document.createTextNode([string])

来创建一个包含指定文本的文本结点

但创建之后的元素或节点,均还在 JS 线程中,必须通过 API 添加到页面上,由渲染线程处理后,才能在页面上显示

通常通过

1
[parentNode].appendChild([childNode])

来将指定的结点添加到某个结点中

关于添加结点,有以下两种特殊情况

  1. 同一结点被多次添加到不同结点的子结点列表中

    此时该子结点会出现在最后一次被添加到的位置

  2. 意图向某结点添加文本

    此时不可以直接 appendChild(string),必须先将 string 转换成文本结点,或用 innerText、textContent 属性来添加文本

    innerText 是 IE 产物,textContent 是其它浏览器产物,但现在所有浏览器都同时支持两个

Delete

一般有两种方法

  1. [parentNode].removeChild([childNode])
  2. [childNode].remove()

对于被从 DOM 树中移除的结点,只要还没丢失对它的引用,就还可以通过 appendChild 再次回到页面上

Update

如同前述,对元素的修改包含对属性的修改

一般分为四种属性

  1. HTML 标签属性
  2. CSS 属性
  3. data 属性
  4. 自定义属性

不论是何种属性,都可以通过点符号进行 get/set

但是 JS 中不能使用连接符来访问,所以在使用连接符的地方,要改用驼峰命名法

HTML 标签属性

直接使用赋值语句改值即可,注意值必须是字符串

img.width = '200px'

但是有的属性不太一样,比如 a 标签的 href 属性

如果通过 a.href 来获取,浏览器会自动补全 href 的根路径,最终获取到的字符串是 http/https 开头的

此时应该通过 a.getAttribute('href') 来获取,能保证获取到的字符串不会被浏览器动手脚

CSS 属性

有 class 和 style 两种

对于 class,使用 [node].className='newClass' 来修改

或使用 [node].classList.add('newClass') 来添加适配的 class

对于 style,可以直接赋值 style 字符串

div.style='width:100px; height: 200px'

也可以针对性赋值,如 div.style.width='100px'

data 属性

常常可以见到形如 data-* 的属性

这些属性类似于 style,可以通过统一入口 dataset 来访问

如属性 data-x-err,可以通过 div.dataset.xErr 来访问

自定义属性

修改方法与 HTML 属性相同,直接通过点符号访问即可

但一般不建议采用不是 data 的自定义属性

因为当目标结点现在在页面中时,对以上三种属性的修改都会直接同步到页面上,但对自定义属性的修改不能同步到页面上

事件

每个元素都会有各种事件,此处以 click 事件为例

要为 click 事件绑定处理函数,可以用如下两种方法

  1. div.onclick=()=>{},此时 div 只能有一个方法,新方法会覆盖旧方法,在冒泡阶段执行

  2. div.addEventListener('click',()=>{},false),此时 div 有若干个方法,新方法与旧方法都会在 click 事件被触发

    此处的第三个参数默认值 false,表示事件在冒泡阶段触发。如果改为 true 就是在捕获阶段触发

    需要注意的是,IE 只有冒泡阶段,并且事件列表执行顺序是 FIFO,其它非 IE 浏览器则有捕获和冒泡,且执行顺序是 LIFO

不论是如何添加事件,在事件被触发时,都有两个默认参数

一个是 this,指向事件触发所在的元素

一个是 event,包含该事件触发时的所有相关信息

文本内容

通过 innerText、textContent 属性来添加文本

innerText 是 IE 产物,textContent 是其它浏览器产物,但现在所有浏览器都同时支持两个

父子结点关系

可以通过结点操作或 innerHTML 注入来实现

例如 div.innerHTML='<span>233</span>' 就是向 div 中插入了一个内容为 233 的 span 标签

注意,此时会覆盖 div 中的所有内容(包括子元素)

如果想添加新儿子,可以使用 appendChild 方法,不再赘述

如果想换个新父结点,则只需要利用 appendChild 会出现在最新位置的特点,直接向新父结点插入当前结点即可

封装

原生 dom 封装

拓展

既然 DOM API 这么复杂,有没有更方便的方法呢?

答案是有,典型的例子就是 JQuery 库

--It's the end.Thanks for your read.--