3.1 浏览器中的DOM

文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档操作的程序接口(API)。在浏览器中,主要用来与HTML文档打交道,同样也用在Web程序中获取XML文档,并使用DOM API来访问文档中的数据。

天生就慢

简单理解,两个相互独立的功能只要通过接口彼此连接,就会产生消耗。将DOM和JavaScript各自想象成一个岛屿,它们之间用收费桥梁连接,JavaScript每次访问DOM都要途径这座桥,并交纳过桥费。访问DOM次数越多,费用就越高。

性能优化:尽可能减少过桥的次数,努力呆在JavaScript岛上。

3.2 访问与修改

  • 描述

访问DOM元素是有代价的,修改元素则更为昂贵,尤其对HTML集合循环操作。

  • 原因

它会导致浏览器重新计算页面的几何变化。

  • 反例
--------------------------------------------------------------------
注:如果你对python感兴趣,我这有个学习Python基地,里面有很多学习资料,感兴趣的+Q群:895817687
--------------------------------------------------------------------// 这个函数每次循环该元素都被访问两次:一次读取innerHTML属性值,另一次是重写它。function innerHTMLLoop() {for (var count = 0; count < 15000; count++) {document.getElementById('here').innerHTML += 'a'; }}
  • 正例
    // 用局部变量存储修改中的内容,在循环结束后一次性写入。function innerHTMLLoop2() {var content = '';for (var count = 0; count < 15000; count++) {content += 'a';}document.getElementById('here').innerHTML += content;}

3.2.1 innerHTML对比DOM方法

修改页面区域的最佳方案?
innerHTML:非标准但支持良好;
DOM API:类似document.createElement()原生DOM方法。
答案:性能相差无几。除开WebKit内核(Chrome和Safari)之外的所有浏览器中,innerHTML会更快一些。

性能优化:如果对一个性能有着苛刻要求的操作中更新一大段HTML,推荐使用innerHTML,因为它在绝大部分浏览器中运行的更快。但对于大多数日常操作而言,没有太大的区别。

3.2.2 节点克隆

使用DOM方法更新页面内容的另一个途径是克隆已有元素,而不是创建新元素。即使用element.cloneNode()替代document.createElement()。

性能优化:大多数浏览器中,节点克隆都更有效率,但效果也不是特别明显。

3.2.3 HTML集合

HTML集合是包含了DOM节点引用的类数组对象。HTML集以一种“假定实时态”实时存在,这意味着当底层文档对象更新时,它也会自动更新。

注意:此集合是类似数组的列表,并非数组。但提供了length属性,以及以数字索引方式访问列表中的元素。

    // 以下方法返回的就是HTML集合。document.getElementsByName()document.getElementsByClassName()document.getElementsByTagName_r()document.images // 页面中所有的<img>元素document.links // 所有的<a>元素document.forms // 所有表单document.forms[0].elements // 页面中第一个表单的所有字段

低效根源:事实上,HTML集合一直与文档保持着连接,每次你需要最新信息时,都会重复执行查询的过程,哪怕是只获取集合中的元素个数(length)也是如此。

3.2.3.1 昂贵的集合

  • 描述

将集合复制到数组中,进行操作数组来代替操作集合。

  • 原因

读取集合中的length比读取普通数组中的length要慢很多。

  • 反例
    var coll = document.getElementsByTagName_r('div');// 慢function loopCollection() {for (var count = 0; count < coll.length; count++) {}}
  • 正例
    function toArray(coll) {for (var i = 0, a = [], len = coll.length; i < len; i++) {a[i] = coll[i];}return a;}var coll = document.getElementsByTagName_r('div');var ar = toArray(coll);// 快function loopCopiedArray() {for (var count = 0; count < arr.length; count++) {}}

多数情况下只需要遍历一个相对较小的集合,那么缓存length就够了。

    // 此函数运行得与loopCopiedArray()一样快。function loopCacheLengthCollection() {var coll = document.getElementsByTagName_r('div'),len = coll.length;for (var count = 0; count < len; count++) {}}

注意:这会因为额外的步骤带来消耗,而且会多遍历一次集合,因此需要评估在特定条件下数组拷贝是否有帮助。

3.2.3.2 访问集合元素时使用局部变量

当遍历一个集合时,第一优化原则是把集合存储在局部变量中,并把length缓存在循环外部,然后食用局部变量替代这些需要多次读取的元素。

    // 较慢function collectionGlobal() {var coll = document.getElementsByTagName_r('div'),len = coll.length,name = '';for (var count = 0; count < len; count++) {name = document.getElementsByTagName_r('div')[count].nodeName;name = document.getElementsByTagName_r('div')[count].nodeType;name = document.getElementsByTagName_r('div')[count].tagName;}return name;};// 较快function collectionLocal() {var coll = document.getElementsByTagName_r('div'),len = coll.length,name = '';for (var count = 0; count < len; count++) {name = coll[count].nodeName;name = coll[count].nodeType;name = coll[count].tagName;}return name; };// 最快function collectionNodesLocal() {var coll = document.getElementsByTagName_r('div'),len = coll.length,name = '',el = null;for (var count = 0; count < len; count++) {el = coll[count];name = el.nodeName;name = el.nodeType;name = el.tagName;}return name;};

3.2.4 遍历DOM

3.2.4.1 获取DOM元素

你可以使用childNodes得到元素集合,或者用nextSibling来获取每个相邻元素。

性能优化:不同浏览器中,这两种方法运行时间几乎相等。在性能要求极高时,在老版本IE中更推荐nextSibling方法来查找DOM节点。

3.2.4.2 元素节点

DOM元素诸如childNodes、firstChild、nextSibling并不区分元素节点和其他类型节点。多数情况下我们只需要访问元素节点,因此可能需要检查和过滤掉非元素节点。这些类型检查和过滤其实是比必要的操作。

性能优化:使用左边的属性代替右边,效率更快。

3.2.4.3 选择器API

使用CSS选择器也是一种定位节点的便利途径,因为开发者都熟悉CSS。最新浏览器提供了一个名为querySelectorAll()的原生DOM方法,这比使用JavaScript和DOM来遍历查找元素要快很多。

    // 使用querySelectorAll()var elements = document.querySelectorAll('#menu a');// 不使用querySelectorAll()var elements = document.getElementById('menu').getElementsByTagName_r('a');

如果要处理大量组合查询,使用querySelectorAll()会更有效率。

    var errs = document.querySelectorAll('div.warning, div.notice');

还可以使用另一个方法querySelector()来获取第一个匹配的节点。

注意:使用之前应该先检查一下浏览器是否支持,如果不支持,也许应该把库升级到最新版本了。

3.3 重绘与重排

浏览器下载完页面的所有组件:HTML标签、JavaScript、CSS、图片后,会解析并生成两个内部数据结构:
DOM树:表示页面结构。DOM树中的每一个需要显示的节点在渲染中至少存在一个对应的节点,隐藏的DOM元素在渲染树中没有对应的节点;
渲染树:表示DOM节点如何显示。渲染树中的节点被称为“帧”或“盒”。
一旦DOM树和渲染树构建完成,浏览器就开始绘制页面元素了。当DOM的变化影响了元素的几何属性(宽和高)时,浏览器就需要重新计算元素的集合属性,同样其他元素的几何属性和位置也会受到影响。

重排(reflow):浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树的过程;
重绘(repaint):完成重排后,浏览器会冲洗绘制受影响的部分到屏幕中的过程。

性能优化:重绘和重排操作的代价都是昂贵的,它们会导致Web应用的UI反应迟钝。所以应尽可能减少这类操作的发送。

3.3.1 重排何时发生

当页面布局和几何属性改变时就需要重排,以下情况会发生重排:

添加或删除可见的DOM元素;
元素位置改变;
元素尺寸改变;
内容改变;
页面渲染器初始化;
浏览器窗口尺寸改变。

3.3.2 渲染树变化的排队与刷新

由于每次重排都会产生计算消耗,大多数浏览器通过队列化修改并批量执行来优化重排过程。但以下获取布局信息的操作会强制刷新队列:

    offsetTop, offsetLeft, offsetWidth, offsetHeightscrollTop, scrollLeft, scrollWidth, scrollHeightclientTop, clientLeft, clientWidth, clientHeightgetComputedStyle() (currentStyle in IE)(在 IE 中此函数称为 currentStyle)

性能优化:应避免使用以上的属性修改样式。

3.3.3 最小化重绘和重排

3.3.3.1 改变样式

  • 描述

重排和重绘的代价可能非常昂贵,因此减少此类操作会提升性能。

  • 反例
    var el = document.getElementById('mydiv');el.style.borderLeft = '1px';el.style.borderRight = '2px';el.style.padding = '5px';
  • 正例
    // 合并改变,一次处理。var el = document.getElementById('mydiv');el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';// 保留现有样式。el.style.cssText += '; border-left: 1px;';// 修改样式名称,尽管可能带来轻微的性能影响,因为改变类时需要检查级联样式。var el = document.getElementById('mydiv');el.className = 'active';

3.3.3.2 批量修改DOM

通过以下步骤可以减少一系列DOM操作带来重绘和重排的次数:

1:使元素脱离文档流(触发重排);
2:对其引用多重改变;
3:把元素带回文档中(触发重排)。
使DOM脱离文档的三种方法:

1:影藏元素,修改应用,重新显示;
2:使用文档片段在当前DOM之外构建一个子树,再把它拷贝回文档中;
3:将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素。

    // 更新节点数据通用函数function appendDataToElement(appendToElement, data) {var a, li;for (var i = 0, max = data.length; i < max; i++) {a = document.createElement('a');a.href = data[i].url;a.appendChild(document.createTextNode(data[i].name));li = document.createElement('li');li.appendChild(a);appendToElement.appendChild(li); }};// 第一种方案var ul = document.getElementById('mylist');ul.style.display = 'none';appendDataToElement(ul, data);ul.style.display = 'block';// 第二种方案var fragment = document.createDocumentFragment();appendDataToElement(fragment, data);document.getElementById('mylist').appendChild(fragment);// 第三种方案var old = document.getElementById('mylist');var clone = old.cloneNode(true);appendDataToElement(clone, data);old.parentNode.replaceChild(clone, old);

性能优化:推荐尽可能使用第二种方案:文档片段。因为它们所产生的DOM和重排次数最少。

3.3.4 缓存布局信息

  • 描述

最好的做法是尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后再操作局部变量。

  • 原因

当你查询布局信息时,浏览器会返回最新值,会刷新队列并应用所有变更。

  • 反例
    // 低效的myElement.style.left = 1 + myElement.offsetLeft + 'px';myElement.style.top = 1 + myElement.offsetTop + 'px';if (myElement.offsetLeft >= 500) {stopAnimation();}
  • 正例
    current++myElement.style.left = current + 'px';myElement.style.top = current + 'px';if (current >= 500) { stopAnimation();}

3.3.5 让元素脱离动画流

用展开/折叠的方式来显示和影藏部分页面是一种常见的交互模式。它通常包括展开区域的几何动画,并将页面其他部分推向下方。
使用以下步骤可以避免页面中大部分重排:

使用绝对位置定位页面上的动画元素,将其脱离文档流;
让元素动起来。当它扩大时覆盖部分页面。但是这只是页面的一个小区域重绘过程,不会产生重排和重绘页面的大部分内容;
当动画结束时回复定位,从而只会下移一次文档的其他元素。

3.3.6 IE和:hover

从IE 7开始,IE允许使用:hover这个CSS伪选择器。然而,当你有大量元素使用:hover,那么会降低响应速度。此问题在IE 8上更为明显。

性能优化:在元素很多的情况下,比如表格,应该避免使用:hover来高亮显示鼠标所在行。

3.4 事件委托

根据DOM标准,每个时间都要经历三个阶段:

捕获;
到达目标;
冒泡。
事件委托只需要冒泡即可。使用时事件代理,只需要给外层元素绑定一个处理器,就可以处理在其子元素上触发的所有时间。

当用户点击了“menu #1”链接,点击事件首先被元素收到。然后它沿着 DOM 树冒泡,被

  • 元素收到,然后是

    • ,以此类推,一直到达文档的顶层直至window。这使得你可以添加一个事件处理器,来接收所有子节点产生的事件消息。
      也许你想为图中的文档提供一个渐进增强的Ajax体验。
  •     document.getElementById('menu').onclick = function(e) {// 浏览器targete = e || window.event;var target = e.target || e.srcElement;var pageid, hrefparts;// 只关心hrefs,非链接点击则退出// exit the function on non-link clicksif (target.nodeName !== 'A') {return;}// 从链接中找到页面IDhrefparts = target.href.split('/');pageid = hrefparts[hrefparts.length - 1];pageid = pageid.replace('.html', ''); // 更新页面ajaxRequest('xhr.php?page=' + id, updatePageContents);// 浏览器组织默认行为并取消冒泡if (typeof e.preventDefault === 'function') {e.preventDefault();e.stopPropagation();} else {e.returnValue = false;e.cancelBubble = true;}};
    

    如你所见,事件委托技术并不复杂,你只需检查事件是否来自你所预期的元素。如果去掉跨浏览器兼容部分,代码会更简洁。
    跨浏览器兼容部分包括:

    1:访问事件对象,并判断事件源;
    2:取消文档树中的冒泡(可选);
    3:组织默认动作(可选,但本例需要,因为需要捕获并阻止打开链接)。

《高性能JavaScript》第三章 DOM编程相关推荐

  1. JavaScript Dom编程艺术(第二版)读书笔记 第三章DOM

    第三章DOM 3.1文档中的DOM的"D" 如果没有document(文档),DOM也就无从谈起.当创建了一个网页并把它加载到Web浏览器中时,Dom就在幕后悄然而生.它把你编写的 ...

  2. 《Spark快速大数据分析》—— 第三章 RDD编程

    本文转自博客园xingoo的博客,原文链接:<Spark快速大数据分析>-- 第三章 RDD编程,如需转载请自行联系原博主.

  3. Javascript第五章DOM简介和window对象第一课

    更多免费教学文章请关注这里 ECMAScript参考:https://blog.csdn.net/qq_30225725/article/details/88621180 Javascript第五章w ...

  4. javascript 学习 —— BOM和DOM编程学习

    这是我的第一个博客,用来记录自己的学习历程.之前的学习会在项目下写备注,再次翻看也有些不方便.从今天开始就用博客记录吧. 一.BOM ( browser object model ) 与DOM 1.在 ...

  5. 高性能JavaScript笔记三(编程实践)

    避免双重求值 有四个标准函数可以允许你传入代码的字符串,然后它才你动态执行.它们分别是:eval.Function.setTimeout.setInterval 事实上当你在javascript代码中 ...

  6. 《高性能JavaScript》第二章 数据存取

    JavaScript四种数据存储位置: 1:字面量:字面量只代表自身,不存储在特定位置.JavaScript中的字面量有:字符串.数字.布尔值.对象.数组.函数.正则表达式,以及null和undefi ...

  7. JavaScript 第三课 DOM

    主要内容: 节点 5个常用的DOM方法: getElementById.getElementByTagname.getElementByClassName.getAttribute和setAttrib ...

  8. 轻松学习JavaScript二十七:DOM编程学习之事件模型

    在介绍事件模型之前,我们先来看什么是事件和什么是event对象. 一事件介绍 JavaScript事件是由访问Web页面的用户引起的一系列操作,使我们有能力创建动态页面,事件是可以被 JavaScri ...

  9. 轻松学习JavaScript二十:DOM编程学习之获取节点

    我们这里所说的获取节点包含元素节点,属性节点和文本节点.通常,通过DOM我们就能够操作HTML元素.为 了做到这件事情,您必须首先找到该元素.W3C提供了比较方便简单的定位节点的方法和属性,以便我们快 ...

最新文章

  1. debian10 mariadb安装
  2. 一次“炼狱”般的电脑维护
  3. MySQL 如何查找删除重复行?
  4. 基于线性预测的语音编码原理解析
  5. lt;%%gt;创建内联代码块(表达)
  6. 和男朋友出去玩,该去哪里​?
  7. 前端学习(3174):react-hello-react之脚手架的配置
  8. vue路由匹配实现包容性_多元化和包容性:停止说话,做作业
  9. Adobe AIR * 设置移动应用程序属性
  10. ubuntu 14.04 编译yocto源码--环境配置篇
  11. 数据仓库专题(14)-数据仓库建设指导原则:一切以就绪数据为主
  12. Matlab实现图像识别(九)
  13. 瞬态抑制二极管有方向吗?
  14. 无法装载DLL Microsoft原因: 126(找不到指定的模块)
  15. 目标跟踪——Tracking without bells and whistles
  16. MyEclipse javaw.exe-没有软盘错误
  17. 电子计算机体育游戏,如何让体育运动战胜电脑游戏?
  18. 叮当健康再闯IPO,三年累亏近30亿,如何杀出巨头重围?
  19. BM开发出量子计算机,迄今错误率最低量子比特面世,有望推进量子计算机研发工作...
  20. linux虚拟光驱软件下载,虚拟光驱工具DAEMON Tools Ultra下载中文旗舰版6.0.0.1623 - 系统之家...

热门文章

  1. SpringBoot 项目war包部署 配置外置tomcat方法
  2. 实验2 递归和分治法(二分查找)
  3. 手把手带你领略双十一背后的核心组件Sentinel之流控规则
  4. spring事务提交回滚原理mybatis版
  5. 解决spring boot+JPA实现操作数据库时编辑时也变成了新增
  6. LeetCode 33 搜索旋转排序数组
  7. 汇编语言 + Visual Studio 2019——Visual Studio 2019 中汇编语言环境解决方案
  8. BugKuCTF WEB 域名解析
  9. es 指定排序字段_ES里多字段分组后排序
  10. springboot使用异步线程池执行自定义任务实例