Node.js v6.0 (Current) がリリースされました。

さて、とうとう皆さん待望の Node.js v6.0 がリリースされました!次のLTS候補です。LTSになるのは2016年の10月からの予定です。v6 の LTS 期間は明示化されてないですが、ルールに照らし合わせれば、LTSになってから 2年半がサポート期間なので、おそらく 2019年4月まではサポートされます。

Node v6.0.0 (Current) | Node.js

Node.js v6.0 の主な変更点

  • ES2015 support の改善
  • module load性能の改善
  • Buffer API の new Buffer() コンストラクタの廃止 (セキュリティ上の理由から)

ES2015 support の改善

やっぱりこれが一番大きな変化ですね。

node.green を見てもらえればわかるかもしれませんが、 ES2015 のサポートがこれまでは 58% だったのが 96% まで大幅に拡大されました。

f:id:yosuke_furukawa:20160427021442p:plain

細かいところはnode.greenを見てもらうとして、いくつかのデフォルトで有効になった機能を紹介します。

デフォルトパラメータ

これは、関数に引数を渡す際に渡されない引数(undefinedな引数)にはデフォルトで特定の値にしてもらう、という機能です

function foo(a = 1, b = 2) {
  return a === 3 && b === 2;
}

foo(3) // true
// 引数の値をデフォルト引数の値として使うことも可能
function f(list, indexA = 0, indexB = list.length) {
  return [list, indexA, indexB];
}
console.log(f([1,2,3]); // [1,2,3], 0, 3
console.log(f([1,2,3],1); // [1,2,3], 1, 3
console.log(f([1,2,3],1,2); // [1,2,3], 1, 2

デフォルトパラメータが書けるようになったので、いままでやっていたような もしも引数 a がundefinedだったらデフォルトで値をセットする という処理が読みやすく、書きやすくなりました。

デストラクチャ

デストラクチャリング、和訳すると分配束縛と呼ばれる機能です。Clojureにある機能ですね。 これを利用すると配列やオブジェクトで設定した値を取り出しやすくなります。 一番良く使うのは値をswapさせる時かと思います。

具体的には以下のとおり。

var hoge = 123;
var fuga =456;

// 値をswapする
var [fuga, hoge] = [hoge, fuga];

console.log(hoge); // 456
console.log(fuga); // 123

var [a, [b], [c], d] = ['hello', [', ', 'junk'], ['world']];

console.log(a + b + c); //hello, world (aに"hello", bに",", cに"world"が入ってる )

var pt = {x: 123, y: 444};
var {x, y} = pt;
console.log(x, y); // 123 444

Rest パラメータ

可変長パラメータを持つ関数を作る時に arguments を使わずに作れるようになりました。

// ...itemsでRestパラメータ
function push(array, ...items) {
  // それをarrayにpush (ここはspread operator)
  array.push(...items);
}
var list = [1, 2, 3];
//list変数に数字をpush
push(list, 4, 5, 6);
console.log(list); //[1, 2, 3, 4, 5, 6]

正規表現に sticky option と unicode option が付く

sticky option は直前に実行した正規表現のlastindexを覚えておき、次に検索する時はその lastindex から検索するというオプションです。

var text = "First line\nsecond line";
var regex = /(\S+) line\n?/y;

var match = regex.exec(text);
console.log(match[1]);  // "First"
console.log(regex.lastIndex); // 11

var match2 = regex.exec(text);
console.log(match2[1]); // "Second"
console.log(regex.lastIndex); // 22

unicode option は正規表現のマッチ時にユニコードリテラルを使って書けるようになったり、サロゲートペアにマッチできるようになるためのフラグです。

"𠮷".match(/^.$/u)[0].length === 2

Proxy API

Proxyが提供してくれるのは、"ちゃんとした" メタプログラミングです。Proxyに関しては、Brendan Eich の構想を描いた発表資料があります。

www.slideshare.net

これが 2010 年という事で、Node.jsが流行りはじめたくらいに提案されたというのが歴史の深さを物語っていますが、Proxyは割りと色んな事ができるAPIです。5年経て、上のスライドで紹介されているものとは API が異なりますが、以下のことに使えます。

  • Getter/Setter への インジェクションを行うことで、プロパティの変更や参照をされた時に処理を変えることができる。
  • method_missing みたいな存在しないメソッドを呼び出した時に呼ばれるメソッドを定義できる。

などなど、名前の通り、代理オブジェクトとして呼び出されたときの処理を肩代わりすることができます。

Proxy API で Getter/Setter にインジェクトする。

こんな感じのことができます。

var handler = {
    get: function(target, name){
        return name in target?
            target[name] :
            37;
    }
};

var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37

存在しないプロパティを呼び出した時のデフォルトの振る舞いを定義できる。

const obj = {abc: 123};
const PropertyChecker = new Proxy(obj, {
  get(target, propKey, receiver) {
    if (!(propKey in target)) {
      throw new ReferenceError('Unknown property: '+propKey);
    }
    return Reflect.get(target, propKey, receiver);
  }
});
console.log(PropertyChecker.abc); // 123
console.log(PropertyChecker.def); // Reference Error

これをうまく応用することで例えば method_missing 的な存在しないメソッドがあった時の振る舞いを定義できます。

Reflect API

一方で Reflect API は Proxy API と対になって使われることが多い API で、 Proxy が処理にインジェクトして、代理オブジェクトとして振る舞うのに対して、 Reflect API はそれのユーティリティオブジェクトで、対象となるオブジェクトに対して処理を反映(Reflect)させる動きをします。

最初にReflectを僕が知った時は対象となるオブジェクトが取れるならそれを直接変更すればいいので、いまいち使いドコロが分かってなかったんですが、ある程度使ってみると使い所が分かってきました。

使い所 その1 演算子じゃなくて、関数にしたい時

in 演算子を考えてみましょう

'assign' in Object // Object のプロパティに `assign` があるかどうか

これは演算子を使った式です、Reflect を使うと下記のように書くことができます。

Reflect.has(Object, 'assign'); // true

演算子じゃなくて関数で表現されているのがわかります。

他にも delete 演算子を Reflect を使って書き換えることが可能です。

// delete 演算子
var a = {abc: '123', def: '456'};
delete a['abc']; // {def: '456'}
// Reflectを使った場合
var a = {abc: '123', def: '456'};
Reflect.deleteProperty(a, 'abc'); // {def: '456'}

専用の演算子を使ったほうが短く書けますが、関数を使って代用できるようにしておくと読みやすさや反映する対象がわかりやすくなります。

使い所その2 例外よりも戻り値として扱いたい時

Object.defineProperty というObjectに対してプロパティを定義する専用の関数がありますが、これは defineProperty に失敗すると例外がthrowされます。そのため下記のように書かなくてはいけません。

try {
  Object.defineProperty(obj, name, desc);
  // 成功時の処理
} catch (e) {
  // 失敗時
}

Reflectができるようになると下記のように書けます

if (Reflect.defineProperty(obj, name, desc)) {
  // 成功時の処理
} else {
  // 失敗時の処理
}

--es_staging flag で有効になるもの

--es_staging flag を付けるとES2015の機能がさらに増えます。ただし、 --es_staging flag はまだ unstable の状態の機能を試すのに使うためのフラグなのであまり本番で積極的に使うものではありません。

末尾再帰呼び出し最適化

再帰呼び出しをした時に再起かどうかを内部的に検知して、再帰呼び出しを普通のループに変換します。 詳しくは下記の teppeis さんのブログが参考になります。

teppeis.hatenablog.com

そこから持ってきた下記のスクリプトで試します。

'use strict'; // 今のところ strict mode でのみ有効
function factorial(n, acc) {
  if (n <= 1) return acc;
  return factorial(n - 1, n * acc);
}

[0,1,2,3,4,5,10,100,1000,10000,100000].forEach(function(n) {console.log(n, factorial(n, 1))});

階上計算処理ですが、これを普通に実行すると、 100000 の所で stack size オーバーフローでエラーになります。

$ node fact.js

0 1
1 1
2 2
3 6
4 24
5 120
10 3628800
100 9.332621544394418e+157
1000 Infinity
10000 Infinity
/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:2
function factorial(n, acc) {
                  ^

RangeError: Maximum call stack size exceeded
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:2:19)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)
    at factorial (/Users/yosuke/go/src/github.com/yosuke-furukawa/node/fact.js:4:10)

しかし、 --es_staging で実行すると通るようになります。

$ node --es_staging fact.js

0 1
1 1
2 2
3 6
4 24
5 120
10 3628800
100 9.332621544394418e+157
1000 Infinity
10000 Infinity
100000 Infinity

Function#name

今までは無名関数だと .name プロパティから関数名を取れませんでした、これが よくある無名関数を左辺に代入する書き方だと問題がありました。

// function.js
var abc = () => {};
const def = function(){};
console.log(abc.name); // abc
console.log(def.name); // def

// ただし、この場合は 普通に名前付き関数の名前が採用される

let ghi = funciton jkl() {};
console.log(ghi.name); //jkl
$ node function.js


jkl

これを --es_staging を付けると下記のようになります。

$ node --es_staging function.js
abc
def
jkl

逆に有効になっていないもの

一番有名所としては import/export でしょうか。 これに関しては、ただいま絶賛議論が紛糾している所です。

ES modules を Node に持ってくる、というのはかなり難しい問題で、今のところまだ有効な打開策が出ていません。そもそもまだ v8 でもパースできないのでパースできるようになったとして、どうやって解決するかという議論が始まったばかりです。

議論のまとめはこちら

github.com

module load 性能の向上

benchmark WG が継続してベンチマークを取るようにしてくれました。

Node.js Benchmarking

benchmark working group では requireや起動時間といった性能を計測するマイクロベンチと acmeairと呼ばれるチケット予約管理アプリを使ったマクロベンチの2種類を実行しています。 v4.x と比較した時に require  した時の性能が初期ロードで 3倍高速に、二回目以降のロード(cache付きロード)で5倍高速になるようになりました。

ops/sec higher is better f:id:yosuke_furukawa:20160427100426p:plain

また acmeair を使った全体のスループットに関しても若干の向上が見られます。

f:id:yosuke_furukawa:20160427101015p:plain

Buffer API の new Buffer() コンストラクタの廃止

セキュリティ上の理由から、 new Buffer() コンストラクタは廃止されました。 詳しくはこの辺りを見てもらうと良いかもしれません。

おまけ

Windows XP/Vista で node.js のサポートが終了しました。

まとめ

他にも色々と変更はありますが、全てを紹介しようとすると膨大な量なので一旦ここまでに停めておきます、すべての変更をちゃんと追いたい人は以下のChangelog を参考にしてください。

node/CHANGELOG.md at master · nodejs/node · GitHub

アップグレードするかどうか

まず v0.10 / v0.12 を使っている場合は今年の年末でサポートが切れてしまうので v4 もしくは v6 への upgradeを推奨します。

f:id:yosuke_furukawa:20160427102953p:plain

v4 を使っているユーザーは 2018年までサポートは続くのでまだアップグレードしなくても構いません、とはいえ上にあげたような新しい機能を使いたいユーザーはアップグレードを検討してください。

v5 を使っているユーザーはあと2ヶ月はサポートされますが、v5はLTS対象じゃないので、アップグレードを推奨します。v6 にアップグレードすることを検討してください。

Stable ラベルから Current ラベルへ

今までは Stable というラベルが最新のバージョンにはついていましたが、 LTS と混同されやすいのでラベル名が Current に変わりました。v6がLTSになったらその時はおそらく LTS ラベルが付き、 v7 が Current へと変わります。

v7 のリリースもまだ明示化されてないですが、今のところの計画では今から半年後の2016年の10月には次のv7.0がリリースされる予定です。

まとめ

Node.js v6.0 がリリースされました。

  • 主な変更点
  • ES2015 サポート拡充
  • 性能向上
  • new Buffer API の廃止
  • アップグレード検討有無
  • Stable から Current へ

という話をしました。