《高性能JavaScript》读书笔记(摘抄)

编程入门 行业动态 更新时间:2024-10-27 18:17:36

《<a href=https://www.elefans.com/category/jswz/34/1769216.html style=高性能JavaScript》读书笔记(摘抄)"/>

《高性能JavaScript》读书笔记(摘抄)

第二章 数据存取
function add(num1, num2){var sum = num1 + num2;return sum;
}

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取或存储数据。该过程搜索执行环境的作用域链,查找同名的标识符。搜索过程从作用域链头部开始,也就是当前运行函数的活动对象。如果找到,就使用这个标识符对应的变量;如果没找到,继续搜索作用域链中的下一个对象。搜索过程会持续进行,直到找到标识符,若无法搜索到匹配的对象,那么标识符将被视为是未定义的。在函数执行过程中,每个标识符都要经历这样的搜索过程。在前面的代码示例中,函数访问sum、num1、num2时都会产生搜索过程,正式这个搜索过程影响了性能。

标识符解析是有代价的,事实上没有哪种计算机操作可以不产生性能开销。在执行环境的作用域中,一个标识符所在的位置越深,它的读写速度也就越慢。因此,函数中读写局部变量总是最快的,而读写全局变量通常是最慢的(优化JavaScript引擎在某些情况下能有所改善)。请记住,全局变量总是存在于执行环境作用域链的最末端,因此它也是最远的。

在没有优化JavaScript引擎的浏览器中,建议尽可能使用局部变量。一个好的经验法则是:如果某个跨作用域的值在函数中被引用一次以上,那么就把它存储到局部变量中。


在JavaScript中,并不是只有with语句能认为改变执行环境作用域链,try-catch语句中的catch子句具有同样的效果。当try代码块中发生错误,执行过程会自动跳转到catch子句,然后把异常对象推入一个变量对象并置于作用域的收尾。在catch代码块内部,函数所有局部变量将会放在第二个作用域链对象中。事例如下:

try {methodThatMightCauseAnError();
} catch (ex){alert(ex.message);
}

请注意,一旦catch子句执行完毕,作用域链就会返回到之前的状态。

如果使用得当,try-catch是个非常有用的语句,因此不建议完全弃用。如果你准备使用try-catch,请确保了解可能会出现的错误。try-catch语句不应该被用来解决JavaScript错误。如果你知道某个错误经常出现,那说明是代码本身有问题,应该尽早被修复。

你尽量简化代码来使得catch子句对性能的影响最小化。一种推荐的做法是将错误委托给一个函数来处理,比如下面的例子:

try {methodThatMightCauseAnError();
} catch (ex){handleError(ex);// 委托给错误处理器函数
}

函数handleError()是catch子句中唯一执行的代码。该函数接受错误产生的异常对象为参数,你可以用适当的方式灵活地处理错误。由于只执行一条语句,且没有局部变量的访问,作用域链的临时改变就不会影响代码性能。


闭包是JavaScript最强大的特性之一,它允许函数访问局部作用域之外的数据。闭包的使用通过Douglas Crockford的多篇文章的介绍而流行开来,如今普遍应用在复杂的Web应用中。然而,使用闭包可能会导致性能问题。

第三章 DOM编程

DOM树中的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素在渲染树中没有对应的节点)。渲染树中的节点被称为“帧(frame)”或盒(boxes),符合CSS模型的定义,理解页面元素为一个具有内边距(padding),外边距(margins),边距(borders)和位置(position)的盒子。一旦DOM和渲染树构建完成,浏览器就开始显示(绘制“paint”)页面元素。

当DOM的变化影响了元素的几何属性(宽和高)——比如改变边框宽度或给段落增加文字,导致行数增加——浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为“重排(reflow)”。完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为“重绘(repaint)”

并不是所有的DOM变化都会影响几何属性。例如,改变一个元素的背景色并不会影响它的宽和高。在这种情况下,只会发生一次重绘(不需要重排),因此元素的布局并没有改变。重绘和重排都是代价昂贵的操作,它们会导致Web应用程序的UI反应迟钝。所以,应当尽可能减少这类过程的发生。

由于每次重排都会产生计算消耗,大多数浏览器通过队列化修改并批量执行来优化重排过程。然而,你可能会(经常不知不觉)强制刷新队列并要求计划任务立刻执行。获取布局信息的操作会导致队列刷新,比如以下方法:

offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, scrollWidth, scrollHeight
getComputedStyle()(currentStyle in IE)

以上属性和方法需要返回最新的布局信息,因此浏览器不得不执行渲染列队中的‘待处理变化’并触发重排以返回正确的值。

在修改样式的过程中,最好避免使用上面列出的属性。他们都会刷新渲染队列,即时你是在获取最近未发生改变的或者与最新改变无关的布局信息。


访问和操作DOM是现代Web应用的重要部分。但每次穿越链接ECMAScript和DOM两个岛屿之间的桥梁,都会被收取‘过桥费’。为了减少DOM变成带来的性能损失,请记住以下几点:

  • 最小化DOM访问次数,尽可能在JavaScript端处理。
  • 如果需要多次访问某个Dom节点,请使用局部变量存储它的引用。
  • 小心处理HTML集合,因为它实时连接着底层文档。把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。
  • 如果可能的话,使用速度更快的API,比如querySelectorAll()和firstElementChild。
  • 要留意重绘和重排;批量修改样式时,“离线”操作DOM树,使用缓存,并减少访问布局信息的次数。
  • 动画中使用绝对定位,使用拖放代码。
  • 使用事件委托来减少事件处理器的数量。
第四章 算法和流程控制

代码的整体结构是影响运行速度的主要因素之一。代码数量少并不意味着运行速度就快,代码数量多也不意味着运行速度一定慢。代码的组织架构和解决具体问题的思路是影响代码性能的主要因素


在for循环初始化中的var语句会创建一个函数级的变量,而不是循环级。由于JavaScript只有函数级作用域,因此在for循环中定义一个新变量相当于在循环体外定义一个新变量。


当循环复杂度为O(n)时,减少每次迭代的工作量是最有效的方法。当复杂度大于O(n),建议着重减少迭代次数。


事实证明,大多数情况下switch比if-else运行得要快,但只有当条件数量很大时才快的明显。这两个语句主要性能区别是:当条件增加时,if-else性能负担增加的程度比switch要多。因此,我们自然倾向于在条件数量较少时使用if-else,而在条件数量较大时使用switch,这从性能方面考虑也是合理的。


优化if-else的目标是:最小化到达正确分支前所需判断的条件数量。最简单的优化方法是确保最可能出现的条件放在首位。

有些时候优化条件语句的最佳方案是避免使用if-else和switch。当有大量的离散值需要测试时,if-else和switch比使用查找表慢很多。JavaScript中可以使用数组和普通对象来构建查找表,通过查找访问数据比用if-else或switch快很多,特别是在条件语句数量很大的时候。

使用查找表相对于if-else和switch,不紧速度更快,而且有时代码的可读性更好,特别是当需要测试的离散值数量非常大的时候,在那种情况下,switch语句变的很笨重:

switch(value){case 0:return result0;case 1:return result1;case 2:return result2;case 3:return result3;case 4:return result4;case 5:return result5;case 6:return result6;case 6:retur7 result7;case 8:return result8;case 9:return result9;default:return result10;
}

switch表达式代码所占的空间可能与它的重要性不成比例。整个结构可以用一个数组作为查找表来替代:

// 将返回值集合存入数组
var results = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10]
// 返回当前结果
return results[value]

当你使用查找表时,必须完全抛弃条件判断语句。整个过程变成数组项查询或者对象成员查询。查找表的一个主要优点是:不用书写任何条件判断语句,即便候选值数量增加时,也几乎不会产生额外的性能开销。

当单个键和单个值之间存在逻辑映射时(正如前面的例子),查找表的优势就能体现出来。switch语句更适合于每个键都需要对应一个独特的动作或一系列动作的场合。


JavaScript和其他编程语言一样,代码的写法和算法会影响运行时间。与其他语言不同的是,JavaScript可用资源有限,因此优化技术更为重要。

  • for、while和do-while循环性能特性相当,并没有一种循环类型明显快于或慢于其他类型。
  • 避免使用for-in循环,除非你需要遍历一个属性数量未知的对象。
  • 改善循环性能的最佳方式是减少每次迭代的运算量和减少循环迭代次数。
  • 通常来说,switch总是比if-else快,但并不总是最佳解决方案。
  • 在判断条件较多时,使用查找表比if-else和switch更快。
  • 浏览器的调用栈大小限制了递归算法在JavaScript中的应用;栈溢出错误会导致其他代码中断运行。
  • 如果你遇到栈溢出错误,可将方法改为迭代算法,或使用Memoization来避免重复计算。
    运行的代码数量越大,使用这些策略所带来的性能提升也就越明显。
第五章 字符串和正则表达式

尽可能具体化分隔符之间的字符串匹配模式。比如模式".* ?",它用来匹配一个由双引号包围的字符串。通过把这个过于宽泛的 .*? 替换成更为具体的[^"\r\n] * ,就去除了回朔时可能发生的集中情况,如尝试用点号匹配引号或者扩展搜索超出预期范围。

如果你的正则表达式包含了多个捕获组,那么你需要使用适当的反向引用次数。

第六章 快速响应的用户界面

setInterval()函数和setTimeout()几近相同,除了前者会重复添加JavaScript任务到UI队列。他们最主要的区别是,如果UI队列中已经存在由同一个setInterval()创建的任务,name后续任务不会被添加到UI队列中。

每个定时器的真实延时时间在很大程度上取决于具体情况,普遍来讲,最好使用至少25ms,因为再小的延时,对大多数UI更新来说不够用。

第七章 Ajax

使用XHR时,POST和GET的对比。当使用XHR请求数据时,你需要在POST和GET之间做出选择。对于那些不会改变服务器状态,只会获取属于(这被称为“幂等行为”)的请求,应该使用GET。经GET请求的数据会被缓存起来,如果需要多次请求同一数据的话,他会有助于提升性能。

只有当请求的URL加上参数的长度接近或超过2048个字符时,才应该用POST获取数据。这是因为IE限制URL长度,过长时将会导致请求的URL被截断。

当使用XHR发送数据到服务器时,GET方式会更快。这是因为,对于少量数据而言,一个GET请求往服务器只发送一个数据包。而一个POST请求,至少要发送两个数据包,一个装在头信息,另一个装载POST正文。POST更适合发送大量数据到服务器,因为它不关系额外数据包的数据,另一个原因是IE对URL长度有限制,它不可能使用过长的GET请求。

第八章 编程实践

JavaScript像其他很多脚本语言一样,允许你在程序中提取一个包含代码的字符串,然后动态执行它。有四种标准方法可以实现:eval()、Function()构造函数、setTimeout()和setInterval()。其中每个方法都允许你传入一个JavaScript代码字符串并执行它。


在JavaScript中创建对象和数组的方法有多种,但使用对象和数组直接量是最快的方式。


在计算机科学领域中最主要的性能优化技术之一就是“避免无谓的工作(work avoidance)”。避免无谓的工作的概念有两重意思:别做无关紧要的工作,别重复做已经完成的工作。


调用延迟加载函数时,第一次总会消耗较长的时间,因为它必须运行检测接着再调用另一个函数完成任务。但随后调用的函数会更快,因为不需要再执行检测逻辑。当一个函数在页面中不会立刻调用时,延迟加载是最好的选择。


条件预加载确保所有函数调用消耗的时间相同。其代价是需要在脚本加载时就检测,而不是加载后。预加载适用于一个函数马上就要被用到。并且在整个页面的生命周期中频繁出现的场合。


无论你的JavaScript代码如何优化,都永远不会比JavaScript引擎提供的原生方法更快。道理很简单:JavaScript的原生部分在你写代码前已经存在在浏览器中了,并且都是用低级语言写的,诸如C++。这意味着这些方法会被编译成机器码,成为浏览器的一部分,所以不会像你写的JavaScript代码那样受到各种限制。

经验不足的JavaScript开发者经常犯的一个错误是在代码中进行复杂的数学运算,而没有使用内置的Math对象中那些性能更好的版本。Math对象中那些特别设计的属性和方法是为了让数学运算变得更容易。


JavaScript提出了一些独一无二的性能挑战,这与你组织代码的方式有关。随着Web应用变得越来越高级,包含的JavaScript代码也越来越多,各种模式和反模式也逐渐出现。为了编写更高效的代码,请牢记以下编程实践:

  • 通过避免使用eval()和Function()构造器来避免双重求职带来的性能消耗。同样的,给setTimeout()和setInterval()传递函数而不是字符串作为参数。
  • 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。
  • 避免做重复的工作,当需要检测浏览器时,可使用延迟加载或条件预加载。
  • 在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算。
  • JavaScript的原生方法总会比你写的任何代码都要快。尽量使用原生方法。

引用

《高性能JavaScript》2015年8月第1版

更多推荐

《高性能JavaScript》读书笔记(摘抄)

本文发布于:2024-03-04 09:53:27,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1708949.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:高性能   读书笔记   JavaScript

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!