Node.js v4リリースに向けて
とうとうメジャーバージョンアップにされたNode.jsである、Node.js v4がリリースされました。今回はこのNode.js v4がこれまでのNode.js v0.12やv0.10と比較してどう違うのかを解説します。また、最新ではv5もリリースされていますので、合わせてお伝えしていきます。
なんでいきなりv4なのか
おそらく一番最初に抱く感想は、v0.10とかv0.12みたいな数字からv1.0を飛ばして、なんでいきなりv4.0がリリースされたのかという疑問だと思います。これにはio.jsというプロダクトが関係しています。
2014年の年末、io.jsというプロダクトが発表され、2015年の初めにv1.0がリリースされました。io.jsというのは Node.js のforkで別リポジトリによって実装されたプロダクトです。io.jsの詳細は筆者のブログを確認してください。io.jsがリリースされた後、週次リリースという早いサイクルでリリースするモデルを確立し、8カ月間もの間リリースを続けてきました。結果として、io.jsはv3までリリースされています。
紆余曲折を経た後に、Node.jsはこのio.jsと再び統合されることになります。統合までの経緯について、興味がある読者の方は筆者のYAPCでの発表資料を参考にしてください。
統合される際にio.jsの既存バージョンと競合しないようにした結果、Node.jsはv1.0からv3.0を飛ばしていきなりv4.0がリリースされることになりました。
Node.js v4とNode.js v0.12の違い
Node.js v4はio.js v3が元になっています。つまり、io.js v1からv3までにはいった機能が、そのままNode.js v4では利用可能です。筆者がまとめた内容を元に、Node.js v4で入った新機能や違いを解説します。また、v5で入った機能は本資料の最後にまとめて紹介します。
JavaScript Syntax/Built-in Object の違い
Node.js v4は内部的にJavaScript実行エンジンである、V8のv4.5を採用しています。これにより下記の部分でJavaScript全体の機能が進化しました。
- ES2015のサポート範囲拡大
- StrongScript等のJavaScriptの新機能対応
- Intlオブジェクトのサポートによる国際化対応
一つ一つ説明していきます。
ES2015のサポート範囲拡大
ES2015のサポート範囲がNode.js v0.12と比較して拡大しました。具体的には下記の機能がデフォルトで有効になっています。
- let/const等のblock scope (“use strict”が必須)
- class構文(“use strict”が必須)
- Map/Set/WeakMap/WeakSetといった新しいコレクションオブジェクト
- Generator構文
- Binary/Octalリテラル
- Symbolオブジェクト
- Template Stringリテラル
- String.prototype.repeat
- Symbol.toStringTag
- 拡張Objectリテラル
- Unicodeリテラル
- アロー関数
ES2015の構文のサポート範囲が変わったことで今までNode.jsで記述していたJavaScriptは変わっていくだろうと予測されます。具体的にどのように変わるのか、EventEmitter
を拡張してMyEventEmitter
を作るというケースを参考に考えてみましょう。
v0.12まではEventEmitter
を拡張する場合、以下のように記述していました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
'use strict'; var events = require('events'); var util = require('util'); var EventEmitter = events.EventEmitter; var MyEventEmitter = function(data) { this.data = data; }; // EventEmitterを継承する util.inherits(MyEventEmitter, EventEmitter); MyEventEmitter.prototype.intervalCheck = function() { var originalData = this.data; var _this = this; setInterval(function (){ if (originalData !== _this.data) { _this.emit('change', new Date() + ': ' + _this.data); originalData = _this.data; } }, 1000); }; module.exports = MyEventEmitter; |
このMyEventEmitter
は、intervalCheck
という内部的にdata
が変わっていないかどうかを1秒ずつ調べて変更があったら、change
イベントを発火させるという動きを行っています。v4からは下記のようにES2015
の構文を使って下記のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
'use strict'; // 変更されないようにするためにconstで変数宣言する const events = require('events'); const EventEmitter = events.EventEmitter; // util.inheritsではなく、class構文とextendsを使う class MyEventEmitter extends EventEmitter { constructor(data) { this.data = data; } intervalCheck() { // 基本的にletで変数宣言する let originalData = this.data; // アロー関数を利用する。 setInterval(() => { if (originalData !== this.data) { // Template String Literalで変数の情報を文字列に埋め込む this.emit('change', `${new Date()}: ${this.data}`); originalData = this.data; } }, 1000); } } module.exports = MyEventEmitter; |
ひとつ前のコードと比較すると大分コードが短く、シンプルに記述できるようになっていることがわかります。
Node.js v5.0では、Spread call
やnew.target
などの新しい機能も使えるようになっています。ただし、現時点ではまだ下記のような機能は有効になっていません。
- default params
- rest params
- tail call optimization
- Proxy/Reflect
- modules
StrongScript等のJavaScriptの新機能対応
StrongScriptというのはES2015のコードを強制させ、JavaScriptの実行エンジンにとって親和性の高いコードを生成するための新しい試みです。 もともとJavaScriptにはuse strict
をつけることで厳格モードというモードになりますが、この厳格モードをより強くしたモードです。
StrongScriptはreadability
(読みやすさ)の向上とpredictability
(予測しやすさ) の向上という2つの事を狙った試みであり、特にpredictability
が上がることでJavaScriptの実行エンジンフレンドリーになり、全体的に性能が上がる事が期待されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
"use strong"; function foo() { //下記のコードは、StrongModeでは全てエラーになります。 // varを利用する代わりにlet、もしくはconstを使いましょう。 var obj1 = 'aaa'; // Restrict var (use let or const instead) // argumentsの代わりに、restパラメータを使いましょう let args = arguments; // Restrict arguments (use Rest Params …args) // ==の代わりに、strict equal ===を使いましょう if (obj1 == 'aaa') {} // Restrict == (use strict equal ===) // if とか for のあとにうっかり ; を置くけど、空の式を置くのは間違いの元なのでやめましょう。 if (obj1 === 'aaa'); // Restrict empty if and for // for-inの代わりに、for-ofを使いましょう。 for (let n in [1,2,3]) // Restrict for-in (use for-of instead) // delete構文を使うなら、Map/Setを代わりに使いましょう。 delete obj1.key // Restrict delete operator (use Map/Set instead) // undefined っていう名前の変数に何かを入れるのはやめましょう。 let undefined = 'aaa'; // Restrict undefined binding // 未定義のプロパティに、勝手にアサインするのはやめましょう。 let obj3 = { foo : 'aaa'}; obj3.bar = '123'; // Restrict undefined property access } |
本コードを実行するためには、"use strong"
ディレクティブを書くだけじゃなく、実行時のオプションとして--strong_mode
オプションが必要です。
1 |
$ node --strong_mode weak.js |
StrongScriptの詳細については、以前まとめた資料があるのでご一読ください。StrongScriptについて
Intlオブジェクトのサポートによる国際化対応
ECMAScript国際対応用のAPIが利用できるようになっています。ECMA-402と呼ばれる仕様で通称ESIntlと呼ばれています。これを利用することで例えば時刻表期の国際化対応や通貨表記の国際化対応ができるようになります。
ただしデフォルトでは利用できないので、ビルドするときにオプションを指定する必要があります。下記のようにオプションを指定して実行すると有効になります。
1 2 3 |
$ ./configure --with-intl=full-icu --download=all $ make $ make install |
これを使うと新たにIntl
というbuilt-inオブジェクトがglobal空間に生成されて使えるようになります。文字列比較処理から見ていきましょう。文字列比較をする際に日本語にはカタカナやひらがなといった表記の違いとまた句読点といった区切りが存在します。
読みやすくするためには表記の違いを使い分けたり、句読点をしかるべき所に入れる必要がありますが、プログラミングで文字列をソートしたり、検索するときなどはこれらの区別が不要なときも多いです。こういう場合に利用するのがIntl.Collator
というクラスです。利用方法は以下のとおり。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 文字列比較系 console.log(new Intl.Collator('ja', { sensitivity: 'base' // 文字の派生系を同値と見なします、カタカナとひらがなの区別もつけません。Ex.か==が , ぴ == ヒ, あ != い }).compare('ハハ', 'パパ')); // 0 つまり同値 console.log(new Intl.Collator('ja', { sensitivity: 'accent' // 文字で発音が同じものを同値と見なします。baseの中で派生系は不等という指定です。Ex. は == ハ , ぴ != ひ, あ != い }).compare('ハハ', 'はは')); // 0 つまり同値 console.log(new Intl.Collator('ja', { sensitivity: 'accent', ignorePunctuation: true // 句読点を無視するかどうか、デフォルトはfalse }).compare('わたしは、ふるかわです', 'わたしはふるかわです')); // 0 つまり同値 |
また、この他にも時刻表記(Intl.DateTimeFormat
)や通貨表記(Intl.Numberformat
)といった数字の表記を、国際化対応することが可能です。
1 2 3 4 5 6 7 8 9 10 |
// 日付フォーマット系 var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0)); console.log(new Intl.DateTimeFormat('en').format(date)); // 12/20/2012 console.log(new Intl.DateTimeFormat('ja').format(date)); // 2012/12/20 console.log(new Intl.DateTimeFormat('ja-JP-u-ca-japanese').format(date)); // 平成24/12/20 // 数値フォーマット系 console.log(new Intl.NumberFormat('en').format(10000000)); // 10,000,000 console.log(new Intl.NumberFormat('ja', {style: 'currency', currency: 'JPY'}).format(10000000)); //¥10,000,000 console.log(new Intl.NumberFormat('en', {style: 'percent', }).format(0.230232)); // 23.023% |
API 部分の変更
実は今回のNode.js v4では、JavaScriptの文法部分での進化と内部でのリファクタリングは進んでいるものの、APIとして新しい機能はそこまで増えていません。追加された機能はいくつかありますが、主だったものとしては下記の通りです。
- Buffer#indexOfの追加
- Simple Stream Constructionの追加
- os.homeDir()が追加
一つ一つ説明していきます。
Buffer API の変更
Node.jsには、Buffer
と呼ばれるbinaryデータを扱うためのAPIが存在します。普通にNode.jsのライブラリを利用するときにはあまり意識していないかもしれませんが、fs
からreadFile
した時の戻り値やhttp
のPOSTメッセージを受け取った時は基本的にBuffer
型の値を受け取っています。JavaScriptにはArrayBufferという似たオブジェクトが存在しますが、v4.0.0からはこのArrayBuffer
をBuffer
が継承する形になりました。これによってArrayBuffer
で利用可能なAPIはBuffer
でも利用可能になります。
またそれとは別にBuffer
にindexOf
メソッドが増えました。これによりわざわざ文字列に変換しなくてもBuffer
としてそのまま文字列を含んでいるかどうかを検索することができるようになります。
1 2 3 4 5 6 |
var fs = require('fs'); // ./foo.txt => yosuke furukawa fs.readFile(__dirname + '/foo.txt', function(err, buf){ console.log(buf.indexOf('furukawa')); // 7 }); |
これまではindexOf
などの文字列検索をする場合、一旦toString
を使って文字列を変換する必要がありました。これはNode.js内部では、Buffer
型からstring
型への変換をしている上にさらにHeapメモリを消費してしまうため、メモリ効率的にも速度効率的にも悪いというデメリットが有りました。今回のv4.0からはindexOf
メソッドがBuffer
で利用可能になっているため、このデメリットを解消しています。また内部的にもこのメソッドを利用することで速度の向上が見られます。
また、ArrayBuffer
で再実装された影響で以下のようなArrayBuffer
をそのままコンストラクタに入れられるようになりました。
1 2 3 4 5 6 7 8 9 10 |
const Buffer = require('buffer').Buffer; const ab = new ArrayBuffer(16); var buf = new Buffer(ab); // Buffer constructor accepts ArrayBuffer. console.log(buf instanceof Uint8Array); // true console.log(buf instanceof Buffer); // true buf.writeUInt32BE(0x61626364, 0); console.log(buf.toString()); //abcd |
Simple Stream Construction の追加
Streamの作成が簡単になりました。今までStreamを作るためには、目的のStreamを継承して、TransformStream
であれば _transform
のようなメソッドを拡張して実現する必要がありました。 これをより簡単にしたものがthrough2に代表されるヘルパライブラリでしたが、簡単にいえばこのthrough2がなくても Node.js v4.0ではStreamを作るのが簡単にできるようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// これまで var Transform = require('stream').Transform; var util = require('util'); util.inherits(MyTransform, Transform); function MyTransform(opts){ Transform.call(this, opts); } MyTransform.prototype._transform = function(chunk, encoding, callback){ // ここで変換して ... // pushする this.push(chunk); }; MyTransform.prototype._flush = function(done){ // 最後に何かしたければここで flush する }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// これから var stream = require('stream'); var transform = new stream.Transform({ transform: function(chunk, encoding, next) { // ここで変換して ... // pushする this.push(chunk); }, flush: function(done) { // 最後に何かしたければここでflushする } }); |
詳しくはこちらのAPI資料を参考にしてください。
os.homedir機能の追加
HOMEディレクトリを取得するための関数os.homedir
が追加されました。
1 2 |
const os = require('os'); console.log(os.homedir()); // /User/yosuke |
今まではHOME
の環境変数から取得したり色々な方法で解決していましたが、これからはos.homedir
関数で取得することができます。
性能向上
v8のバージョンアップ、http_parserモジュールのバージョンアップにより、これまでよりも性能が8%ほど向上しています。まだ、前述したBuffer
の改善により、メモリ効率的にも向上が見られます。
この結果を見ていただくと分かる通り、Requests/secondベースでhttpの速度が8%程改善されています。
またこちらの結果では、性能改善はもとより、メモリ効率的にも効率化されていることがわかります。
http
だけではなく、fs
にwritev
書き込みをするモードが追加されたり、require
関数の無駄な処理を省いて高速化するといったことが行われています。
Deprecated になったAPI
残念ながら、deprecated
になったAPIも存在します。下記のAPIは今後は使わないようにしてください。
- domains(エラーはキャッチできるものの、キャッチしたエラーでできることが少ない)
- fs.exist/existSync(存在確認後に別プロセスから消されることもあるのでRace Conditionに弱い)
- util.isObject/isNumberなどのisXXX系(一部既存のライブラリの関数と異なる振る舞いがあり、使いにくいため)
Node.js v4のNode.js v0.12は互換性はあるのか
JavaScriptのレイヤは互換性があります。ほとんどのモジュールはそのまま動くでしょう。ただし、CやC++のnative module をバインディングして作っているライブラリはV8がバージョンアップされたことにより、内部のABIの互換が崩れているため動かなくなります。それらのモジュールが、まだv4.0
に対応するまではアップグレードをしても動きません。
C/C++のnativeモジュールの問題で動いていないモジュールを確認するためにはこのissue
を確認していただくとよいと思います。
https://github.com/nodejs/node/issues/2798
Node.js v4とNode.js v5の違い
Node.js v4はLTSというリリースしてから2年半サポートするサポートポリシーがついていますが、Node.js v5にはついていません。つまり、 Node.js v5は次のバージョンが出たらサポートされなくなります。
今のところ、 LTSの対象になるのは偶数のバージョンとされています。ただし、偶数のバージョンが必ずLTS
というわけではなく、今のところ偶然そういうバージョンになっているというのが正しい状態なので、きちんとLTSかどうかを把握するためには、バージョン番号の他にLTS識別名(Argon
やBoron
等の元素名)が付いていることを確認したほうがよいです。
確認するには、公式サイトのトップページに書いてある情報から識別するのが簡単です。
v0.10からv0.12までのLTSプランについては下記の図を参考にしてください。
Node.js v5には、npmのバージョン3
やALPN
サポートがついています。ただし、v5.0は前述したとおりLTSバージョンではないので次のバージョンからはサポートされません。最新の機能を使ってみたい方向けのバージョンになります。実際に最新の機能を使ってみたい場合は、Node.js v5を利用するのがいいかと思います。
まとめ
Node.js v4.0がリリースされました。実際にすでにプロダクションで使っているところもある安定したバージョンになります。また、Node.js v4.0には新しいJavaScriptであるES2015の機能が使えるようになっています。APIにもいくつか変更があるのと、性能が改善されています。
また、LTSということで、今から2年半、2018年4月まではメンテナンスされる予定です。v5もすでにリリースされていますが、これは今のところ最新の機能を使ってみたいedgeなエンジニア向けのバージョンです。
バージョンの違いを意識した上で利用してください。
また、Node.jsのv0.10やv0.12を使っている人たちはまだ多いと思いますが、来年の末には両方のバージョンともにサポートが切れるので、今のうちから新しいバージョンにバージョンアップを検討しておくことをおすすめします。