dmz社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 811|回复: 0

[Html/Css] 前端最佳实践——DOM操作

[复制链接]
  • TA的每日心情

    2024-2-20 11:15
  • 签到天数: 11 天

    [LV.3]偶尔看看II

    878

    主题

    4343

    帖子

    3987

    积分

    终身会员[A]

    Rank: 7Rank: 7Rank: 7

    积分
    3987

    发表于 2020-4-9 16:00:02 | 显示全部楼层 |阅读模式

    本站资源全部免费,回复即可查看下载地址!

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    1、浏览器渲染原理
    在讲DOM操作的最佳性能实践之前,先介绍下浏览器的基本渲染原理。
    分为以下四个步骤:
    • 解析HTML(HTML Parser)
    • 构建DOM树(DOM Tree)
    • 渲染树构建(Render Tree)
    • 绘制渲染树(Painting)

    浏览器请求解析(Parser) HTML 文档,并将各标记逐个转化成 DOM 节点(DOM Tree)。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树(Render Tree)。呈现树(Render Tree)包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。呈现树(Render Tree)构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制(Painting) - 浏览器会遍历呈现树(Render Tree),由用户界面后端层将每个节点绘制出来。
    需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,浏览器会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,浏览器会将部分内容解析并显示出来。
    2、Repaints and reflows
    Repaint:可以理解为重绘或重画,当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,例如改变背景颜色 。则就叫称为重绘。
    Reflows:可以理解为回流、布局或者重排,当渲染树(render Tree)中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow),也就是重新布局(relayout)。
    回流或者重绘何时触发?
    改变用于构建渲染树的任何内容都可能导致重绘或回流,例如:
    1、添加,删除,更新DOM节点
    2、用display: none(回流和重绘)或者visibility: hidden隐藏节点(只有重绘,因为没有几何更改)
    3、添加样式表,调整样式属性
    4、调整窗口大小,更改字体大小
    5、页面初始化的渲染
    6、移动DOM元素
    。。。
    我们来看几个例子:
    [HTML] 纯文本查看 复制代码
    var bstyle = document.body.style; // cache
     
    bstyle.padding = "20px"; // reflow, repaint
     
    bstyle.border = "10px solid red"; // another reflow and a repaint
     
    bstyle.color = "blue"; // repaint only, no dimensions changed
     
    bstyle.backgroundColor = "#fad"; // repaint
     
    bstyle.fontSize = "2em"; // reflow, repaint
     
    // new DOM element - reflow, repaint
     
    document.body.appendChild(document.createTextNode('dude!'));

    我们可以想象一下,如果直接在渲染树(render Tree)最后面增加或者删除一个节点,这对于浏览器渲染页面来说无伤大雅,因为只需要在渲染树(render Tree)的末端重绘那一部分变动的节点。但是,如果是在页面的顶部变动一个节点,浏览器需要重新计算渲染树(render Tree),导致渲染树(render Tree)的一部分或全部发生变化。渲染树(render Tree)重新建立后,浏览器会重新绘制页面上受影响的元素。重排的代价比重绘的代价高很多,重绘会影响部分的元素,而重排则有可能影响全部的元素。

    3、DOM操作最佳实践
    DOM操作带来的页面 Repaints 和 Reflows 是不可避免的,但可以遵循一些最佳实践来最大限度地减少Repaints 和 Reflows。如下是一些具体的实践方法:
    3.1、合并多次的DOM操作
    [HTML] 纯文本查看 复制代码
    // bad
     
    var left = 10,
     
    top = 10;
     
    el.style.left = left + "px";
     
    el.style.top = top + "px";
    
    // better
     
    el.className += " theclassname";
     
    // better
     
    el.style.cssText += "; left: " + left + "px; top: " + top + "px;";



    由于与渲染树更改相关的 Repaints and Reflows 是代价非常高,因此现代浏览器针对频繁的 Repaints and Reflows 有性能的优化。 一个策略是浏览器将设置脚本所需更改的队列,并分批执行。 这样,每个需要 Reflows 的几个变化将被组合,并且将仅计算一个 Reflows 。 浏览器可以添加排队的更改,然后在一定时间过去或达到一定数量的更改后刷新队列(并不是所有的浏览器都存在这样的优化。推荐的方式是把DOM操作尽量合并)。但有时脚本可能会阻止浏览器优化 Reflows ,并使其刷新队列并执行所有批量更改。 当您请求如下样式信息时(并非包含全部),会发生这种情况。见下图:
    以上所有这些基本上都是请求有关节点的样式信息,浏览器必须提供最新的值。 为了做到这一点,它需要应用所有计划的更改,刷新队列,强行回流。所以在有大批量DOM操作时,应避免获取DOM元素的布局信息,使得浏览器针对大批量DOM操作的优化不被破坏。如果需要这些布局信息,最好是在DOM操作之前就去获取。

    [HTML] 纯文本查看 复制代码
    //bad
     
    var bstyle = document.body.style;
     
    bodystyle.color = 'red';
     
    tmp = computed.backgroundColor;
    
    bodystyle.color = 'white';
     
    tmp = computed.backgroundImage;
     
    bodystyle.color = 'green';
     
    tmp = computed.backgroundAttachment;
     
    
    //better
     
    tmp = computed.backgroundColor;
     
    tmp = computed.backgroundImage;
     
    tmp = computed.backgroundAttachment;
     
     
    bodystyle.color = 'yellow';
     
    bodystyle.color = 'pink';
     
    bodystyle.color = 'blue';

    3.2、让DOM元素脱离渲染树(render Tree)后修改
    (1)使用文档片段
    DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的孩子所代替。因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(Reflow)。当然,最后一步把文档片段附加到页面的这一步操作还是会造成回流(Reflow)。


    [HTML] 纯文本查看 复制代码
    var fragment = document.createDocumentFragment();
     
    // 一些基于fragment的大量DOM操作
     
    ...
     
    document.getElementById('myElement').appendChild(fragment);

    (2)通过设置DOM元素的display样式为none来隐藏元素
    原理是先隐藏元素,然后基于元素做DOM操作,经过大量的DOM操作后才把元素显示出来。

    [HTML] 纯文本查看 复制代码
    var myElement = document.getElementById('myElement');
     
    myElement.style.display = 'none';
     
    // 一些基于myElement的大量DOM操作
     
    ...
     
    myElement.style.display = 'block';

    (3)克隆DOM元素到内存中
    这种方式是把页面上的DOM元素克隆一份到内存中,然后再在内存中操作克隆的元素,操作完成后使用此克隆元素替换页面中原来的DOM元素。

    [HTML] 纯文本查看 复制代码
    1 var old = document.getElementById('myElement');
    2 var clone = old.cloneNode(true);
    3 // 一些基于clone的大量DOM操作
    4 ...
    5 old.parentNode.replaceChild(clone, old);

    3.3、使用局部变量缓存样式信息
    获取DOM的样式信息会有性能的损耗,所以如果存在循环调用,最佳的做法是尽量把这些值缓存在局部变量中。

    [HTML] 纯文本查看 复制代码
    // bad
     
    function resizeAllParagraphsToMatchBlockWidth() {
     
    for (var i = 0; i < paragraphs.length; i++) {
     
    paragraphs[i].style.width = box.offsetWidth + 'px';
     
    }
     
    }
     
     
     
    // better
     
    var width = box.offsetWidth;
     
    function resizeAllParagraphsToMatchBlockWidth() {
     
    for (var i = 0; i < paragraphs.length; i++) {
     
    paragraphs[i].style.width = width + 'px';
     
    }
     
    }




    3.4、 设置具有动画效果的DOM元素为固定定位
    使用绝对定位使得该元素在渲染树中成为 body 下的一个直接子节点,因此当它进行动画时,它不会影响太多其他节点。
    4、dom操作性能查看4.1.1、首先用谷歌浏览器打开如上的链接。按下F12,切换到Performance选项4.1.2、按下ctrl + E(或者点击小圆点)开始录制,点击 body 区域,待文字变成绿色后点击“stop”停止录制4.1.3、选中上图中蓝色(js堆)突然升高的部分,表示刚才点击body的过程,滚动鼠标放大主线程4.1.4、点击圆点旁边的clear按钮清空,重复上述的操作,直到文字变蓝色停止:
    4.2、频繁回流造成的影响优化前的代码:
    [HTML] 纯文本查看 复制代码
    var pos = m.classList.contains('down') ?
     
    m.offsetTop + distance : m.offsetTop - distance;
     
    if (pos < 0) pos = 0;
     
    if (pos > maxHeight) pos = maxHeight;
     
    m.style.top = pos + 'px';
     
    if (m.offsetTop === 0) {
     
    m.classList.remove('up');
     
    m.classList.add('down');
     
    }
     
    if (m.offsetTop === maxHeight) {
     
    m.classList.remove('down');
     
    m.classList.add('up');
     
    }


    优化后的代码:
    [HTML] 纯文本查看 复制代码
    var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px')));
     
    m.classList.contains('down') ? pos += distance : pos -= distance;
     
    if (pos < 0) pos = 0;
     
    if (pos > maxHeight) pos = maxHeight;
     
    m.style.top = pos + 'px';
     
    if (pos === 0) {
     
    m.classList.remove('up');
     
    m.classList.add('down');
     
    }
     
    if (pos === maxHeight) {
     
    m.classList.remove('down');
     
    m.classList.add('up');
     
    }



    先节流cpu,然后加多小“谷歌”图标,直到图标速度明显减慢,再点击“Optimize”优化按钮,可以明显感受出差距。









    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|小黑屋|本站代理|dmz社区

    GMT+8, 2024-12-23 20:13 , Processed in 0.191831 second(s), 33 queries .

    Powered by Discuz! X3.4 Licensed

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表