LoginSignup
59
32

More than 3 years have passed since last update.

ES6 class 構文のパフォーマンスについて

Last updated at Posted at 2019-08-21

概要

この記事は「class 構文」と「クロージャによるカプセル化」のパフォーマンス比較記事です。
検証ブラウザーは Chrome のみになります。

何か1つの処理を作るのに態々 class 化しなくても、クロージャ(関数)を作成しカプセル化するなどの方法もある。しかしパフォーマンスの観点で考えると、どちらで実装していくのが良いのだろうか...という疑問があった。本記事では、その検証結果をまとめる。
検証前に「class 構文」と「クロージャによるカプセル化」に対する考えを述べる。

class 構文に対する考え(検証前)

class 構文は実質 prototype の糖衣構文なので、同じようなオブジェクトを幾つも生成する場合は class の方が良さそうではある。しかし、class から instance(オブジェクト)を生成する new はそこそこのコストを孕んでいそうなので、オブジェクトの生成数が1〜2つ程度なら態々 class 構文で書かなくても良さそう。
また、プライベートなプロパティーを作成できない点が気に掛かる。

class構文の例
class Sample {
    constructor(val) {
        this.value = val; // value には外部からもアクセスできる
    }
    getValue() {
        return this.value.toUpperCase();
    }
}
const test = new Sample('hello world');
test.getValue(); // HELLO WORLD
test.value // hello world(アクセスできちゃう)

クロージャによるカプセル化に対する考え(検証前)

class で実現できないプロパティーのプライベート化(外部からアクセス不可)を実現することができる。class と異なり、実行の都度毎回新しいオブジェクト(prototypeではない)を返すので、オブジェクトの生成数が多ければ多いほどメモリの消費が気に掛かる。

クロージャによるカプセル化の例
const sample = (val) => {
    const value = val; // value には外部から直接アクセスできない
    return {
        getValue() {
            return value.toUpperCase();
        }
    };
};
const test = sample('hello world');
test.getValue(); // HELLO WORLD

パフォーマンス検証

「class 構文」と「クロージャによるカプセル化」の2通りについて同一の処理を用意し、「メモリの使用量」と「オブジェクト生成速度」を比較する。なお、下記コードが比較対象のコードである。

比較コード(class構文)
class Shape {
    constructor(option) {
        this.shape = document.createElement('div');
        this._text = document.createTextNode(option.text);
        this._width = option.width;
        this._height = option.height;
        this._backgroundColor = option.backgroundColor;
        this.parent = option.parent;
    }
    add() {
        this.width().height().backgroundColor();
        this.shape.appendChild(this._text);
        this.parent.appendChild(this.shape);
    }
    text(t) {
        this._text.nodeValue = t;
        return this;
    }
    width(w = this._width) {
        if (w !== this._width) this._width = w;
        this.shape.style.width = `${this._width}px`;
        return this;
    }
    ...コードが長いため
}
比較コード(クロージャによるカプセル化)
const shape = (opiton) => {
    const el = document.createElement('div');
    const _text = document.createTextNode(option.text);
    const _width = option.width;
    const _height = option.height;
    const _backgroundColor = option.backgroundColor;
    const parent = option.parent;
    return {
        add() {
            this.width().height().backgroundColor();
            el.appendChild(_text);
            parent.appendChild(el);
        },
        text(t) {
            _text.nodeValue = t;
            return this;
        },
        width(w = _width) {
            if (w !== _width) _width = w;
            el.style.width = `${_width}px`;
            return this;
        },
        ...コードが長いため
    }
};

メモリの使用量とオブジェクト生成速度の計測

class Shape と関数 shape で生成したオブジェクトを savingVariable に for 文で任意数追加し終わる速度と、その時のメモリ使用量を計測する。
メモリの使用量に関しては Chrome の「タスクマネージャ」を利用して「JavaScript メモリ」の値を計測する(タスクマネージャによるメモリ使用量のリアルタイム監視)。

  • 計測は片方ずつ行う。片方計測時はもう片方はコメントアウトしておく。
  • 10 回ループと 100000 回ループで数値を取る
検証コード
// 作成したオブジェクトを保存する(メモリ使用量の計測用)
window.savingVariable = [];

console.time("no new");
for (let i = 0; i < 100000; i++) {
    savingVariable.push(shape(option));
}
console.timeEnd("no new");

console.time("new");
for (let i = 0; i < 100000; i++) {
    savingVariable.push(new Shape(option));
}
console.timeEnd("new");

オブジェクト生成速度のみの計測

「メモリの使用量とオブジェクト生成速度の計測」では、メモリの圧迫による速度低下が考えられるので、こちらでは毎回作成したオブジェクトを永続的に参照可能な変数には保存しないようする(GC(メモリ解放)させるようにする)。
※ なお、メモリの圧迫による速度低下は 10回ループ程度場合では考えられないため、こちらでは 100000 回ループのみ計測。

  • 計測は片方ずつ行う。片方計測時はもう片方はコメントアウトしておく。
  • 100000 回ループで数値を取る。
検証コード
console.time("no new");
for (let i = 0; i < 100000; i++) {
    const box = shape(option);
}
console.timeEnd("no new");

console.time("new");
for (let i = 0; i < 100000; i++) {
    const box = new Shape(option);
}
console.timeEnd("new");

検証結果

検証の結果は下記のようになった。

メモリの使用量とオブジェクト生成速度の計測(結果)

コード ループ回数 メモリ使用量 速度(3回計測)
関数 shape 10 4.594MB 0.198ms, 0.171ms, 0.175ms
class Shape 10 4.590MB 0.148ms, 0.172ms, 0.161ms
関数 shape 100000 62.518MB 288.227ms, 232.893ms, 244.277ms
class Shape 100000 20.369MB 107.515ms, 93.580ms, 131.524ms

class Shape は関数 shape に比べて圧倒的にメモリ使用量が少ない。大体予測は付いていたが、実際数値で見ると prototype の使用は圧倒的に効率がいいなと再認識させられた。
また、速度についてもメモリの圧迫に引っ張られてなのか class Shape の方が圧倒的に早い。
10 回ループについては、大した差は出なかった。

オブジェクト生成速度のみの計測(結果)

コード ループ回数 速度(3回計測)
関数 shape 100000 86.072ms, 83.144ms, 92.013ms
class Shape 100000 79.072ms, 77.062ms, 85.856ms

やはり、メモリ使用量の計測と同時に行った結果については速度に差が出たが、純粋にオブジェクト生成速度の計測のみを行うコチラでは大きく速度の差は開かなかった。
その結果、new による instance の生成はコストというほどパフォーマンスに影響を与えるものではない事が分かった。

追加・補足検証

一番最初の例(概要の章)で出した class Sample と関数 sample についても計測してみた。
理由としては、class Shape や関数 shape のように多くの機能を持ったオブジェクトの生成比較とは別に、小さなオブジェクト生成の比較結果を得るため。

追加・補足検証結果

コード ループ回数 メモリ使用量 速度(3回計測)
関数 sample 10 4.713MB 0.198ms, 0.171ms, 0.175ms
class Sample 10 4.713MB 0.148ms, 0.172ms, 0.161ms
関数 sample 100000 22.388MB 7.521ms, 7.259ms, 6.287ms
class Sample 100000 11.488MB 10.986ms, 8.089ms, 14.462ms

class Sample / 関数 sample 自体、メンバ変数 / 関数 の数が少ないので、100000 回 for 文で処理してもあまり差は無かった。メモリ使用量を見るとやはり class Sample の方が少なくはあるが、メモリの圧迫も然程無い為かそれによる速度差は特に見られなかった(誤差の範囲)。その為、class Shape / 関数 shape で行なった速度のみの計測は行わなかった。

結果としてやはりオブジェクトが大きいものであるほど、それが多数生成された際に class 構文による恩恵が大きく見られることが分かった。

パフォーマンス検証を終えて

第一に class 構文優秀じゃん!と思いました。個人的に new ってコストのかかるイメージを固定概念的で持っていたので、それが払拭されたのが1つと、検証結果の数値を見るに prototype (class) を使用することの効率の良さ(無駄にメモリを喰わない)を改めて再認識できた点が大きいです。

プロパティーのプライベート化については現状 class 構文で使用できない(Chrome74以降のみ可)ですが、将来的に実装もされそうですし、この検証結果の元もっと class 構文を積極的に使っていこう!という考えを自分の中で促進することができました。

59
32
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
32