Panda Noir

JavaScript の限界を究めるブログです。

即席Immutableライブラリを作ってみた

たった17行という超即席ライブラリです。なぜ作ったかというと、この記事のいうImmutableのメリットをJavaScriptでも授かりたいと思ったためです。たった17行でもできてしまってビックリしました。

nekogata.hatenablog.com

コード

全コードが収まってしまう省スペースさ

class Immutable {
    constructor(obj) {
        this._root = null;
        this._properties = obj;
    }
    set(key, value) {
        const res = new this.constructor();// Immutableを継承したクラスに対応させるため
        res._root = this;
        res._properties = {[key]: value};
        return res;
    }
    get(key) {
        if (this._properties.hasOwnProperty(key)) return this._properties[key];
        if (!this._root) return undefined;
        return this._root.get(key);
    }
}

原理

差分を保持するという仕組みをとっています。_propertiesになければ_rootを順にたどっていくという仕組みです。お手軽ですね。

ポイントは、Immutable自身はgetとsetを提供するにとどまっている点です。このことにより、既存のコードに埋め込むことが簡単になっています。

使い方

例えばImmutableクラスを継承したPlayerクラスを作ります。

class Player extends Immutable {
    constructor() {
        super({hp: 10, maxHp: 10, atk: 10, def: 10});
    }
    levelUp() {
        return this.set('hp', this.get('hp') + 3)
            .set('maxHp', this.get('maxHp') + 3)
            .set('atk', this.get('atk') + 3)
            .set('def', this.get('def') + 3);
    }
    damage(damage) {
        return this.set('hp', this.get('hp') - 3);
    }
}

このクラスからoldPlayerAを作り、HPを3減らしたnewPlayerAを作ってみます。

const oldPlayerA = new Player();
const newPlayerA = oldPlayerA.damage(3);
oldPlayerA.get('hp'); // 10
newPlayerA.get('hp'); // 7

この二つはHPが3異なる以外はすべて同じ値を参照します。差分を管理する分のメモリだけでHPが3減ったプレイヤーを作ることに成功したわけです。すばらしく省メモリです。

Immutable.js

この_rootという仕組み、Immutable.jsから着想を得ています。が、コードを追ってみると、どうやらImmutable.jsでは差分管理ではなく、プロパティすべてをコピーするという方式のほうです。_rootがImmutable.jsでどういう役割を担っているのか、今後の調査が待たれます。

改善したコード

上のコードをより使いやすくしたコードをGitHub上で公開しました。

github.com

2017/6/13 追記:

GCが効きませんね。僕も「あれ?obj.set(‘key’, value)したあとobj削除したらどうなるんだ?」と思い気がつきました。どうしたらいいんですかね。

一応、改善したコードでは差分が4つ以上たまったら差分をひとつに統合するという処理を組み込みました。これで参照を切れるので、だいぶ解消されました。まあとりあえずこれで。