夜听城嚣 夜听城嚣
首页
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 前端基建与架构
  • 专题分享

    • Git入门与开发
    • 前端面试题汇总
    • HTML和CSS知识点
  • 项目实践
  • 抓包工具
  • 知识管理
  • 工程部署
  • 团队规范
bug知多少
  • 少年歌行
  • 青年随笔
  • 文海泛舟
  • 此事躬行

    • 项目各工种是如何协作的
    • TBA课程学习
收藏

dwfrost

前端界的小学生
首页
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 前端基建与架构
  • 专题分享

    • Git入门与开发
    • 前端面试题汇总
    • HTML和CSS知识点
  • 项目实践
  • 抓包工具
  • 知识管理
  • 工程部署
  • 团队规范
bug知多少
  • 少年歌行
  • 青年随笔
  • 文海泛舟
  • 此事躬行

    • 项目各工种是如何协作的
    • TBA课程学习
收藏
  • 第1章 什么是JavaScript
  • 第2章 HTML中的JavaScript
  • 第3章 语言基础
  • 第4章 变量、作用域与内存
  • 第5章 基本引用类型
  • 第6章 集合引用类型
  • 第7章 迭代器与生成器
  • 第8章 对象、类与面向对象编程
  • 第9章 代理与反射
  • 第10章 函数
  • 第11章 异步编程
  • 第12章 BOM
  • 第14章 DOM
    • 第15章 DOM扩展
    • 第16章 DOM2和DOM3
    • 第17章 事件
    • 第18章 动画与Canvas图形
    • 第19章 表单脚本
    • 第20章 JavaScript API
    • 第21章 错误处理与调试
    • 第23章 JSON
    • 第24章 网络请求与远程资源
    • 第25章 客户端存储
    • 第26章 模块
    • 第27章 工作者进程
    • 第28章 最佳实践
    • 《JavaScript高级程序设计》
    frost
    2022-01-05

    第14章 DOM

    DOM:文档对象模型,是 html 的编程接口。从上到下依次是

    • document
      • html
        • head
        • body

    # Node 类型

    每个节点都有 nodeType 属性,表示该节点的类型,共有 12 种类型,是 12 个数值常量。

    开发中最常见的是元素节点和文本节点。

    # 节点关系

    每个节点都有 childNodes 属性,对应 NodeList 实例,是一个类数组对象,可以使用下面的方式转为真数组。

    let nodes = Array.prototype.slice.call(someNode.childNodes, 0)
    
    let nodes = Array.from(someNode.childNodes)
    
    1
    2
    3

    每个节点都有 parentNode 属性,指向其 DOM 树的父元素。

    previousSibling 指向节点的上一个同胞节点,nextSibling 指向节点的下一个同胞节点。

    父节点有 2 个专门属性,firstChild 表示第一个子节点,lastChild 表示最后一个子节点。

    利用上述的节点关系,可以访问到文档树中的任何节点。

    # 操纵节点

    关系指针只能读,下面介绍几种操纵节点的方法。

    • appendChild()。用于在 childNodes 列表末尾添加节点,返回新添加的节点。如果添加的节点在文档中已存在,则这个节点会从之前的位置转移到新位置。

      let returnedNode = someNode.appendChild(newNode)
      console.log(returnedNode === newNode) // true
      console.log(someNode.lastChild === returnedNode) // true
      
      let returnedNode = someNode.appendChild(someNode.firstChild)
      console.log(returnedNode === someNode.firstChild) // false
      console.log(someNode.lastChild === returnedNode) // true
      
      1
      2
      3
      4
      5
      6
      7
    • 如果想在特定位置插入节点,可以使用 insertBefore(),接收 2 个参数:要插入的节点和参照节点。返回被插入的节点。

      let returnedNode = someNode.insertBefore(newNode, null)
      console.log(returnedNode === someNode.lastChild) // true
      
      let returnedNode = someNode.insertBefore(newNode, someNode.firstChild)
      console.log(returnedNode === newNode) // true
      console.log(returnedNode === someNode.firstChild) // true
      
      1
      2
      3
      4
      5
      6
    • replaceChild()则会替换节点。接收 2 个参数:要插入的节点和要替换的节点。被替换的节点将从文档完全移除。

      let returnedNode = someNode.replaceChild(newNode, someNode.firstChild)
      
      1
    • removeChild()则会移除该节点。

      let returnedNode = someNode.removeChild(someNode.firstChild)
      
      1

    这 4 个方法用于操作某个节点的子元素,也就是说,必须先取得父节点。

    此外,还有 2 个 DOM 方法,cloneNode()和 normalize(),就不介绍了。

    # Document 类型

    html 是根元素,获取方式有如下几种

    • document.documentElement
    • document.firstChild
    • document.childNodes[0]

    可以通过document.body访问 body 元素。

    document.title 可读可写,通常显示在页面标题栏。

    document.URL 显示的页面完整的 URL,基本和 location.href 一样,只读。

    document.domain 可读可写域名,注意不能设置 URL 中不包含的值,即只能放松,不能收紧。

    // 页面是test.tencent.com
    document.domain = 'tencent.com' // ok
    document.domain = 'test.tencent.com' // 设置回去,出错
    
    1
    2
    3

    document.getElementById():最经典的获取 DOM 方式,根据 id 获取。

    document.getElementByTagName():获取标签元素。

    document.querySelector():selectors API 经典获取 DOM 方法,推荐。

    document.querySelectorAll():查询所有匹配的节点,返回的是 NodeList 列表的静态快照。

    # Element 类型

    该节点有如下特点:

    • nodeType 等于 1
    • nodeName 值为元素的标签名
    • nodeValue 为 null

    HTML 元素拥有的属性有:

    • id,元素在文档中的唯一标识符
    • title,鼠标悬停时的提示信息
    • className,指元素的 CSS 类

    属性相关的 DOM 方法有 3 类:

    • getAttribute():获取属性。自定义属性名一般写data-前缀。特殊类型有 2 类,1 是 style 属性,2 是事件处理程序。所以一般用该方法获取自定义属性。
    • setAttribute():设置属性。有则替换,无则创建。
    • removeAttribute():删除属性。是从元素中抹掉该属性,而不只是清除属性值。

    创建元素:

    • document.createElement():之后可以给创建的属性添加属性和子元素,也可以将其挂载到文档树上。

      let div = document.createElement('div')
      div.id = 123
      div.className = 'hello'
      div.innerHTML = '我是一个div'
      
      document.body.appendChild(div)
      
      1
      2
      3
      4
      5
      6

      得到如下 html

      <body>
        <div id="123" class="hello">我是一个div</div>
      </body>
      
      1
      2
      3

    # Text 类型

    该节点有如下特征:

    • nodeType 等于 3
    • nodeName 为'#text'
    • nodeValue 为节点中包含的文本
    • 不支持子节点

    除了 nodeValue,也可以使用 data 属性访问和设置 Text 节点中的文本。

    假设有一个 div

    <div>我是一个div</div>
    
    1
    let textNode = div.firstChild
    console.log(textNode.nodeValue === textNode.data) // true
    textNode.nodeValue = 'hello' // 文本改为 hello
    textNode.data = 'world' // 文本改为 world
    
    1
    2
    3
    4

    创建文本节点:

    • document.createTextNode()

    一般一个元素只包含一个文本子节点,实际可以有多个。

    • normalize():将所有同胞文本节点合并为一个子节点。
    • splitText():与 normalize()相反,指定在偏移位置拆分 nodeValue,形成 2 个文本节点。

    # DocumentFragment 类型

    该节点具有如下特征:

    • nodeType 等于 11
    • nodeName 为'#document-fragment'
    • 不能直接把文档片段添加到文档,它不存在真实的文档树上。
    • 如果文档中的一个节点被添加到文档片段,则该节点会从文档树中移除。
    • 如果把文档片段添加到文档,则该文档片段的所有子节点都会转移到文档树,文档片段本身不会。

    可以看出,文档片段的作用是充当其他要被添加到文档的节点的仓库,一般在优化 DOM 性能时,为避免多次渲染使用到。

    比较下面 2 段代码。

    // 代码1
    const ul = document.querySelector('ul')
    
    for (let i = 0; i < 3; i++) {
      const li = document.createElement('li')
      li.appendChild(document.createTextNode(`Item ${i + 1}`))
      ul.appendChild(li)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    // 代码2
    const ul = document.querySelector('ul')
    const fragment = document.createDocumentFragment()
    
    for (let i = 0; i < 3; i++) {
      const li = document.createElement('li')
      li.appendChild(document.createTextNode(`Item ${i + 1}`))
      fragment.appendChild(li)
    }
    ul.appendChild(fragment)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    代码 1 和代码 2 都会呈现相同的结果,不同点在于代码 1 会渲染 3 次(每次添加列表项时,重新渲染新添加的内容),而代码 2 只渲染 1 次(使用文档片段添加所有列表项,并 没有 DOM 消耗,然后一次性添加到 ul 元素),减少了 DOM 操作。

    # DOM 编程

    # 动态脚本

    有 2 种方式为网页添加脚本:

    • 引入外部文件

      <script src="foo.js"></script>
      
      // 或者通过DOM方法
      <script>
        let script = document.createElement('script')
        script.src = 'foo.js'
        document.body.appendChild(script)
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
    • 嵌入源代码

      <script>
        console.log('hello')
      </script>
      
      1
      2
      3

    注意,通过 innerHTML 属性创建的<script>元素永远不会执行。比较下面两段代码。

    const div1 = document.createElement('div')
    
    let script = document.createElement('script')
    script.innerHTML = `console.log('hello')`
    div1.appendChild(script)
    
    document.body.appendChild(div1)
    // 输出hello
    
    1
    2
    3
    4
    5
    6
    7
    8
    const div1 = document.createElement('div')
    
    div1.innerHTML = `
      <script>console.log('hello')<\/script>
      `
    
    document.body.appendChild(div1)
    // 无输出
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 动态样式

    CSS 样式可以通过两种方式加载。

    • <link>元素

      <link rel="stylesheet" href="style.css" />
      
      1
    • <style>元素

      <style>
        div {
          width: 100px;
          height: 100px;
        }
      </style>
      
      1
      2
      3
      4
      5
      6

    # MutationObserver 接口

    MutationObserver 可以在 DOM 被修改时异步执行回调。可以用于观察整个文档、DOM 树的一部分,或者某个元素。同时还可以观察属性、子节点、文本等变化。

    # 基本用法

    1. observe()

      调用该方法来进行观察,传入 2 个参数:要观察的 DOM 节点,MutationObserverInit 对象。

    1. MutationObserverInit 对象

      属性 说明
      subtree 是否观察目标节点的子树,默认 false
      attributes 是否观察目标节点的属性变化,默认 false
      attributeFilter 可以过滤的属性数组,默认观察所有属性
      attributeOldValue MutationRecord 是否记录变化之前的属性值,默认 false
      characterData 是否观察文本节点中字符的变化,默认 false
      characterDataOldValue MutationRecord 是否记录变化之前的字符数据,默认 false
      childList 修改目标子节点是否会触发变化事件,默认 false
    2. 回调与 MutationRecord

      节点的变化会触发回调,变化的信息保存在 MutationRecord 实例中,然后被添加到记录队列。记录队列是 DOM 变化事件的有序列表,回调结束后清空队列。

      某个属性或节点进行连续修改的变化,会生成多个 MutationRecord 实例。

    3. disconnect()与重新使用

      可以调用 disconnect()提前终止执行回调,但它不会结束 MutationObserver 的生命,如果再次调用 observe(),将会重新使用这个观察者。

      <style>
        div {
          width: 100px;
          height: 100px;
        }
      </style>
      
      <body>
        <button>点我</button>
        <div></div>
      </body>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const button = document.querySelector('button')
      const div = document.querySelector('div')
      button.onclick = function() {
        let { background } = div.style
        background = background === 'pink' ? 'skyblue' : 'pink'
        div.style.background = background
      
        // 连续修改会生成多个MutationRecord
        div.id = '1'
        div.id = '2'
      
        div.setAttribute('foo', 'bar')
        div.setAttribute('foo', 'baz')
        div.removeAttribute('foo')
      }
      
      let observer = new MutationObserver((record) => {
        // console.log(record)
        for (let item of record) {
          console.log(`${item.attributeName} was changed`)
        }
      })
      observer.observe(div, { attributes: true })
      
      // 抛弃执行回调
      observer.disconnect()
      // 重新使用观察者
      observer.observe(div, { attributes: true })
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28

    # 引用关系

    MutationObserver 实例与目标节点之间的引用关系是非对称的。

    MutationObserver 拥有对要观察的目标节点的弱引用,而目标节点拥有对 MutationObserver 的强引用,所以会影响到垃圾回收 MutationObserver。除非目标节点从 DOM 中移除 ,MutationObserver 才会被回收。

    #前端笔记
    上次更新: 2022/01/14, 08:52:26
    第12章 BOM
    第15章 DOM扩展

    ← 第12章 BOM 第15章 DOM扩展→

    最近更新
    01
    提交代码时修改commit消息
    04-09
    02
    如何快速定位bug
    02-20
    03
    云端web项目开发踩坑
    08-25
    更多文章>
    Theme by Vdoing | Copyright © 2021-2025 dwfrost | 粤ICP备2021118995号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×