zoukankan      html  css  js  c++  java
  • 记几个 DOM 操作技巧

    使用 attributes 属性遍历元素特性

    // 迭代元素的每一个特性,将它们构造成 name = value 的字符串形式
    function outputAttributes (element) {
      const pairs = []
      let attrName
      let attrValue
    
      for (let i = 0, len = element.attributes.length; i < len; i++) {
        attrName = element.attributes[i].nodeName
        attrValue = element.attributes[i].nodeValue
        pairs.push(`${attrName}=${attrValue}`)
      }
      return pairs.join(" ")
    }
    

    使用 classList 属性来操作类名

    <div class="bd user disabled">...</div>
    

    这个 <div> 元素一共有三个类名。要从中删除一个类名,需要把这三个类名拆开,删除不想要的那个,然后再把其他类名拼成一个新字符串。请看下面的例子:

    // div.className = 'bd user disabled'
    let classNames = div.className.split(/s+/)
    let pos = -1
    for (let i = 0, len = classNames.length; i < len; i++) {
      if (classNames[i] === 'user') {
        pos = i
        break
      }
    }
    
    classNames.splice(pos, 1) // 删除类名
    div.className = classNames.join(' ') // 把剩下的类名拼接成字符串并重新设置
    

    HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加 classList 属性,这个新类型定义了如下方法:

    • add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
    • contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。
    • remove(value):从列表中删除给定的字符串。
    • toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。

    这样,前面那么多行代码用下面这一行代码就可以代替了

    div.classList.remove("user")
    

    使用 Element Traversal API 操作元素节点

    过去,要遍历某元素的所有子元素,需要像下面这样写代码:

    let child = element.firstChild
    while (child !== element.lastChild) {
      if (child.nodeType === 1) { // 检查是否为元素节点
        processChild(child)
      }
      child = child.nextSibling
    }
    

    Element Traversal API 为 DOM 元素添加了以下 5 个属性:

    • childElementCount:返回子元素(不包括文本节点和注释)的个数。
    • firstElementChild:指向第一个子元素;firstChild 的元素版。
    • lastElementChild:指向最后一个子元素;lastChild 的元素版。
    • previousElementSibling:指向前一个同辈元素;previousSibling 的元素版。
    • nextElementSibling:指向后一个同辈元素;nextSibling 的元素版。

    使用 Element Traversal 新增的 API,代码会更简洁:

    let child = element.firstElementChild
    while (child !== element.lastElementChild) {
      processChild(child) // 肯定是元素节点
      child = child.nextElementSibling // 遍历下一个元素节点
    }
    

    getElementsByTagName('*') 会返回什么?

    最近看到一道面试题:找出页面出现最多的标签,或者说出现次数最多的前 2、3 个标签,可以试着自己实现下。

    // 获取元素列表,以键值对的形式存储为一个对象
    function getElements () {
      // 如果把特殊字符串 "*" 传递给 getElementsByTagName() 方法
      // 它将返回文档中所有元素的列表,元素排列的顺序就是它们在文档中的顺序。
      // 返回一个 HTMLCollection - 类数组对象
      const nodes = document.getElementsByTagName('*')
      const tagsMap = {}
      for (let i = 0, len = nodes.length; i < len; i++) {
        let tagName = nodes[i].tagName
        if (!tagsMap[tagName]) {
          tagsMap[tagName] = 1
        } else {
          tagsMap[tagName] ++
        }
      }
      return tagsMap
    }
    
    // n 为要选取的标签个数 - 即出现次数前 n 的标签名
    // 将上面的方法获取的对象的键值对取出组成数组,按出现次数排序
    function sortElements (obj, n) {
      const arr = []
      const res = []
      for (let key of Object.keys(obj)) {
        arr.push({ tagName: key, count: obj[key] })
      }
    
      // 冒泡
      for (let i = arr.length - 1; i > 0; i--) {
        for (let j = 0; j < i; j++) {
          if (arr[j].count < arr[j + 1].count) { // 升序
            swap(arr, j, j + 1)
          }
        }
      }
    
      for (let i = 0; i < n; i++) {
        res.push(arr[i].tagName)
      }
      return res
    }
    
    function swap (arr, index1, index2) {
      let temp = arr[index1]
      arr[index1] = arr[index2]
      arr[index2] = temp
    }
    
    let res = sortElements(getElements(), 2)
    console.log(res)
    

    动态添加脚本与样式

    // 动态添加脚本
    function loadScript (url) {
      var script = document.createElement("script")
      script.type = "text/javascript"
      script.src = url
      document.body.appendChild(script)
    } 
    
    // 动态添加样式
    function loadStyles (url) {
      var link = document.createElement("link")
      link.rel = "stylesheet"
      link.type = "text/css"
      link.href = url
      var head = document.getElementsByTagName("head")[0]
      head.appendChild(link)
    } 
    

    使用 contains() 方法判断某个节点是否为另一个节点的后代

    contains() 方法用于判断某个节点是否为另一个节点的后代,调用 contains() 方法的应该是祖先节点,也就是搜索开始的节点,这个方法接收一个参数,即需要检测的节点。

    console.log(document.documentElement.contains(document.body)) // true
    

    这个例子检测了 <body> 元素是不是 <html> 元素的后代

    Element.getBoundingClientRect() 及 dataset 的使用

    这是 小册 上的一个例子,使用原生 JS 实现图片懒加载,需要了解这两个知识点

    1.Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。具体解释及用法参考 MDN

    通过 Element.getBoundingClientRect().top
    window.innerHeight(当前视窗的高度)比较就可以判断图片是否出现在可视区域。

    注意这个 top 是相对于当前视窗的顶部的 top 值而不是一开始的顶部。

    2.通过 Element.dataset 可以获取到为元素节点添加的 data-* 属性,我们可以通过这个属性来保存图片加载时的 url。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <style>
            * {
                padding: 0;
                margin: 0;
            } 
            .box-image {
              display: flex;
              flex-direction: column;
              align-items: center;
            }
            img {
                display: inline-block;
                height: 300px;
                margin-bottom: 20px;
            }
        </style>
    </head>
    
    <body>
        <div class="box-image">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/6.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/8.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/aotu-10.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/aotu-15.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/05/%E7%BD%AA%E6%81%B6%E7%8E%8B%E5%86%A04.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/05/%E7%BD%AA%E6%81%B6%E7%8E%8B%E5%86%A06.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/9.jpg" alt="">
            <img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/aotu-16.jpg" alt="">
        </div>
        <script>
            var viewHeight = document.documentElement.clientHeight;
            // 节流:加一个 300ms 的间隔执行
            function throttle(fn, wait) {
              let canRun = true
              return function (...args) {
                if (!canRun) return
                canRun = false 
                setTimeout(() => {
                  fn.apply(this, args)
                  canRun = true
                }, wait)
              }
            }
            function lazyload() {
              let imgs = document.querySelectorAll('img[data-original][lazyload]') // 获取文档中所有拥有 data-original lazyload 属性的<img>节点
              imgs.forEach(item => {
                if (item.dataset.original == '') {// HTMLElement.dataset 访问在 DOM 中的元素上设置的所有自定义数据属性(data-*)集。
                  return
                }
                // 返回一个 DOMRect 对象,包含了一组用于描述边框的只读属性——left、top、right 和 bottom,
                // 单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。
                let rect = item.getBoundingClientRect()
                // 其 top 值是相对于当前视窗的顶部而言的而不是绝对的顶部,所以 top 值 < window.innerHeight 的话图片就出现在底部了就需要加载
                if (rect.bottom >= 0 && rect.top < viewHeight) {
                  let img = new Image()
                  img.src = item.dataset.original
                  // 图片加载完成触发 load 事件
                  img.onload = function () {
                    item.src = img.src
                  }
                  // 移除属性的话就不会重复加载了
                  item.removeAttribute('data-original')
                  item.removeAttribute('lazyload')
                }
              })
            }
            // 先调用一次加载最初显示在视窗中的图片
            lazyload();
            let throttle_lazyload = throttle(lazyload, 300)
            document.addEventListener('scroll', throttle_lazyload)
        </script>
    </body>
    </html>
    

    如何渲染几万条数据且不卡住页面?

    这也是小册上的,考察了利用文档碎片 (createDocumentFragment) 分批次插入节点

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
      </head>
      <body>
        <ul>
          控件
        </ul>
        <script>
          const total = 100000 // 10万条数据
          const once = 20      // 每轮插入的数据条目
          const loopCount = total / once // 渲染总次数
          let countOfRender = 0
          let ul = document.querySelector('ul')
          function add() {
            // 使用文档碎片优化性能
            const fragment = document.createDocumentFragment()
            for (let i = 0; i < once; i++) {
              const li = document.createElement('li')
              li.innerText = Math.floor(Math.random() * total)
              fragment.appendChild(li)
            }
            ul.appendChild(fragment)
            countOfRender+=1
            loop()
          }
          function loop() {
            if (countOfRender < loopCount) {
              window.requestAnimationFrame(add) // 使用 requestAnimationFrame 每隔 16ms(浏览器自己选择最佳时间)刷新一次
            }
          }
        </script>
      </body>
    </html>
    
  • 相关阅读:
    selenium WebDriver 清空input的方式
    selenium 获取input输入框中的值的方法
    webdriver报不可见元素异常方法总结
    git踩过的坑
    what's the 数据结构
    算法进阶——贪心与动态规划
    what's the 二叉树
    算法基础——列表排序
    算法基础——列表查找
    Flask项目示例目录
  • 原文地址:https://www.cnblogs.com/cmk1018/p/11347635.html
Copyright © 2011-2022 走看看