七、不要使用 DOM
在这一章中,我们将回顾 DOM 与编写高性能 JavaScript 的关系,并看看如何优化 JavaScript 以使我们的 web 应用明显更快。
我们还将看看 JavaScript 动画,并测试他们的性能与现代 CSS3 动画; 我们还将测试 DOM 中的 paint redraw 事件,并快速测试附加到页面上可能影响性能的滚动事件。
在本章中,我们将讨论以下主题:
- 为什么要担心 DOM 呢?
- 我们不需要一个 mv 库吗?
- 使用
createElement
函数创建新对象 - 动画元素
- 了解漆事件
- 讨厌的鼠标滚动事件
为什么要担心 DOM?
文档对象模型(DOM)是我们的 HTML 内容在 web 浏览器中显示的方式。 它不是完全的相同的源代码; DOM 是我们在 web 浏览器中对 web 应用页面进行更新时源代码的实时更新版本。
我们可以说,快速、优化的 JavaScript 肯定会帮助我们的应用运行和性能更好,正如我们在前几章学到的。 但重要的是要理解 DOM 对 JavaScript 性能的重要性,就像理解如何优化for
循环。
在 Web 的早期,我们作为 Web 开发人员并没有过多地考虑 DOM。 如果我们思考一下 JavaScript 已经走了多远,我们就会发现 web 开发世界已经发生了许多变化。 如果我们回想一下 google 之前的网络,我们知道网站是非常简单的,用户交互主要局限于超链接标签和偶尔的 JavaScriptwindow.alert()
功能,以显示某种形式的应用交互。
随着时间的推移,我们遇到了 Web 2.0,或者更确切地说,在这里异步 JavaScript 和 XML(AJAX)应运而生。 如果你不熟悉 AJAX,我想总结一下:AJAX web 应用允许开发人员从外部来源获取内容,通常是 XML 文件(这是 AJAX 中的 X)。
有了 AJAX,网站内容突然变得动态了,这意味着开发者不再需要依靠后端技术来更新网页。 突然间,对更强大的 JavaScript 的需求出现了。 企业和他们的客户不再想要一个网站和页面响应闪光(或网站后端技术来更新页面使用POST
提交方法),更因此与谷歌等网站地图和 Gmail 似乎把网络作为一个平台软件的概念而不是一个桌面操作系统。
我们不需要一个 mv 库吗?
今天,我们有一些框架可以帮助解决这类应用的繁重工作; AngularJS、Backbone.js、Knockout.js 和 jQuery 都是一些会让人想到的库。
然而,在本书中,我们将坚持使用普通 JavaScript,原因有二。 第一个原因是,整本书都是关于这些库的,讨论性能和不同层次的体验,所有这些都很好,但超出了本书的范围。 第二个原因是大多数开发人员通常不需要这些库来构建项目。
请记住,这里提到的所有 JavaScript 库,以及在 Web 上找到的那些库,同样都是 JavaScript! 对于大多数项目,我们不应该需要一个库来按照我们想要的方式构建项目; 此外,这些库中有许多都附带了额外的代码。
我这么说的意思是,这些库附带的特性在给定的项目中可能是不需要的,除非一个库是模块化的,否则在不删除不需要的特性的情况下使用它是很困难的。 如果您工作在一个团队环境中,其他人可能会为应用的某些领域使用共享库,这些领域可能会使用一些特性,但不是所有特性,那么这就更加困难了。
我们将在第 9 章,优化 iOS 混合应用中探讨移动 JavaScript 性能。 我们会发现这些库将成为更大的负担。 现在,让我们看看破坏 DOM 的一些常见方法,以及我们可以做些什么来提高它的性能。
使用 createElement 函数创建新对象
在这里,我们将学习使用createElement
函数创建新对象,以及以下三个主题:
- 围绕
createElement
功能工作 - 使用
createElement
功能 - 何时使用
createElement
功能
围绕 createElement 函数工作
在 JavaScript 中,我们可以使用document.createElement()
函数创建新的页面元素,并使用document.createTextNode()
函数将文本对象放置到生成的元素中。 通常情况下,如果使用多个生成的元素创建新元素并将其注入 DOM,则会消耗渲染资源和交互性能。
使用 createElement 函数
让我们测试一下createElement
函数在屏幕上呈现内容的效果如何。 下面是我们的测试:我们将使用for
循环创建一个包含大量数据的表。 我们将用一个带有for
循环迭代次数的文本对象填充一个表格单元格。 然后,我们将查看另一个版本,使用不同的代码实现创建相同的效果,并对两者进行比较。 让我们看看使用createElement
函数的第一个选项,如下所示:
这里,我们有一个简单的 HTML5 页面,在head
部分有一些格式化的 CSS 样式,在第 21 行有一个空的占位符div
元素,id
设置为datainsert
。 在第 25 行,我们有一个anonymous function
,只要它被加载到浏览器中就可以运行; 同样在第 26 行,我们开始了一个console.time
函数来计算我们的 JavaScript 执行了多长时间。 然后在第 27 行创建一个名为tableElem
的表元素变量; 在第 28 行到第 31 行,我们设置了一些属性来帮助样式化表的格式。
然后在第 33 行,我们开始我们的for
循环; 在我们的for
循环范围,我们创建一个表行元素,一个表格单元元素,和一个文本节点插入文本生成的表格单元,从cellContent
变量在 35 行,在第 36 行tableTr
变量,tableTd
变量在 37 行。 在第 39-41 行,我们将生成的单元格附加到表中,并继续循环10000
次。 最后,我们将 table 元素添加到页面上的datainsert
div 元素以呈现内容。 让我们在浏览器中运行这个,看看使用 Chrome开发工具选项渲染内容需要多长时间。
正如我们所看到的,这需要相当长的处理时间,在 Chrome 中大约 140 毫秒,这是一个相当长的渲染时间。 您可以考虑在构建消息传递客户机或从 JSON 显示数据时进行类似操作。 无论如何,使用createElement
功能的成本是相当大的,应该只在一小部分使用。
在不使用createElement
函数的情况下,在这样的表上生成数据的另一种方法是使用innerHTML
属性。 这个属性提供了一种简单的方法来完全替换元素的内容,并以与给变量赋值相同的方式赋值。 当使用innerHTML
属性时,无需刷新页面即可更改页面内容。 这可以让你的网站感觉更快,对用户输入的响应更迅速。 也可以使用+=
append 运算符附加该属性。 知道了这一点,我们可以以稍微不同的方式构建代码库。 我们正在做的事情如下面的截图所示:
这个函数的布局应该与我们的createElement
函数示例非常相似。 在第 21 行,我们有相同的datainsert
div; 在第 25 行,我们的Anonymous function
开始了。 现在在第 28 行,我们看到一些完全不同的东西; 在这里,我们可以看到一个名为tableContents
的字符串变量的开始,它的 HTML 表的开始具有与前面示例设置的相同的属性。 这就像我们使用createElement
函数所做的一样,只不过这次我们使用的是 HTML 标记的 JavaScript 字符串,而不是 DOM 对象。
在接下来的第 30 行,我们开始我们的for
循环,并将tableContents
字符串添加到我们的表行和表单元格中,并将 for 循环的迭代计数插入到单元格中,再次计数 10,000 次。
当循环在第 35 行结束时,我们在字符串后面加上表的右括号。 最后,在第 37 和 38 行,我们使用innerHTML
属性,并将表写入到datainsert
div 元素的innerHTML
属性中。 让我们在浏览器中运行这个示例,看看它的处理时间。
这一次,我们的表的渲染时间大约是 40 毫秒,这几乎是使用createElement
函数的 4 倍。 现在这是一个很大的速度改进! 它在 Chrome 上的视觉速度甚至更快。
何时使用 createElement 函数?
虽然函数很慢,但有时它在通过复杂布局生成 HTML 时更有帮助,因为复杂的应用生成的元素比innerHTML
属性的样式要多得多。
如果是这种情况,那么这样做更多的是为了方便开发团队在修改元素类型时使用,而不是为了更新完整的字符串以满足应用的需要。 在任何情况下,如果你需要创建 HTML 元素,innerHTML
属性总是更快。
动画元素
其中一个令人印象深刻的 JavaScript 使用出现在 JavaScript 的Web 2.0时代,而 AJAX 正在流行; 另一个有趣的想法是 JavaScript 动画。 这些动画是通过简单地遍历一个元素的样式来创建的,这些样式是使用setInterval
函数在左边和顶部放置的,然后在元素到达它的端点后解散它。 这允许 div 在页面上以补间或动画的形式出现。
用老式的方式制作动画
大多数 JavaScript 开发人员都熟悉使用 jQuery 做动画,jQuery 是流行的 JavaScript DOM 操作库,使用animate
函数来创建 DOM 动画。 但是,由于我们在本书中讨论的是纯 JavaScript,让我们看一个如何从头开始构建它的示例。 查看以下截图中的代码:
在这个例子中,我只用 JavaScript 简单地创建了一个 webkit 友好的动画(这意味着它只会在谷歌 Chrome 和苹果 Safari 浏览器中正确显示)。 在第 7 行中,我们设置了一些基本样式,包括将id
设置为dot
的黑点 div 元素。
现在在第 27 和 28 行,我们分别声明了dot
和i
变量。 然后,在第 31 行,我们创建了一个名为interval
的变量,它实际上是传递给setInterval
函数的参数。 在这段代码中,它是针对每一毫秒的,如第 38 行所示。 在setInterval
函数中,我们将i
变量的计数加1
,并更新dot
元素的位置。 最后,当i
变量的值与450
严格相等时,我们使用clearInterval
函数消去我们的区间变量,从而停止setInterval
函数的进一步处理。 如果我们看看这个,我们可以在浏览器中用纯 JavaScript 看到一个简单的动画补间。 如下截图所示:
现在,您可能认为以这种方式创建setInterval
函数可能会引起关注,您可能是对的。 幸运的是,作为开发者,我们现在有了为 HTML5 应用创建这样的动画的替代方法!
动画使用 CSS3
让我们重新构建这个示例使用 CSS3 和 JavaScript 只触发动画。 同样,为了简单起见,我们将简单地为以 webkit 为重点的浏览器设计样式。 让我们看看更新后的代码示例,如下面的截图所示:
通过这个例子,我们可以看到我们的 JavaScript 有更少的行,这是一件好事; 它保持我们的内容样式完全基于 css,而不是使用 JavaScript 逻辑样式内容。
现在,在 JavaScript 方面,我们可以看到我们正在使用相同的Anonymous function
在第 39 行,除了我们设置了一个超时来触发dot
元素,以添加一个激活的类属性来触发 CSS3 中的动画。 在我们的示例中,第 19 到 30 行显示了这一点
不公平的绩效优势
在本书的许多代码示例中,我使用了console.time
和console.timeEnd
来检查性能,这个示例也不例外。 你可能已经注意到,我已经将每个动画示例封装在一个time
和timeEnd
函数中,以测量处理时间。
正如我们在前面的截图中所看到的,JavaScript 处理时间大约是 1900 毫秒,而 CSS3 动画大约是 0.03 毫秒。 现在,在我们得出 CSS3 方法更好的结论之前,我们必须记住,我们只使用 CSS3 来呈现页面,而 JavaScript 只处理动画的触发器。 它仍然更有效率,但应该注意的是 JavaScript 处理的代码更少。
现在对于较新的浏览器,这是构建内容动画的推荐方法,考虑到到目前为止所看到的性能改进,无论是否由 JavaScript 完成。 然而,有些项目需要旧的浏览器支持,这些项目可能无法访问 CSS3 过渡和动画,或者我们在升级应用的一部分动画的同时仍然保持兼容性。 这里有一种方法来做到这一点,同时使用相同的 javascript 动画,就像之前一样:
在这里,我们修改了最初的 JavaScript 示例,更新了dot
元素的位置; 然而,我们在第 17 行和第 18 行添加了两行 CSS。 第一个是-webkit-transform
和translate3d
属性,它只设置元素不改变位置; 在较旧的浏览器或非 webkit 聚焦的浏览器中,此属性将被忽略。 但在这里,它只是简单地将元素的位置设置为初始位置,这听起来很愚蠢,从某种程度上说,确实如此!
这实际上是告诉 DOM 运行时,它需要作为一个独特的图形进程运行; 它还告诉浏览器设备上的图形处理单元(GPU)快速绘制这个元素! will-change
也是如此,这是一个类似的属性不一样的translate3d
属性除了这不是更新位置只是告诉 GPU 重绘这个元素在非常高的速度和期待它在 DOM 改变。 现在,这个实践叫做向复合层添加元素。 我们将在第 9 章,优化 iOS 混合应用中深入了解复合层。 但现在,这就是我们要做的; 这样,更新的浏览器仍然可以使用遗留的 JavaScript 动画获得一些视觉速度的提高。
理解油漆事件
Paint 事件是 DOM 事件,当 DOM 用 JavaScript 更新时,浏览器会绘制网页。 对于内存较低的浏览器,这可能是个问题,因为绘制事件需要大量的处理和图形渲染来显示大量的更新。
如何检查油漆事件?
通常,paint 事件可以在 Web Inspector 的时间轴视图中找到。 由于在网页浏览器中,绘图事件是按时间顺序显示的,所以在 Chrome 的开发者工具选项中,它们的显示略有不同。
打开 Chrome 的开发工具选项,点击抽屉图标(它是在开发工具选项右上方齿轮图标旁边的图标)。 打开抽屉中的Rendering选项卡,点击Show paint rectangles选项。 完成之后,刷新页面。 当页面加载时,我们将看到页面在各个区域中以绿色高亮显示。 这些是在屏幕上加载时的绘制事件。 这是一个使用我们的动画和显示在 Chrome 的开发工具选项的绘制矩形的例子:
注意在页面加载和动画结束时,绿色的方块是如何出现的。 这是因为 DOM 只在页面加载或动画结束时重新绘制浏览器窗口。
有时候,项目可以单独使用 JavaScript 创建相当复杂的动画。 为了发现错误与我们的 JavaScript 逻辑,并确保油漆事件不会造成一个问题,我们可以使用 Chrome 的开发者工具中的连续页面重绘功能。
测试涂料事件
为了测试这一点,我们已经设置了一个带有内置 bug 的 JavaScript 动画,如下所示:
这看起来应该是非常类似于我们在本章中创建的早期动画。 但是如果我们看一下第 35 行到第 38 行,我们可以看到有一个条件语句else if
,用来检查增量变量i
是否在 250-258 范围内; 如果是,则将left``style
从dot
元素中移除。
当我们运行这个,我们应该遇到一个闪烁动画到达这一点。 我们可以验证这是否真的是一个 JavaScript 问题,通过启用 Chrome 的开发者工具的连续页面重绘。
要做到这一点,打开开发人员工具选项,打开抽屉,并点击抽屉中的渲染选项卡。 然后我们可以检查Enable 连续页面重绘和Show paint rectangles选项。 当我们这样做时,我们的网页应该显示一个绿色覆盖层,并在浏览器窗口的右上角显示一个信息框。 如下截图所示:
现在,当我们重新加载页面并重新播放动画时,我们的dot
元素应该在整个动画期间显示一个围绕它的绿色框。 这是 Chrome 迫使页面不断重绘,因为动画更新。 正如我们所看到的,即使我们遇到了预先设定的错误,这个框仍然是正确的,这表明了一个 JavaScript 问题。 如果这是一个真正的油漆问题,当重绘问题发生时,该框将消失。
讨厌的鼠标滚动事件
当你使用 JavaScript 工作时,油漆事件(或缺乏)不是唯一的问题。 应用于浏览器窗口或文档的滚动事件可能会对应用造成严重破坏; 通过滚动鼠标来连续触发事件从来都不是一个好主意,更不用说多个事件了。
如果我们正在编写一个应用,我们知道应用是否添加了一个或多个事件。 但如果我们要更新一个 web 应用,在 Chrome 的开发者工具中有一个工具,可以让我们直观地检查滚动事件。
让我们创建一个快速示例来说明这个特性是如何工作的,以及在尝试优化 DOM 接口时它需要什么。 为此,我创建了一个mousewheel
事件捕获X和Y 坐标的鼠标指针的位置的页面,并打印到一个输入字段与一组id``txtfield
; 每次移动鼠标滚轮都会触发。 让我们看看下面的代码示例:
我们可以在这里看到页面本身很轻,但是在第 23 行,我们可以看到mousewheel
事件监听器正在使用第 26 行getMouseLocation
函数添加连续事件。 然后,在第 27 行,我们的输入字段(带有txtfield
的id
)被分配了一个带有鼠标事件信息的字符串,抓取鼠标指针的X
和Y
坐标,并将其应用于txtfield
的值。 现在让我们看看开发工具突出显示滚动的性能问题。
打开抽屉,打开Rendering选项卡,然后点击Show potential scroll 瓶颈。 这将突出显示 JavaScript 中分配了滚动事件的块区域; 下面是启用过滤器后的示例:
现在,就性能而言,这本身并不是太糟糕,但具有多个鼠标移动事件的应用可能会导致问题,如果移动区域重叠的话就更严重了。 如果我们向文本区域添加相同的事件监听器,并从文档中删除该监听器,我们会在开发人员工具过滤器中看到多个滚动监听器实例吗? 让我们通过查看本章的最后一个示例文件07_08.html
的输出来找出它:
不! 正如我们所看到的,即使在单个元素上启用了mousewheel
事件,整个页面也会高亮显示。 由于可以在 DOM 顶部检查mousewheel
事件,因此即使应用只关注mousewheel
事件的一个小元素,整个页面也会受到影响。
因此,记住事件很重要,因为它们可能会降低页面的性能。
小结
在这一章中,我们讨论了 JavaScript 如何影响 DOM 的性能; 我们回顾了createElement
函数,学习了如何更好地编写 JavaScript 来优化从代码中生成元素。
我们还回顾了 JavaScript 动画,并比较了它们与现代 CSS3 动画的性能。 我们还学习了如何优化现有或遗留的 JavaScript 动画。
最后,我们回顾了 DOM 中的 paint 事件,并看到了在 JavaScript 操作之后 DOM 是如何重新绘制内容的; 我们还讨论了mousewheel
事件,并了解了它们如何潜在地降低 DOM 的速度。
在下一章,我们将看看 JavaScript 在性能方面的新朋友:web workers,以及我们如何让 JavaScript 像多线程应用一样运行。
版权属于:月萌API www.moonapi.com,转载请注明出处