Skip to content

Latest commit

 

History

History
184 lines (118 loc) · 7.97 KB

《高性能JavaScript》学习笔记(三).md

File metadata and controls

184 lines (118 loc) · 7.97 KB
name title tags categories info time desc keywords
《高性能JavaScript》学习笔记(三)
《高性能JavaScript》学习笔记(三)
技术
学习笔记
高性能JavaScript
学习笔记
高性能JavaScript 第4章 算法和流程控制, 第5章 正则表达式 第六章 响应接口
2019/4/29
高性能JavaScript, 资料下载, 学习笔记, 第4章 算法和流程控制, 第5章 正则表达式, 第六章 响应接口
高性能JavaScript资料下载
前端
高性能JavaScript
学习笔记
第4章 算法和流程控制
第5章 正则表达式
第六章 响应接口

《高性能JavaScript》学习笔记(三)

第4章 算法和流程控制

代码整体结构是执行速度的决定性因素之一。性能损失与代码组织方式和具体问题解决办法直接相关。

循环

循环性能

在 JavaScript 提供的四种循环类型(for, while, do-while, for...in)中(现在是5种了,还有一种 for...of),只有一种循环比其他循环明显要慢:for...in 循环。除此以外,其他循环类型性能相当。

基于函数的迭代

**forEach()**方法

此方法遍历一个数组的所有成员,并在每个成员上执行一个函数。该函数在调用时接收三个参数,它们是:数组项的值,数组项的索引,数组自身。

在现代浏览器中,使用 forEach 来循环迭代数组比用 for 循环的效率更高,速度更快。

条件表达式

if-else 与 switch 比较

如果条件较少时,if-else 容易阅读,而条件较多时,switch 更容易阅读。

大多数情况下 switch 表达式比 if-else 更快,但只有当条件体数量很大时才明显。

查表法

查表法访问数据比 if-else 或者 switch 更快,特别当条件体数目很大时。

Recursion 递归

复杂算法比较容易使用递归实现。

递归函数的问题是,一个错误定义,或者缺少终结条件可能会导致长时间运行,冻结用户界面。此外,递归函数还会遇到浏览器调用栈大小的限制

当使用了太多的递归,超过最大调用栈尺寸时,浏览器会出错并在控制台上报出报错信息。

迭代

任何可以用递归实现的算法都可以用迭代实现。迭代算法通常包括几个不同的循环,分别对应算法过程的不同方面,同时也会导致自己的性能问题。

使用优化的循环替代长时间运行的递归函数可以降低开销,优化性能。

第5章 正则表达式

在 JavaScript 中,正则表达式是必不可少的东西,它的重要性远超过琐碎的字符串处理。

字符串连接

str += "one" + "two"

上面的代码时连接字符串最常用的方法,在此代码的执行期间,会发生四个步骤:

  1. 内存中创建了一个临时字符串
  2. 临时字符串的值被赋予"onetwo"
  3. 临时字符串与 str 的值进行连接
  4. 结果赋予 str

这基本就是浏览器完成这一任务的过程。

为了浏览器性能的提升,我们应该尽量避免使用临时字符串。如上面的代码可以改写为str = str + "one" + "two",就可以避免使用临时字符串了。

正则表达式优化

粗浅的编写正则表达式是造成性能瓶颈的主要原因。两个正则表达式匹配相同的文本并不意味着他们具有同等的速度。

正则表达式工作原理

正则表达式处理的基本步骤:

  • 编译
  • 设置起始位置
  • 匹配每个正则表达式的字元
  • 匹配成功或失败

只有字符串中的每个字符都经历了这样的过程以后,还没有成功匹配,那么正则表达式才会宣布彻底失败。

理解回溯

回溯是匹配过程的基本组成部分。气很大程度上也是正则表达式如此强大的根源。然而,回溯计算代价昂贵,如果你不够小心的话则容易失控。

当一个正则表达式扫描目标字符串时,它从左到右逐个扫描正则表达式的组成部分,在每个位置上测试能不能找到一个匹配。对于每一个量词和分支,都必须决定如何继续进行。

PS: 正则表达式的章节留到以后再详细阅读...

第6章 响应接口

确保网页应用程序的响应速度是一个重要的性能关注点。

大多数浏览器在每个时刻只有一个操作可以执行,也就是说当 JavaScript 代码运行时用户界面不能对输入产生反应。反之亦然。所以,管理好 JavaScript 运行时间对网页应用的性能很重要。

浏览器在 JavaScript 运行时间上采取了限制,这个必要的限制确保恶意代码的编写者不能通过无尽的密集操作锁定用户浏览器或计算机。这类限制有两个:调用栈尺寸限制和长时间脚本限制。

但尽管你尽了最大的努力,还是有一些 JavaScript 任务因为复杂性等原因不能在 100 毫秒或更短的时间内完成。这种情况下,理想的方法是让出对 UI 线程的控制,使得 UI 更新可以进行。让出控制意味着停止 JavaScript 运行,给 UI 线程机会进行更新,然后再继续运行 JavaScript。

为此我们需要的就是定时器。

定时器基础

定时器代码只有等创建它的函数运行完成后,才有可能被执行。在任何一种情况下,创建一个定时器造成 UI 线程暂停,如同它从一个任务切换到下一个任务。

JavaScript 定时器延时往往不准确,快慢大约几毫秒。正因为这个原因,定时器不可用于测量实际时间。

对于一些无需同步处理,也无需顺序处理的问题,则该问题适用于使用定时器分解工作。

分解任务

我们通常将一个任务分解成一系列子任务。如果一个函数运行时间太长,那么可以基于函数调用进行拆分。

如果函数运行时间太长,它可以拆分成一系列更小的步骤,把独立方法放在定时器中调用。你可以将每个函数都放入一个数组,然后使用数组处理模式。

// 数组处理模式
function saveDocument(id) {
    var tasks = [openDocument, writeText, closeDocument, updateUI]
    setTimeout(function () {
        var task = tasks.shift()
        task(id)
        if (tasks.length > 0) {
            setTimeout(arguments.callee, 25)
        }
    }, 25)
}

此模式也可以封装使用。

function multistep(steps, args, callback) {
    var tasks = steps.concat(); // clone Array
    setTimeout(function () {
        var task = tasks.shift()
        task.apply(null, args || [])
        if (tasks.length > 0) {
            setTimeout(arguments.callee, 25)
        } else {
            callback()
        }
    }, 25)
}

定时器性能

过度使用定时器会对性能产生负面影响,如果想避免这种负面的性能影响,最好的办法是确保同一时间只有一个定时器存在。当这个定时器结束时,才创建一个新的定时器。

Web Workers

Web Worker 改变了 JavaScript 没有办法在浏览器 UI 线程之外运行代码的现状。Web Worker 对网页应用来说是一个潜在的巨大性能提升,Web Worker 中的代码运行不仅不会影响浏览器UI,而且也不会影响其他 Web Worker中运行的代码。

但这也意味着他不能访问许多的浏览器起源。Web Worker 的运行环境由下列部分组成:

  • 一个浏览器对象,包含四个属性:appName, appVersion, userAgent, platform
  • 一个 location 对象
  • 一个 self 对象,指向全局 Web Worker 线程对象
  • 一个 importScripts() 方法,使得 web worker 可以加载外部 JavaScript
  • 所有的 ECMAScript 对象
  • XMLHttpRequest 构造器
  • setTimeout 和 setInterval 方法
  • close 方法可以立即停止工人线程

由于 Web Worker 有不同的全局运行环境,所以你不能再 JavaScript 代码中创建一个 Web Worker,而只能从外部引入,如下:

var worker = new Worker("code.js")

关于 Web Worker 的更多知识点可以查找以前的博客,有更多详细的解释。