给前端开发者的 14 个 JavaScript 代码优化建议

程序员成长指北

共 1572字,需浏览 4分钟

 ·

2020-11-18 14:50

JavaScript 已经成为当下最流行的编程语言之一。根据 W3Tech,全世界几乎 96% 的网站都在使用它。关于网站,你需要知道的最关键的一点是,你无法控制访问你网站的用户的硬件设备规格。访问你的网站的终端用户也许使用了高端或低端的设备,用着好的或差的网络连接。这意味着你必须确保你的网站是尽可能优化的,你能够满足任何用户的要求。

这里有一些技巧,可以帮助你更好地优化 JavaScript 代码,从而提高性能。

顺便提一下,为了共享和复用 JS 组件,需要在高质量代码(需要花时间)和合理交付时间之间保持正确的平衡。你可以使用流行的工具例如 Bit (Github),去共享组件(vanilla JS, TS, React, Vue 等)到 Bit 的 component hub,而不浪费太多时间。

1. 删除不使用的代码和功能

程序包含越多的代码,给客户端传递的数据就越多。浏览器也需要更多的时间去解析和编译代码。

有时,代码里也许会包含完全未使用到的功能,最好只将这些额外的代码保留在开发环境中,并且不要把它们留到生产环境中,因为无用的代码可能会增加客户端浏览器的负担。

经常问自己那个函数、特性或代码是否是必需的。

你可以手动的删掉无用的代码,也可以用工具 Uglify 或 谷歌开发的 Closure Compiler 帮你删。你甚至可以使用一种叫做 tree shaking 的技术来删除程序中未使用的代码。例如打包工具 Webpack 就提供了它。你可以在 这里 了解更多关于 tree shaking 信息。还有,如果你想删掉未使用的 npm 包,你可以输入命令 npm prune 。阅读 NPM 文档 了解更多。

2. 尽可能缓存

缓存通过减少等待时间和网络请求提高了网站的速度和性能,因此减少了展示资源的时间。可以借助于 缓存 API 或 HTTP 缓存 实现它。你也许好奇当内容改变时发生了什么。上述缓存机制能够在满足某些条件(如发布新内容)时处理和重新生成缓存。

3. 避免内存泄漏

作为一种高级语言,JS 负责几个低级别的管理,比如内存管理。对于大多数编程语言来说,垃圾回收是一个常见的过程。通俗地说,垃圾回收就是简单地收集和释放,那些已经分配给对象,但目前又不被程序任一部分使用的内存。在像 C 这样的编程语言中,开发者必须使用 malloc()dealloc() 函数来处理内存分配和回收。

尽管垃圾回收是 JavaScript 自动执行的,但在某些情况下,它可能并不完美。在 JavaScript ES6 中,Map 和 Set 与它们的“weaker”兄弟元素一起被引入。“weaker”对应着 WeakMap 和 WeakSet,持有的是每个键对象的“弱引用”。它们允许对未引用的值进行垃圾收集,从而防止内存泄漏。了解更多关于 WeakMaps 的信息。

4. 尽早跳出循环 Try to Break Out of Loops Early

执行循环在代码量大的循环中肯定会消耗大量宝贵的时间,这就是为什么要尽早打破循环的原因。你可以使用 break 关键字和continue 关键字跳出循环。编写最有效的代码是开发者们的责任。

在下面的例子中,如果你不在循环中使用 break ,你的代码将运行循环 1000000000 次,显然是超出负荷的。

let arr = new Array(1000000000).fill('----');
arr[970] = 'found';
for (let i = 0; i < arr.length; i++) {
  if (arr[i] === 'found') {
        console.log("Found");
        break;
    }
}

在下面的例子中,当不满足条件时如果你不使用 continue,那么将执行函数 1000000000 次。而我们只处理了位于偶数位置的数组元素,就将循环执行减少了近一半。

let arr = new Array(1000000000).fill('----');
arr[970] = 'found';
for (let i = 0; i < arr.length; i++) {
  if(i%2!=0){
        continue;
    };
    process(arr[i]);
}

你可以在 这里 了解更多关于循环和性能。

5. 最小化变量的计算次数

要减少计算变量的次数,可以使用闭包。JavaScript 中的闭包允许你从内部函数访问外部函数作用域。每次创建一个函数时都会创建闭包——但不调用。内部函数可以访问外部作用域的变量,即使外部函数已经调用结束。

让我们看两个例子,看看这是怎么回事。这些例子的灵感来自 Bret 的博客。

function findCustomerCity(name{
  const texasCustomers = ['John''Ludwig''Kate']; 
  const californiaCustomers = ['Wade''Lucie','Kylie'];
  
  return texasCustomers.includes(name) ? 'Texas' : 
    californiaCustomers.includes(name) ? 'California' : 'Unknown';
};

如果我们多次调用上述函数,每次都会创建一个新对象。对于每个调用,不会将内存重新分配给变量 texasCustometrscaliforniaCustomers

通过使用带有闭包的解决方案,我们只能实例化变量一次。让我们看看下面的例子。

function findCustomerCity({
  const texasCustomers = ['John''Ludwig''Kate']; 
  const californiaCustomers = ['Wade''Lucie','Kylie'];
  
  return name => texasCustomers.includes(name) ? 'Texas' : 
    californiaCustomers.includes(name) ? 'California' : 'Unknown';
};

let cityOfCustomer = findCustomerCity();

cityOfCustomer('John');//Texas
cityOfCustomer('Wade');//California
cityOfCustomer('Max');//Unknown

上述例子中,在闭包的帮助下,返回给变量 cityOfCustomer 的内部函数可以访问外部函数 findCustomerCity() 的常量。并且当调用内部函数并传参 name 时,不需要再次实例化这些常量。如果想要对闭包有更多了解,我建议你浏览Prashant的这篇博客。

6. 最小化 DOM 的访问

与其他 JavaScript 语句相比,访问 DOM 要慢一些。如果你要操作 DOM,从而触发重绘布局,那么操作会变得相当缓慢。

要减少访问 DOM 元素的次数,请访问它一次,并将其作为局部变量使用。当需求完成时,确保通过将变量设置为 null 来删除该变量的值。这将防止内存泄漏,因为它允许垃圾回收。

7. 压缩文件

通过使用诸如 Gzip 之类的压缩方法,可以减小 JavaScript 文件的大小。这些较小的文件将提升网站性能,因为浏览器只需要下载较小的资源。

这些压缩可以减少多达 80% 的文件大小。在这里了解更多关于 压缩:https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer#text_compression_with_gzip


8. 缩小你的最终代码

有些人认为缩小和压缩是一样的。但却相反,它们是不同的。在压缩中,使用特殊的算法来改变输出文件的大小。但在缩小中,需要删除 JavaScript 文件中的注释和额外的空格。这个过程可以在网上找到的许多工具和软件包的帮助下完成。缩小已经成为页面优化的标准实践和前端优化的主要组成部分。

缩小可以减少你的文件大小高达 60%。在这里了解更多关于 缩小。

9. 使用节流 throttle 和防抖 debounce

通过使用这两种技术,我们可以严格执行代码需要处理事件的次数。

节流是指函数在指定时间内被调用的最大次数。例如,“最多每 1000 毫秒执行一次 onkeyup 事件函数”。这意味着如果你每秒输入 20 个键,该事件将每秒只触发一次。这将减少代码的加载。

另一方面,防抖是指函数在上次触发后再次触发要间隔的最短时间。换句话说,“仅当经过 600 毫秒而没有调用该函数时才执行该函数”。这将意味着,你的函数将不会被调用,直到 600 毫秒后,最后一次执行相同的函数。要了解更多关于节流和防抖的知识,这里有一个快速阅读。

你可以实现自己的防抖和节流函数,也可以从 Lodash 和 Underscore 等库导入它们。

10. 避免使用 delete 关键字

delete 关键字用于从对象中删除属性。关于这个 delete 关键字的性能,已经有一些争议。你可以在 此处 和 [此处](https://stackoverflow.com/questions/43594092/slow-delete-of-object- propertieses-in-js-in-v8/44008788) 中查看它们。这个问题有望在未来的更新中得到解决。

As an alternative, you can simply to set the unwanted property as undefined. 另一种选择是,你可以直接将将不想要的属性设置为 undefined

const object = {name:"Jane Doe"age:43};
object.age = undefined;

你还可以使用 Map 对象,因为根据 Bret,Map 的 delete 方法被认为更快。

11. 使用异步代码防止线程阻塞

你应该知道 JavaScript 是同步的,也是单线程的。但是在某些情况下,可能会花费大量的时间来执行一段代码。在本质上同步意味着,这段代码将阻止其他代码语句的运行,直到它完成执行,这会降低代码的整体性能。

但其实,我们可以通过实现异步代码来避免这种情况。异步代码以前是以回调的形式编写的,但是在 ES6 中引入了一种处理异步代码的新风格。这种新风格被称为 promises。你可以在 MDN 的官方文档 中了解更多关于回调和 promises 的信息。

等等…

JavaScript默认是同步的,也是单线程的

为什么在单一线程上运行,还能运行异步代码?这是很多人感到困惑的地方。这要归功于浏览器外壳下运行的 JavaScript 引擎。JavaScript 引擎是执行 JavaScript 代码的计算机程序或解释器。JavaScript 引擎可以用多种语言编写。例如,支持 Chrome 浏览器的 V8 引擎是用 c++ 编写的,而支持 Firefox 浏览器的 SpiderMonkey 引擎是用 C 和 c++ 编写的。

这些 JavaScript 引擎可以在后台处理任务。根据 Brian,调用栈识别 Web API 的函数,并将它们交给浏览器处理。一旦浏览器处理完成这些任务,它们将返回并作为回调推到堆栈上。

你有时可能想知道,Node.js 在没有浏览器帮助的情况下是如何运行的。事实上,为 Chrome 提供动力的 V8 引擎同样也为 Node.js 提供动力。下面是一篇由 Salil 撰写的非常棒的博客文章:Node.js真的是单线程吗,它解释了节点生态系统上的这个过程。

12. 使用代码分割

如果你有使用 Google Light House 的经验,你就会熟悉一个叫做“first contentful paint”的度量。它是 Lighthouse 报告的性能部分跟踪的六个指标之一。

First Contentful Paint(FCP)测量用户导航到页面后浏览器渲染 DOM 第一个内容所花费的时间。页面上的图像、非白色 元素和 SVG 被认为是 DOM 内容;iframe 中的任何内容都不被包含在内。

获得更高 FCP 分数的最好方法之一是使用代码分割。代码分割是一种在开始时只向用户发送必要模块的技术。减少最初传输的有效内容的大小,会显著地影响 FCP 得分。

流行的模块打包工具(如 webpack)提供了代码分割功能。你可以在原生 ES 模块的帮助下,加载各个模块。你可以阅读更多关于原生 ES 模块的 详细信息。

13. 使用异步 async 和延迟 defer

在现代网站中,脚本比 HTML 更密集,它们的尺寸更大,消耗更多的处理时间。默认情况下,浏览器必须等待脚本下载、执行,然后处理页面的其余部分。

庞大的脚本可能会阻塞网页的加载。为了避免这种情况,JavaScript 提供了两种技术,即异步和延迟。你只需将这些属性添加到