【JS编程接口】DOM 编程

351 阅读9分钟

一、什么是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

  1. 在 HTML DOM 中,所有事物都是节点。
  2. 常见节点
  • 元素Element(标签Tag)节点--1------详见三、四
  • 文本Text节点--3
  • 注释Comment节点--8
  • 文档Document节点--9
  1. x.nodeType可以查看x是哪种节点,返回数字。
  2. HTML DOM 将 HTML 文档视作树结构。这种结构被称为节点树:

5. 节点树中的节点彼此拥有层级关系。

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

三、如何获取元素

(一)获取任意元素

1、偷偷用的最简便方法

  • window.id
  • id
  • 但是如果id为保留字,比如parent,就不能用了。只能用下面的了

2、兼容IE才会用的方法

  • document.getElementByld('id')
  • document.getElementsByTagName('标签名')[0](因为得到的是所有的这个标签,所以是个数组,你要自己选择需要该数组中的第几个)
  • document.getElementsByClassName('class名')[0](同上)

3、工作中用的方法

  • document.querySelector('80%情况下css选择器咋写就咋写')
  • document.querySelectorAll('.red')[0]

(二)获取特定元素

  1. 获取根元素html元素: document.documentElement
  2. 获取head元素: document.head
  3. 获取body元素: document.body
  4. 获取窗口(不是一个元素):window
  5. 获取所有元素: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)看原型链

  1. 自身属性: classNameidstyle 等等
  2. 第一层原型HTMLDivElement.prototype: 这里面是所有div共有的属性,不用细看
  3. 第二层原型HTMLElement.prototype: 这里面是所有HTML标签共有的属性,不用细看
  4. 第三层原型Element.prototype: 这里面是所有XML、HTML 标签的共有属性,你不会以为浏览器 只能展示HTML吧
  5. 第四层原型Node.prototype: 这里面是所有节点共有的属性,节点包括XML标签文本注释、 HTML标签文本注释等等
  6. 第五层原型EventTarget.prototype: 里面最重要的函数属性是addEventListener
  7. 最后一层原型就是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或者bodydocument.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.addEventListenerdiv.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.parentNode
  • node.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多次操作,浏览器可能会合并成一 次操作,也可能不会(之前在动画里提到过)

属性同步

  1. 标准属性
  • 对div1的标准属性的修改,会被浏览器同步到页面中
  • 比如id、className、 title 等
  1. data-*属性 同上
  2. 非标准属性
  • 对非标准属性的修改,则只会停留在JS线程中,不会同步到页面里
  • 比如x属性,示例代码
  1. 启示
  • 如果你有自定义属性,又想被同步到页面中,请使用
  • data-作为前缀

Property V.S. Attribute

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