Illust & Music 月の高いところ

今日のプリン言

謎のプリン語る。
一人書く人増えました。

【JavaScript】真・オブジェクトのディープコピー

2020年02月03日

みやびプリン 140 87

500 320

【JavaScript】真・オブジェクトのディープコピー - サムネイル

あけおめことよろ。

ご無沙汰でした。
死んでません。生きてました。
ただ、新しいネタがあっても、書く気力と時間がなかっただけです。

さて、さっそく表記の件行ってみよう。

参照型言語である、JavaScriptにおいては、このディープコピーっていうのはたいそう難しい。
一番簡単なのは、JSON文字列に変換後に、再度オブジェクト化する方法だが、
他にも、再起的にプロパティを潜っていき、新しくオブジェクトを作成し、再度新しい値の参照として同名プロパティを定義していく、という下記の方法がある。

function deepCopy(obj) {
    // 配列なら配列、と定義した方が手っ取り早い。
    const r = Array.isArray(obj) ? [] : {};
    // Object.keys(obj).forEachとかでもよし。ただし、entriesは、IEでは使えないので注意
    for (let key in obj) {
        // プロパティがオブジェクトなら、再起的に自身を実行して潜っていく、値なら、そのまま代入
        r[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
    }
    return r;
}

だが、この方法、最大の欠点があり、それが多くのデベロッパーを苦しませた。
上記の方法だと、クラスメソッド(prototypeで定義したメソッド)は引き継がれないのである。

例えば下記のようなものは、オブジェクトとしてディープコピーはされるものの、
参照不可のクラスメソッドは消滅してしまう。

// 上記deepCopyメソッドを、定義済みとする
class TestClass {
    constructor(hoge, mog) {
        this.hoge = hoge;
        this.mog = mog;
    }
    testFunc() {
        return this.hoge + this.mog;
    }
}

const testInstanse = new TestClass('ほげ', 'もぐ');
const testCopy = deepCopy(testInstanse);

console.log(testCopy.testFunc()); // Uncaught TypeError: testCopy.testFunc is not a function

これは、prototype構文を使ったとしても同様で、
(JSのクラスはprototypeの糖衣だからね)prototypeメソッドは、引き継げない。
おそらくオブジェクトのプロトタイプ汚染をなるたけ防ぐためと思われる。
だが、けっこうな場合は、インスタンスのコピーという形で、メソッドは実行したい、とはなるだろう。
実は、__proto__属性を使えば案外さっくり実装できるのだが、いかんせん、__proto__属性は現在非推奨となっている。(Object.prototype.proto - JavaScript | MDN

メソッドのコピーは泣き寝入りするしかないか・・・?

そんなことはない。

プロが選ぶWordPressテーマテンプレート【LIQUID PRESS】 - メイン

Object.createをうまく使うことによって、汎用的にオブジェクトのディープコピーをすることができるのだ。
(先ほどの関数を少し変えている)

function deepCopy(obj) {
    const r = (() => {
        // 一応、余計な処理をいれないために、クラス名がObjectか、Arrayだったらそのまま返す
        if (obj.constructor.name === 'Array' || obj.constructor.name === 'Object') return Array.isArray(obj) ? [] : {};
        // クラス名が上記以外なら、Object.createをうまく使って、新しくインスタンスを生成して返す
        return Object.create(obj.constructor.prototype);
    })();

    // IEはアロー関数が使えないので、下記のようにするといいだろう。
    /*
    const r = (function(){
        // 中は同じ処理
    })();
    */

    // Object.keys(obj).forEachとかでもよし。ただし、entriesは、IEでは使えないので注意
    for (let key in obj) {
        // プロパティがオブジェクトなら、再起的に自身を実行して潜っていく、値なら、そのまま代入
        r[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
    }
    return r;
}

Object.createメソッドは、第一引数のオブジェクトをプロトタイプとした新しいオブジェクトを返すメソッドだ。
よって、コピー元のオブジェクトのコンストラクターをプロトタイプとすることによって、同じクラスからインスタンスを生成できる、って寸法だ。
あとは、従来の再起実行を持って、各プロパティに潜って、値を別参照で定義していけばいい。

さぁ、これで、JSでの大規模開発も本格的にできるぜよ。
ではまた。

必ずテレビアニメに出演できる唯一の声優養成所【インターナショナル・メディア学院】 - メイン

トラックバック(0)

トラックバックURL:

コメントする