本系列其他译文请看[JS工作机制 - 小白1991的专栏 - 掘金 (juejin.cn)] (juejin.cn/column/6988…
本文阅读指数:3
本文偏重于对Node的分析,并且是基于语法层面的。建议阅读译者的另一篇文章2021年前端性能优化的思路 (juejin.cn)
总览
JS是一个很流行的语言,在客户端,服务端,手机和桌面应用上都有使用。因此,构建高性能的JS应用是很必要的。 这一章,我们会讨论一些引起性能下降的常见问题,以及优化性能的一些建议---它们可能帮助你解决一些大问题。
常见错误
我们列举一些常见的错误,显著影响我们的JS性能。
1. 太多的第三方库或者脚本 随着应用越来越大,可能就会有很多的Node依赖,插件,甚至脚本,他们会严重影响我们应用的性能。因此,移除不必要的依赖。
2. 没有CDN
提升具有很多web内容的应用性能的一个有效途径是,使用CDN。它将会从距离你最近的服务器高速分发静态内容。
3. HTTP 请求太多
你的网站,通过HTTP请求跟服务器交流,才能显示信息。这意味这,页面加载的时候需要很多请求。如果你的网站加载了很多脚本和资源,就会有性能问题。
4. 没有最小化JS代码 最小化JS文件,将会移除注释,缩短变量名,移除空格,也会移除无用代码。这些都不会影响应用的功能,但是会缩短运行时间,因为文件的大小减少了。最小化代码,有很多工具可以使用。
优化提示
我们讨论一些优化手段,让我们的JS代码有更好的性能
- 使用 Promise.all替代await
这个意思是我们不用等待执行,而是把我们的调用和unresolved 的promise绑定到一个数组中。 例如,我们想向数据库调用两个请求,通常我们会给每一次调用加上await。
const getUsers = async () => {
const customers = await findAllCustomers();
const admins = await findAllAdmins();
return { customers, admins }
}
最好的方式是并行调用,并用接近一半的时间来resolve结果:
const getUsers = async () => {
const customers = findAllCustomers();
const admins = findAllAdmins();
return Promise.all([customers, admins]);
}
我们让调用并行的工作,这样能节省等待执行的时间。
2. 有效循环
使用一个好的循环声明,也有效提升JS应用的性能。依赖你的应用或者你的进程,你或许想使用声明方式来循环你的数据。循环声明包含for-loop 或者 while loop,类似的还有声明式的*map, reduce, filter,*等等。
看看,我们如何使用 map, filter, and reduce 来遍历数据:
// Imagine we had a thousand software engineers data to loop from
const result = allSoftwareEngineers
.map(engineer => engineer.salary)
.filter(salary => salary > 100000)
.reduce((prev, next) => prev + next, 0);
我并不是说map, reduce和其他方法不好,只是在数据量很大的时候这样容易成性能瓶颈。对比一下声明式和命令式循环的区别 Performance-analysis 我们改变一下。
let results = 0;
for (engineer of allSoftwareEngineers) {
// Check if salary is above 100000
if (engineer.salary > 100000) results += engineer.salary;
}
这种方式提升了至少3倍的性能。
3.在Cluster中执行app
在Node中,我们可以使用[Cluster 模块]来运行子程序。我们创建的每一个子clusters或者进程,运行在自己的V8,事件循环和内存中。这么做的目的是给每个进程分发加载和任务。
在node中创建 cluster:
const express = require('express');
const app = express();
const port = 5000;
const cluster = require('cluster');
const numberOfCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Number of CPUs is ${numberOfCPUs}`);
console.log(`Master ${process.pid} running`);
// Fork workers - create a child process.
for (let i = 0; i < numberOfCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
console.log("Let's fork another worker!");
cluster.fork();
});
} else {
console.log(`Worker ${process.pid} started`);
app.get('/', (req, res) => {
res.send('Hello Worker');
})
app.listen(port, () => {
console.log(`Listening on port ${port}`);
})
}
我们创建了一个node 的cluster,来处理加载任务。
4. 用户多的时候使用缓存响应
在一些特殊的时期,我们可能想缓存服务器的响应,这样用户不用等待同样的数据。在这些时期内,我们的应用数据几乎不会被改变,因此不用去数据库查询,而是直接从缓存中获取
5. 使用进程管理
开发的时候,我们会使用node app.js来启动项目,如果项目崩溃,就再运行一次。但是在发布环境,你可能无法重启它。这时,就需要一个进程管理,比如 PM2 ****或者 Forever。
除此之外,他们还为提供了性能上的更多细节和观察。
6. 使用Node.js 流替代 fs.readFile
使用fs.readFile已经是读文件的一个主流方案了。它会读取整个文件,然后放在内存中。如果处理的文件很大,那么内存和CPU的使用率就会飙升。使用node 流会更好一点,它直接从磁盘中读文件,并通过回应发送文件。 使用流:
const fs = require("fs");
const data = '';
const readerStream = fs.createReadStream('file.txt'); // Create a readable stream
readerStream.setEncoding('UTF8'); // Set the encoding to be utf8.
// Handle stream events = data, end, and error
readerStream.on('data', function(chunk) {
data += chunk;
});
readerStream.on('end',function() {
console.log(data);
});
readerStream.on('error', function(err) {
console.log(err.stack);
});
console.log("File Read");
7.使用异步函数
Node 是单线程的。如果你执行的操作会阻塞线程---比如从数据库/文件系统中读取或者执行一个CPU密集型的任务,你应该使用异步函数,这样不会阻塞线程。
8. 使用 HTTP/2
最新的HTTP协议版本是HTT/2。它的好处很多,其中一个是混合,它允许你使用同一个TCP连接,同时接受并行的请求和响应。在性能上,比HTTP/1要好的多,它可以一次性传递多个资源到客户端。