原PPT地址
翻译:@仙森
本文经译者授权,刊登在 Alinode 团队博客。如未经译者授权,谢绝转载。
主要内容
- 5月30日发布 Node 8.0.0。
- 8.x (LTS) 的 code name 是 Carbon。
- ES2017 features 全部可以使用, 除了 shared memory 和 atomics。
- N-API 已经添加,Native 模块开发更加方便。
- WHATWG-URL的实施得到了增强。
- 通过 util.promisify(),不需要再显式创建 Promise 了。
LTS - Long Term Support
github: nodejs/LTS
LTS介绍
偶数版本会在每年四月发布,然后在10月份开始长期支持。
LTS 会一直支持18个月,然后将进入长达一年的维护期。
这两种方式的区别主要是修复的优先级。
- Bug修复
- 安全更新
- 文档更新
- 无破坏性的功能更新
发布计划
版本表格
Release | LTS Status | Codename | Active LTS Start | Maintenance Start | Maintenance End |
---|---|---|---|---|---|
v0.10 | End-of-Life | - | - | 2015-10-01 | 2016-10-31 |
v0.12 | End-of-Life | - | - | 2016-04-01 | 2016-12-31 |
4.x | Active | Argon | 2015-10-01 | 2017-04-01 | 2018-04-01 |
5.x | No LTS | ||||
6.x | Active | Boron | 2016-10-18 | 2018-04-18 | 2019-04-18 |
7.x | No LTS | ||||
8.x | Pending | Carbon | 2017-10-01 | 2019-04-01 | 2019-12-31 |
9.x | No LTS | ||||
10.x | Pending | Pending | 2018-10-01 | 2020-04-01 | 2021-04-01 |
V8
V5.7主要更新
- promise 和 async 提速
- spread operator, destructuring 和 generators 提速
- 通过 TurboFan, RegExp 提速了 15%
- padStart 和 padEnd 被添加到了 es2017 (ECMA 262)
-
Intl.DateTimeFormat.prototype.formatToParts
被添加到了 (ECMA402) - WebAssembly 默认打开
- 添加 PromiseHook
v8-release-57
speeding-up-v8-regular-expressions
V5.8主要更新
- 任意设置堆大小的限制值 (范围是带符号32位整数)
- 启动性能提升约5%
- 缩短 IC 系统的代码编译,分析,优化时间
v8-release-58
one-small-step-for-chrome-one-giant
how-v8-measures-real-world-performance
ES2017 在 (v5.7, v5.8) 中的状况
features | v5.7 | v5.8 |
---|---|---|
Object static methods | yes | yes |
String padding | yes | yes |
Trailing commas in function syntax | flag | yes |
Async Functions | yes | yes |
Shared memory and atomics | no | no |
Object static methods: values
, entries
, getOwnPropertyDescriptors
Table: node.green, compat-table
Slide: abouthiroppy/ecmascript
ES2017(MISC, ANNEX B), ES2018
-
ES2017
-
ES2018
-
- v5.9 With flag
-
TURBOFAN + IGNITION
V8: Behind the Scenes (November Edition feat. Ignition+TurboFan and ES2015)
TURBOFAN 编译器
V8 优化了 JIT 编译器,它是用 Sea of Nodes 概念进行设计的。之前采用的编译技术是 Crankshaft,它支持优化更多的代码。但是 ES 的标准发展很快,后来发现 Crankshaft 已经很难去优化 ES2015 代码了,而通过 Ignition 和 TurboFan 可以做到。
TurboFan
High-performance ES2015 and beyond
IGNITION 解释器
已知的是到目前为止 V8 都没有自己的解析器,都是直接把 JS 编译成机器码。
作为JIT的JIT的问题,即使代码被执行一次,它也消耗大量的存储空间,所以需要尽量避免内存开销。
使用 Ignition,可以精简 25% - 50% 的机器码。
Ignition 是没有依赖 Turbofan 的底层架构,它采用宏汇编指令。为每个操作码生成一个字节码处理程序。
通过 Ignition 可以较少系统内存使用情况。
如图:
Ignition Design Doc
Firing up the Ignition Interpreter
Node.js 与 v8 底层探索
8.0.0
变更了 8.0.0 的发布时间
原计划是四月25号发布的,现在被延期到了5月30号。
V8版本从 v5.7 更新到 v5.8。
这是为了兼容 ABI(Application Binary Interface)6.0。
让 V8 Ignition 和 TurboFan pipeline 进入 8.0.0,也方便 9.x backport 到 LTS。
Ignition 和 TurboFan 在 v5.9 默认打开,到时候 Node 会以 semver-minor 方式升级到 v5.9。
V8 plan for Node.js LTS Carbon (A potential path to TurboFan + Ignition)
Ignition + TurboFan: Node.js benchmarks
计划内容
- 04月21日: V8 v5.8 进入 stable
- 05月09日: semver-major 冻结
- 05月中旬: v6.0 API / ABI 进入 stable
- 05月30日: Node8 发布
- 06月上旬: 如果 V8 v5.9 stable 则会被作为 semver-minor 进行更新
- 08月上旬: 更新到 V8 v6.0
版本说明
- semver-major: 8.0.0
- semver-minor: 7.x.0
- semver-patch: 7.x.y
N-API
semver-minor since 7.8.0
nodejs/abi-stable-node
什么是 N-API(NODE-API)?
ABI stable abstraction layer of native module
不同的 Node 版本、VM间,提供 ABI (Application Binary Interface) 来保证兼容性,
支持 N-API 的本地模块将无需重新编译就可以工作。
VM Summit - 2017-03-03
node-eps/005-ABI-Stable-Module-API.md
module: add support for abi stable module API
问题点
目前 Node 的实现中,V8 的 API 是直接暴露出来的。由于 V8 经常变更 API,那就存在下面的这些问题:
- Native 模块在每个版本间需要重新编译
- Native 模块需要变更代码
- Native 模块无法工作在其他JS engine 上 (比如: ChakraCore)
这些问题之前的 NAN(Native Abstractions for Node.js)(https://github.com/nodejs/nan) 搞不定。
N-API
目前已经在 ChakraCore 中验证了
已经进行 N-API 适配的模块
Module | Conversion Status | Performance Assesment |
---|---|---|
leveldown | Completed | #55 |
nanomsg | Completed | #57 |
canvas | Completed | #77 |
node-sass | Completed | #82 |
iotivity | Completed | N/A |
node-sqlite3 | Started | - |
ASSERT
默认支持 map, set
semver-major
> assert.deepEqual(new Set([1, 2]), new Set([1]))
AssertionError: Set { 1, 2 } deepEqual Set { 1 }
at repl:1:8
at ContextifyScript.Script.runInThisContext (vm.js:44:33)
at REPLServer.defaultEval (repl.js:239:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval](domain.js:314:12)
at REPLServer.onLine (repl.js:433:10)
at emitOne (events.js:120:20)
at REPLServer.emit (events.js:210:7)
at REPLServer.Interface._onLine (readline.js:262:10)
at REPLServer.Interface._line (readline.js:611:8)
> assert.deepEqual(new Set([1, 2]), new Set([1, 2]))
undefined
BUFFER
添加 icu.transcode()
semver-minor since 7.1.0
通过使用 icu transcode,把 Node 支持的编码从缓冲区转移到另一个缓冲区。
> const icu = process.binding('icu');
undefined
> const newBuf = icu.transcode(Buffer.from('€'), 'utf8', 'ascii'); // source, from, to
undefined
> console.log(newBuf)
<Buffer 3f>
> Buffer.isBuffer(newBuf)
true
CHILD_PROCESS
添加 channel
到公共接口
semver-minor since 7.1.0
之前是名为 _channel
的私有接口,这个私有接口将来会被废弃掉。
IPC(Inter Process Communication) 会返回一个到 channel 的引用。
> const fork = require('child_process').fork
> const n = fork('./test.js')
> n.channel
Pipe {
bytesRead: 0,
_externalStream: [External],
fd: 12,
writeQueueSize: 0,
buffering: false,
onread: [Function],
sockets: { got: {}, send: {} } }
> n.channel === n._channel
true
child_process: add public API for IPC channel
CLUSTER
worker.disconnect() 返回引用
semver-minor since 7.3.0
if (cluster.isMaster) {
const worker = cluster.fork();
cluster.fork().on('listening', (address) => {
setTimeout(() => {
const w = worker.disconnect();
console.log(w); // undefined
// Worker // 7.3.0 ~
}, 1000);
});
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
http.createServer((req, res) => {}).listen(8000);
}
cluster: return worker reference from disconnect()
CRYPTO
setAuthTag 和 setAAD 返回 this 对象
semver-minor since 7.2.0
> const key = '0123456789'
> const tagbuf = Buffer.from('tagbuf')
> const aadbuf = Buffer.from('aadbuf')
> const decipher = crypto.createDecipher('aes-256-gcm', key)
> decipher.setAuthTag(tagbuf)
Decipher {
_handle: {},
_decoder: null,
_options: undefined,
writable: true,
readable: true }
> assert.strictEqual(decipher.setAuthTag(tagbuf), decipher)
undefined
> assert.strictEqual(decipher.setAAD(aadbuf), decipher)
undefined
crypto: return this in setAuthTag/setAAD
支持系统 CA
semver-minor since 7.5.0
支持能够使用系统提供 CA。
CLI: --use-openssl-ca
, --use-bundled-ca
环境变量: SSL_CERT_DIR=dir
, SSL_CERT_FILE=file
crypto: use system CAs instead of bundled ones
DNS
TTL 变成可用
semver-minor since 7.2.0
可以通过 dns.resolve4()
、 dns.resolve6()
获取每个记录的TTL(生存时间)。
> dns.resolve4("google.com", {ttl:true}, console.log)
null [ { address: '216.58.221.174', ttl: 273 } ]
> dns.resolve6("google.com", {ttl:true}, console.log)
null [ { address: '2404:6800:400a:807::200e', ttl: 242 } ]
dns: implement {ttl: true} for dns.resolve4() and dns.resolve6()
FS
支持 File 协议
semver-minor since 7.6.0
在 fs 模块中支持使用 whatwg-url 的文件协议。
根据 whatwg-url 的 spec,必须使用文件的绝对路径。
> const URL = require('url').URL;
undefined
> const myURL = new URL('file:///C:/path/to/file');
undefined
> fs.readFile(myURL, (err, data) => {});
TypeError: path must be a string or Buffer
// 7.6.0 ~
> fs.readFile(myURL, (err, data) => {});
undefined
fs: allow WHATWG URL and file: URLs as paths
fs.SyncWriteStream 已经废弃
semver-major
> fs.SyncWriteStream
...
> (node:57352) [DEP0061] DeprecationWarning: fs.SyncWriteStream is deprecated.
HTTP/HTTPS
添加 OutgoingMessage
semver-minor since 7.7.0
添加了三个方法:getHeaderNames()
, getHeaders()
, hasHeader()
无需再调用 _headers
私有对象
const http = require('http');
http.createServer((req, res) => {
res.setHeader('x-test-header', 'testing');
res.setHeader('X-TEST-HEADER2', 'testing');
console.log(res._headers); // { 'x-test-header': 'testing', 'x-test-header2': 'testing' }
console.log(res.getHeaders()); // { 'x-test-header': 'testing', 'x-test-header2': 'testing' }
console.log(res.getHeaderNames()); // [ 'x-test-header', 'x-test-header2' ]
console.log(res.hasHeader('X-TEST-HEADER2')); // true
}).listen(3000, function() {
http.get({ port: this.address().port }, (res) => {});
});
http: add new functions to OutgoingMessage
在 request 中使用 URL
semver-minor since 7.5.0
http.request
和 https.request
可以使用 URL 对象。
const http = require('http');
const url = require('url');
const URL = url.URL;
const server = http.createServer((req, res) => {
console.log(req.url); // /foo?bar
console.log(req.method); // GET
res.end();
server.close();
}).listen(3000, function() {
const u = `http://localhost:${this.address().port}/foo?bar`;
http.get(u);
http.get(url.parse(u));
http.get(new URL(u)); // 7.5.0 ~
});
url: allow use of URL with http.request and https.request
INSPECTOR
添加 --inspect-brk
semver-minor since 7.6.0
--debug-brk
通过这个参数,在开始调试的时候能够定位到代码的第一行。
$ node --inspect-brk test.js
Debugger listening on 127.0.0.1:9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229 ...
Debugger attached.
inspector: add --inspect-brk flag
V8 inspector 的切换
semver-major
Node 8 之后 --debug
就不再支持了。
$ node --debug
node: bad option: --debug
$ node --inspect
Debugger listening on 127.0.0.1:9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229 ...
>
$ node --inspect-brk
Debugger listening on 127.0.0.1:9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229 ...
Switch the CLI debugger to V8 inspector
deps: Add node-inspect
src: Remove support for --debug
添加 --
semver-minor since 7.5.0
--
在 -e
之后说明是 end-of-options
$ node -e "console.log(process.argv)" -- -arg1 -arg2
[ '/Users/xxx/.yyy/node/v7.4.0/bin/node' ]
# 7.5.0 ~
$ node -e "console.log(process.argv)" -- -arg1 -arg2
[ '/Users/xxx/.yyy/node/v7.5.0/bin/node', '-arg1', '-arg2' ]
--
after -e <script>
means end-of-options
PROCESS
添加 NODE_NO_WARNINGS
semver-minor since 7.5.0
通过设置 NODE_PRESERVE_SYMLINKS
环境变量为 1
关闭进程的 warning 信息。
参数 --no-warnings
效果同上。
process: add NODE_NO_WARNINGS environment variable
添加 NODE_PRESERVE_SYMLINKS
semver-minor since 7.1.0
通过设置 NODE_PRESERVE_SYMLINKS
环境变量为 1
开启符号链接。
参数 --preserve-symlinks
效果同上。
Add NODE_PRESERVE_SYMLINKS environment variable
添加 externalMemory
semver-minor since 7.2.0
返回 C++ 对象的内存使用量。
> console.log(util.inspect(process.memoryUsage()));
{ rss: 23371776,
heapTotal: 10465280,
heapUsed: 5756560 }
// 7.2.0 ~
> console.log(util.inspect(process.memoryUsage()));
{ rss: 23244800,
heapTotal: 7692288,
heapUsed: 4918392,
external: 22846 }
process: add externalMemory
to process.memoryUsage
PROMISE
堆栈的优化
通过参数 --trace-warnings
开启优化 Promise
和 UnhandledPromiseRejectionWarning
堆栈信息。
$ node --trace-warnings
> const p = Promise.reject(new Error('This was rejected'))
> setImmediate(() => p.catch(() => {}))
(node:40981) Error: This was rejected
...
(node:40981) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated.
In the future, promise rejections that are not handled will terminate
the Node.js process with a non-zero exit code.
...
(node:40981) PromiseRejectionHandledWarning:
Promise rejection was handled asynchronously (rejection id: 1)
at getAsynchronousRejectionWarningObject (internal/process/promises.js:12:10)
at rejectionHandled (internal/process/promises.js:42:21)
...
promise: better stack traces for --trace-warnings
URL
WHATWG-URL
UTIL
添加 [Array] 的符号
semver-major
对于嵌套数组,改为写为[Array]。
> const obj = util.inspect({'a': {'b': ['c']}}, false, 1)
> obj
'{ a: { b: [Object] } }'
> assert.strictEqual(obj, '{ a: { b: [Array] } }')
AssertionError: '{ a: { b: [Object] } }' === '{ a: { b: [Array] } }'
// 8.0.0
> obj
'{ a: { b: [Array] } }'
> assert.strictEqual(obj, '{ a: { b: [Array] } }')
undefined
添加新的 format
semver-minor since 7.9.0
-
%i
: 格式化为整型 -
%f
: 格式化为浮点型
> util.format('%d', 42.2)
'42.2'
> util.format('%i', 42.2)
'%i 42.2'
> util.format('%f', 42.2)
'%f 42.2'
// 8.0.0
> util.format('%d', 42.2)
'42.2'
> util.format('%i', 42.2)
'42'
> util.format('%f', 42.2)
'42.2'
util: add %i and %f formatting specifiers
从 util.format 中删除 SIMD
semver-major
这是因为 V8 已经不支持 SIMD 了。
$ node --harmony_simd
> assert.strictEqual(util.inspect(SIMD.Int32x4()), 'Int32x4 [ 0, 0, 0, 0 ]');
undefined
// 8.0.0
> assert.strictEqual(util.inspect(SIMD.Int32x4()), 'Int32x4 [ 0, 0, 0, 0 ]');
AssertionError: 'Int32x4 {}' === 'Int32x4 [ 0, 0, 0, 0 ]'
lib: remove simd support from util.format()
url.format 支持 WHATWG-URL
semver-minor since 7.6.0
> const URL = require('url').URL
> const myURL = new URL('http://example.org/?a=b#c')
> const str = url.format(myURL, {fragment: false, search: false})
> console.log(str)
http://example.org/?a=b#c
// 7.6.0 ~
> console.log(str)
http://example.org/
url: extend url.format to support WHATWG URL
v8对象
添加 does_zap_garbage
semver-minor since 7.2.0
v8 HeapStatistics 已经添加 does_zap_garbage
。
这是一种覆盖堆垃圾的模式。
通过参数--zap_code_space
开启。malloced_memory
, peak_malloced_memory
同时被添加到字段中。
> v8.getHeapStatistics()
{ total_heap_size: 7168000,
total_heap_size_executable: 3670016,
total_physical_size: 6132432,
total_available_size: 1492201768,
used_heap_size: 5416688,
heap_size_limit: 1501560832,
malloced_memory: 8192,
peak_malloced_memory: 1412016,
does_zap_garbage: 0 }
src: Add does_zap_garbage to v8 HeapStatistics
正在进行中的任务
迁移 errors 到 internal/errors.js
semver-major
在当前 Node 的核心模块中,每个文件中都会定义错误信息。现在的任务是统一到internal/errors.js
/lib/internal/errors.js
Tracking Issue: Migrate errors to internal/errors.js
支持 Uint8Array
- open: stream: support Uint8Array input to methods
- open: string_decoder: support Uint8Array input to methods
- closed: zlib: support Uint8Array in convenience methods
- closed: dgram: support Uint8Array input to send()
- closed: tls: support Uint8Arrays for protocol list buffers
- closed: crypto: support Uint8Array prime in createDH
- closed: child_process: support Uint8Array input to methods
- closed: fs: support Uint8Array input to methods
- closed: buffer: allow Uint8Array input to methods
添加 promisify()
semver-minor since ???
这可以说是最令人兴奋的 PR 了。通过 util.promisify,原生模块完美支持 async/await
const util = require('util');
const setTimeoutPromise = util.promisify(setTimeout);
setTimeoutPromise(2000, 'foobar').then((value) => console.log(value));
const stat = util.promisify(require('fs').stat);
async function callStat() {
const stats = await stat('.');
console.log(`This directory is owned by ${stats.uid}`);
}
callStat();