DOM编程

Posted zhangzhengsmiling

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DOM编程相关的知识,希望对你有一定的参考价值。

DOM编程

  首先,需要明确javascript访问DOM性能的开销所在。在浏览器中。对于DOM和ECMAScript的支持一般是各自独立实现的,因此通过ECMAScript访问DOM自然而然就会产生较大的开销,而且对于DOM的访问越频繁,性能的开销越大。

  1. 尽量将运算放在ECMAScript中进行,避免操作DOM进行迭代更新

     function addString = function (strArr) {
         strArr.forEach(str => {
             document.getElementById(‘id‘).innerhtml += str; // 读取DOM的属性,同时进行更新
         })
     }
     // 当需要进行DOM节点更新是可能会不自觉的使用以上的方法进行更新(至少我可能会这样写的)
     // 这种方法会带来额外的性能开销,因为在每一次循环遍历中,都访问了DOM中的innerHTML属性,同时又对其进行了更新操作,而当字符串的数量较大时,这个开销将会被放大
     ?
     ?
     function addString2 = function (strArr) {
         let string = ‘‘;
         strArr.forEach(str => string += str)
         document.getElementById(‘id‘).innerHTML += string;
     }
     // 第二种方式中,将字符串的拼接放在ECMAScript中完成,这样对于DOM的操作就只有一次读取和更新DOM的操作了,而这样正是避免了对DOM的迭代更新。
  2. HTMLCollection与NodeList

    在看这本书之前,我其实一直以为HTML集合类型与NodeList类型是一样的,而且原生获取DOM元素使用的夜一直是document.getElement...方法。

    HTML集合

    • 包含DOM节点引用的伪数组对象,不能直接使用forEach方法进行遍历

    • document.getElement...方法返回的是一个HTML集合对象

    • HTML集合会随着界面中的DOM元素进行实时更新的,与文档对象始终保留连接。

       const divs = document.getElementByTagName(‘div‘);
       // divs --> HTML集合
       // 当删除界面中的某一个div标签时,divs中也会移除相对应的div元素
       console.log(divs.length);
       // 当需要访问divs.length属性时,由于HTML集合"不知道界面中的元素是否有删除或者增加",为了返回"实时"的divs当前长度,需要对文档中的divs进行遍历。即使文档中的div元素没有任何更新,这一步依旧无法避免,因此就带来了额外的性能开销

      由于HTMLCollection的实时更新而带来的一个意外的死循环

       let divs = document.getElementsByTagName(‘div‘);
       for (let i = 0; i < divs.length; i++) {
           document.body.appendChild(document.createElement(‘div‘));
       }
       // 你写这段的代码的本意可能想使界面中的div加倍,但最后的结果却是界面加载不出来
       // 原因正是在于HTMLCollection的自动更新,当为body添加一个div标签时,此时会更新divs.length,而每一次通过divs.length获取到的是最新的(即更新之后的div的数目),因此循环永远也不会终止。
    • 尽量避免在迭代过程中读取HTML集合的length属性,因为每次都要查询DOM以获取当前的集合长度,在循环中可以将HTMLCollection的length属性缓存避免其迭代更新

    • 访问HTMLCollection的速度比访问数组慢得多,如有必要可以将HTMLCollection转化为数组

    NodeList

    • 首先,NodeList是一个数组类型,可以直接使用forEach进行遍历

    • 不同于HTMLCollection,NodeList类型不会随着界面DOM元素的更新而变化,因此访问效率会高于HTMLCollection

    • document.querySelector(),document.querySelectorAll()方法返回的是NodeList对象

       let divs = document.querySelectorAll(‘div‘);
       for (let i = 0; i < divs.length; i++) {
           document.body.appendChild(document.createElement(‘div‘));
       }
       // 由于NodeList不再进行实时更新因此,同样的代码不再产生死循环
    • 当界面中需要用到大量组合查询时,querySelector()与querySelectorAll()方法会比较简单,可以支持CSS选择器

  3. 重绘与重排

    重排

    • 发生时机:元素的几何和位置属性发生变化时,会触发界面重排

    重绘

    • 界面发生了改变(包括元素的位置,几何形状,颜色等等属性),浏览器就会重新绘制界面

    1. 防止在更新界面的过程中调用一下属性:

      • offsetTop, offsetLeft, offsetWidth, offsetHeight

      • scrollTop, scrollLeft, scrollWidth, scrollHeight

      • clientTop, clientLeft, clientWidth, clientHeight

      • getComputedStyle(), currentStyle() ---> IE

      上述属性需要返回最新的布局信息,因此会触发立即更新,强制执行界面更新以返回正确的布局信息

    2. 防止在更新界面信息时读取界面信息,读取操作将会触发当前渲染队列的更新

       const div = document.getElementById(‘id‘);
       div.style.backgroundColor = ‘red‘;
       div.style.backgroundColor = ‘green‘;
       div.style.backgroungColor = ‘yellow‘;
       ?
       console.log(div.style.backgroundColor); // 在此时读取样式值,由于javascript解析的速度极快,因此div的背景颜色在很短的时间内被修改了两次,但界面的重绘只会发生一次,即由红色变为黄色
       ?
       div.style.backgroundColor = ‘red‘;
       div.style.backgroundColor = ‘green‘;
       ?
       console.log(div.style.backgroundColor);
       // 由于在此处JavaScript访问了div的backgroundColor属性,为了返回当前div的正确颜色,将会触发div元素的重绘,即从红色变为绿色的过程,增加了界面渲染的开销
       // 当然,在此处获取backgroudColor属性是没有意义的,因为很快就会被覆盖,此处仅仅为了说明这样一个问题:不要在界面样式改变的过程中访问元素的属性值,这样将会触发界面的重绘,增加开销
       div.style.backgroungColor = ‘yellow‘;
       ?
       console.log(div.style.backgroundColor);
    3. 最小化重绘和重排

      • 合并多次对DOM的修改,一次性处理掉,在DOM修改的过程中,避免读取元素的样式,可以通过修改csscText属性或者通过修改类名的方式来修改样式实现

      • 批量更新DOM

        当需要对DOM进行多次修改时(大于两次),此时可以采用一种通用的模式来更新DOM

        1. 使元素脱离文档流 ---> 脱离时会触发界面重排和重绘

        2. 对元素进行多重更新

        3. 将元素带回文档流 ---> 带回文档流时也需要进行重排和重绘

        使元素脱离文档流的三种方法:

        • 隐藏元素,应用修改,重新显示

        • 使用DocumentFragment

        • 将元素拷贝到一个脱离文档流的节点中,修改DOM,然后替换回去

        hover伪类选择器将会降低页面的响应速度,不推荐大量使用

        事件委托

        • 事件委托是基于事件冒泡(事件由源对象向父级逐级传递)原理

        • 绑定事件处理函数是需要代价的,因此需要尽量将大量的“<li></li>”上的事件委托给父元素,然后通过时间源对象的判断对相应的DOM元素响应事件回调函数

以上是关于DOM编程的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript单行代码,也就是代码片段

实用代码片段将json数据绑定到html元素 (转)

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

深入理解DOM节点类型第四篇——文档片段节点DocumentFragment

使用 Pygments 检测代码片段的编程语言