作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Youssef使用了React, Angular, NodeJS, 和Python来构建复杂的web应用程序, API services, 以及机器学习应用.
Node 8 is out! 事实上,Node 8已经发布了足够长的时间,可以看到一些可靠的实际使用情况. 它配备了快速的V8引擎和新功能, 包括异步/等待, HTTP/2, and async hooks. 但是它为你的项目准备好了吗? Let’s find out!
Editor’s note: 您可能已经意识到Node 10(代号为 Dubnium) is out, too. 我们选择专注于Node 8 (Carbon),原因有二:(1)Node 10刚刚进入其长期支持(LTS)阶段, (2)节点8的迭代比节点10的迭代更重要.
我们首先来看一下这个非凡版本的性能改进和新特性. 一个主要的改进是Node的JavaScript引擎.
What exactly is 一个JavaScript引擎?
JavaScript引擎执行并优化代码. 它可以是标准解释器,也可以是将JavaScript编译为字节码的即时(JIT)编译器. Node使用的JS引擎.js都是JIT编译器,而不是解释器.
Node.js has used Google’s Chrome V8 JavaScript引擎, or simply V8,从一开始. 一些Node版本用于与更新版本的V8同步. 但是注意不要混淆V8和Node 8,因为我们在这里比较V8版本.
这很容易被绊倒, 因为在软件环境中,我们经常使用“v8”作为俚语,甚至是“version 8”的官方缩写,,所以有些人可能会把“Node V8”或“Node”混为一谈.js V8”和“NodeJS 8”,但我们在本文中避免了这一点,以帮助保持清晰: V8总是指引擎,而不是Node的版本.
Node 6使用V8 release 5作为它的JavaScript引擎. (Node 8的前几个点版本也使用了V8 release 5, 但是他们使用了比Node 6更新的V8版本.)
V8版本5和更早的版本有两个编译器:
实际上,V8使用了不止一种类型的线程:
首先,Full-codegen编译器执行JavaScript代码. 当代码正在执行时, 分析器线程收集数据以确定引擎将优化哪些方法. 在另一个线程,曲轴优化这些方法.
上述方法有两个主要问题. 首先,它在建筑上很复杂. 第二,编译后的机器码消耗更多的内存. 所消耗的内存量与代码执行的次数无关. 即使是只运行一次的代码也会占用大量内存.
第一个使用V8版本6引擎的Node版本是Node 8.3.
在版本6中,V8团队构建了Ignition和TurboFan来缓解这些问题. 点火和涡扇分别取代全编码和曲轴.
新的架构更直接,消耗更少的内存.
Ignition 将JavaScript代码编译为字节码而不是机器码,节省了大量内存. Afterward, TurboFan优化编译器从这个字节码生成优化的机器码.
让我们浏览一下Node 8中性能有所提高的领域.3+相对于旧的Node版本发生了变化.
在Node 8中创建对象的速度大约快了5倍.3+ than in Node 6.
V8引擎根据几个因素来决定一个功能是否应该被优化. 一个因素是函数大小. 小函数被优化,而长函数没有.
老式V8发动机的曲轴使用“字符计数”来确定功能大小. 函数中的空白和注释减少了它被优化的机会. 我知道这可能会让你感到惊讶,但在那个时候,一个评论可能会使速度降低10%左右.
In Node 8.不相关的字符,如空格和注释不会影响函数的性能. Why not?
因为新的TurboFan不计算字符来确定函数大小. 相反,它计算抽象语法树(AST)节点,因此它有效地只考虑 实际功能说明. Using Node 8.3+,你可以随心所欲地添加注释和空白.
Array
-ifying Arguments中的正则函数 JavaScript carry an implicit Array
-like argument
object.
Array
-like Mean?The arguments
object acts somewhat like an array. It has the length
property but lacks Array
的内置方法,如 forEach
and map
.
Here’s how the arguments
object works:
function foo() {
console.log(arguments[0]);
//期望输出:a
console.log(arguments[1]);
//期望输出
console.log(arguments[2]);
//期望输出
}
foo("a", "b", "c");
那么我们如何转换 arguments
object to an array? By using the terse Array.prototype.slice.call(arguments)
.
function test() {
const r = Array.prototype.slice.call(arguments);
console.log(r.map(num => num * 2));
}
test(1, 2, 3); // Expected output: [2, 4, 6]
Array.prototype.slice.call(arguments)
会影响所有Node版本的性能. 因此,通过a for
循环性能更好:
function test() {
const r = [];
For (index in arguments) {
r.推动(参数(指标));
}
console.log(r.map(num => num * 2));
}
test(1, 2, 3); // Expected output [2, 4, 6]
The for
循环有点麻烦,不是吗? 我们可以使用扩展运算符,但它在Node 8中很慢.2 and down:
function test() {
const r = [...arguments];
console.log(r.map(num => num * 2));
}
test(1, 2, 3); // Expected output [2, 4, 6]
这种情况在Node 8中发生了变化.3+. 现在扩展执行得快多了,甚至比for循环还快.
柯里化是将一个有多个参数的函数分解成一系列函数,其中每个新函数只接受一个参数.
假设我们有一个简单的 add
function. 这个函数的柯里化版本只有一个参数, num1
. 它返回一个接受另一个参数的函数 num2
并返回的和 num1
and num2
:
函数add(num1, num2) {
return num1 + num2;
}
add(4, 6); // returns 10
函数curriedAdd(num1) {
返回函数(num2) {
return num1 + num2;
};
}
const add5 = curriedAdd(5);
add5(3); // returns 8
The bind
方法返回语法更简洁的柯里化函数.
函数add(num1, num2) {
return num1 + num2;
}
const add5 = add.bind(null, 5);
add5(3); // returns 8
So bind
令人难以置信,但是在旧的Node版本中速度很慢. In Node 8.3+, bind
快得多,您可以使用它而不用担心任何性能影响.
几个实验 已经进行了 比较Node 6和Node 8的性能. 注意,这些都是在Node 8上进行的.因此它们不包括上面提到的特定于Node 8的改进.多亏了V8版本6的升级.
Node 8中的服务器渲染时间比Node 6少了25%. 在大型项目中,服务器实例的数量可以从100个减少到75个. 这很惊人. 在Node 8中测试一组500个测试要快10%. Webpack的构建速度快了7%. 总的来说,结果显示在Node 8中有明显的性能提升.
速度并不是Node 8唯一的改进. 也许它还带来了一些方便的新功能 most importantly, async/await.
回调和承诺通常用于处理JavaScript中的异步代码. 回调因产生不可维护的代码而臭名昭著. 他们造成了混乱(具体称为 callback hell). 承诺将我们从回调地狱中解救了很长一段时间, 但是它们仍然缺乏同步代码的整洁性. Async/await是一种现代方法,它允许您 编写看起来像同步代码的异步代码.
而async/await可以在以前的Node版本中使用, 例如,它需要外部库和工具, 通过Babel进行额外的预处理. 现在它是本机可用的,开箱即用.
我将讨论async/await优于传统承诺的一些情况.
假设您正在获取数据,您将确定是否需要一个新的API调用 基于有效载荷. 看看下面的代码,看看这是如何通过“常规承诺”方法实现的.
const request = () => {
return getData().then(data => {
if (!data.car) {
返回fetchForCar(数据.id).then(carData => {
console.log(carData);
return carData;
});
} else {
console.log(data);
return data;
}
});
};
正如您所看到的,仅仅从一个额外的条件来看,上面的代码看起来已经很混乱了. Async/await的嵌套更少:
const request = async () => {
const data = await getData();
if (!data.car) {
const carData = await fetchForCar(data);
console.log(carData);
return carData;
} else {
console.log(data);
return data;
}
};
Async/await授予你在try/catch中处理同步和异步错误的权限. 假设您想解析来自异步API调用的JSON. 一个try/catch可以同时处理解析错误和API错误.
const request = async () => {
try {
console.日志(等待getData ());
} catch (err) {
console.log(err);
}
};
如果一个承诺需要一个应该从另一个承诺解决的参数怎么办? 这意味着异步调用需要连续执行.
使用传统的承诺,你可能会得到这样的代码:
const request = () => {
返回fetchUserData ()
.then(userData => {
返回fetchCompanyData(用户数据);
})
.then(companyData => {
返回fetchretiingplan (userData, companyData);
})
.then(retiringPlan => {
const retiringPlan = retiringPlan;
});
};
Async/await在这种情况下发挥了作用,需要链式异步调用:
const request = async () => {
const userData = await fetchUserData();
const companyData = await fetchCompanyData(userData);
const retiringPlan = await fetchRetiringPlan(userData, companyData);
};
如果您想并行调用多个异步函数该怎么办? 在下面的代码中,我们将等待 fetchHouseData
要解决,就打电话 fetchCarData
. 尽管每一个都是独立的,但它们是顺序处理的. 您将等待两秒钟,等待这两个api解析完毕. This is not good.
函数fetchHouseData() {
return new Promise(resolve => setTimeout(() => resolve("Mansion"), 1000));
}
函数fetchCarData() {
return new Promise(resolve => setTimeout(() => resolve("Ferrari"), 1000));
}
异步函数action() {
const house = await fetchHouseData(); // Wait one second
const car = await fetchCarData(); // ...然后再等一秒钟.
console.原木(房子,汽车,“串联”);
}
action();
更好的方法是并行处理异步调用. 查看下面的代码,了解这是如何在async/await中实现的.
异步函数parallel() {
houseDataPromise = fetchHouseData();
carDataPromise = fetchCarData();
const house = await houseDataPromise; // Wait one second for both
const car = await carDataPromise;
console.原木(房子,汽车,“平行”);
}
parallel();
并行处理这些调用只需要为两个调用等待一秒钟.
Node 8还带来了一些新的核心功能.
在Node 8之前,为了复制文件,我们过去常常创建两个流,并将数据从一个流传输到另一个流. 下面的代码显示了读流如何将数据输送到写流. 正如您所看到的,对于复制文件这样简单的操作,代码是混乱的.
Const fs = require('fs');
const rd = fs.createReadStream(“源文件.txt');
rd.on('error', err => {
console.log(err);
});
const wr = fs.createWriteStream(“目标.txt');
wr.on('error', err => {
console.log(err);
});
wr.On ('close', function(ex) {
console.日志(“文件复制”);
});
rd.pipe(wr);
In Node 8 fs.copyFile
and fs.copyFileSync
是否有新的方法可以减少复制文件的麻烦.
Const fs = require("fs");
fs.copyFile("firstFile.txt", "secondFile.txt", err => {
if (err) {
console.log(err);
} else {
console.log("File copied");
}
});
util.promisify
将普通函数转换为异步函数. 注意,输入的函数应该遵循公共Node.js callback style. 它应该接受一个回调作为最后一个参数,i.e., (error, payload) => { ... }
.
Const {promisify} = require('util');
Const fs = require('fs');
const readFilePromisified = promisify(fs . conf).readFile);
Const file_path = process.argv[2];
readFilePromisified (file_path)
.then((text) => console.log(text))
.catch((err) => console.log(err));
As you could see, util.promisify
has converted fs.readFile
到一个async函数.
另一方面,Node.js comes with util.callbackify
. util.callbackify
is the opposite of util.promisify
:将async函数转换为Node.Js回调样式函数.
destroy
可读和可写的函数The destroy
函数是一个销毁/关闭/中止可读或可写流的文档化方法:
Const fs = require('fs');
const file = fs.createWriteStream('./big.txt');
file.on('error', errors => {
console.log(errors);
});
file.write(`New text.\n`);
file.destroy(['First Error', 'Second Error']);
上面的代码创建了一个名为 big.txt
(如果它不存在的话) New text.
.
The Readable.destroy
and Writeable.destroy
Node 8中的函数发出a close
event and an optional error
event—destroy
并不一定意味着出了什么问题.
展开运算符(又名 ...
)可以在Node 6中工作,但只能用于数组和其他可迭代对象:
Const arr1 = [1,2,3,4,5,6]
const arr2 = [...arr1, 9]
console.Log (arr2) //期望输出:[1,2,3,4,5,6,9]
在Node 8中,对象也可以使用扩展操作符:
const userCarData = {
type: 'ferrari',
color: 'red'
};
const userSettingsData = {
lastLoggedIn:“12/03/2019”,
featuresPlan:“溢价”
};
const userData = {
...userCarData,
name: 'Youssef',
...userSettingsData
};
console.log(userData);
/*期望输出:
{
type: 'ferrari',
color: 'red',
name: 'Youssef',
lastLoggedIn:“12/03/2019”,
featuresPlan:“溢价”
}
*/
实验性功能不稳定,可能会被弃用,并且可能会随着时间的推移而更新. 中不要使用任何这些特性 production 直到它们变得稳定.
异步钩子通过API跟踪在Node内部创建的异步资源的生命周期.
在进一步使用异步钩子之前,请确保理解了事件循环. This video might help. 异步钩子对于调试异步函数很有用. They have several applications; one of them is error stack traces for async functions.
看看下面的代码吧. Notice that console.log
是async函数吗. 因此,它不能在异步钩子中使用. fs.writeSync
is used instead.
const asyncHooks = require('async_hooks');
Const fs = require('fs');
const init = (asyncId, type, triggerId) => fs.writeSync(1, ' ${type} \n ');
const asynchoks = asynchoks.createHook({init});
asyncHook.enable();
Watch this video 了解更多关于异步钩子的知识. In terms of a Node.我的指南, this article 通过一个说明性的应用程序帮助揭开异步钩子的神秘面纱.
Node 8现在支持ES6模块,允许你使用以下语法:
import {UtilityService}./utility_service';
要在Node 8中使用ES6模块,需要执行以下操作.
——experimental-modules
命令行标志.js
to .mjs
HTTP/2是对不经常更新的HTTP协议和Node 8的最新更新.4+ 本机支持 在实验模式下. 它比它的前身HTTP/1更快、更安全、更高效.1. And 谷歌建议你使用它. 但它还能做什么呢?
In HTTP/1.1、服务器一次只能对每个连接发送一个响应. 在HTTP/2中,服务器可以并行发送多个响应.
服务器可以为单个客户机请求推送多个响应. 为什么这是有益的?? 以web应用程序为例. Conventionally,
服务器推送特性利用了服务器已经知道所有这些资源的事实. 服务器将这些资源推送给客户端. 对于web应用程序的例子, 在客户端请求初始文档后,服务器推送所有资源. 这减少了延迟.
客户端可以设置优先级方案,以确定每个所需响应的重要性. 然后,服务器可以使用此方案来确定内存分配的优先级, CPU, bandwidth, 还有其他资源.
Since HTTP/1.不允许多路复用, 使用了一些优化和变通方法来掩盖缓慢的速度和文件加载. 不幸的是,这些技术导致内存消耗增加和渲染延迟:
现在使用HTTP/2,您可以忘记这些技术并专注于您的代码.
大多数浏览器只通过安全的SSL连接支持HTTP/2. This article 可以帮助您配置自签名证书吗.
Add the generated .crt
file and .key
目录中的文件 ssl
. 然后,将下面的代码添加到名为 server.js
.
记得使用 --expose-http2
在命令行中启用此特性. I.e. 我们示例中的run命令是 node server.js --expose-http2
.
Const http2 = require('http2');
Const path = require('path');
Const fs = require('fs');
const PORT = 3000;
const secureServerOptions = {
cert: fs.readFileSync(path.join(__dirname, './ssl/server.crt')),
key: fs.readFileSync(path.join(__dirname, './ssl/server.key'))
};
Const server = http2.createSecureServer(secureServerOptions, (req, res) => {
res.statusCode = 200;
res.end('来自Toptal的Hello ');
});
server.listen(
PORT,
err =>
err
? console.error(err)
: console.日志('服务器正在监听端口${port} ')
);
当然,还有Node 8, Node 9, Node 10等等. 仍然支持旧的HTTP 1.1 -官方节点.js documentation on 标准HTTP事务 不会陈腐很久的. 但是如果你想使用HTTP/2,你可以使用 this Node.js guide.
Node 8带来了性能改进和async/await等新特性, HTTP/2, and others. 端到端实验表明,Node 8比Node 6快25%左右. This leads to 节省大量成本. 所以对于绿地项目来说,绝对是这样! 但是对于现有的项目,你应该 update Node?
这取决于您是否需要更改大部分现有代码. This document 如果您是从Node 6过来的,则列出所有Node 8的破坏性更改. 请记住,通过重新安装所有的项目来避免常见问题 npm
使用最新的Node 8版本. 此外,始终使用相同的Node.Js版本在开发机器上和在生产服务器上一样. Best of luck!
Node.js是一个开源的JavaScript运行时,可以在Windows等各种平台上运行, Linux, and Mac OS X. Node.js是建立在Chrome的V8 JavaScript引擎.
Node.js用于在服务器端运行JavaScript. 与其他运行时和语言一样,Node.Js可以处理服务器上的文件, 与数据库通信, 生成动态页面内容, 构建RESTful API服务, etc.
Node.Js在处理实时双向通信和开箱即用运行异步代码方面更胜一筹. 这有助于创建高度可伸缩的高性能应用程序. 此外,已经知道JavaScript的前端开发人员不需要学习另一种语言.
LTS代表“长期支持”.在这种情况下,Node.js 8处于LTS阶段,这意味着它是最稳定的版本之一.
Youssef使用了React, Angular, NodeJS, 和Python来构建复杂的web应用程序, API services, 以及机器学习应用.
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.