LoginSignup
4
1

More than 3 years have passed since last update.

const hoge = { ...hoge }はシャローコピーだと思ってたけどシャローコピーじゃなかった

Last updated at Posted at 2019-10-11

多分コメント欄が本番です

バカみたいな話ですが、今まで、

const hoge = { ...hoge }

みたいなコードを書いてシャローコピーしたつもりになってたんですが、全然そんなことなかったって話です。黒歴史だ・・・

例えば、以下のようなクラスAを作ったとして、

class A {
    constructor() {
        this.aaa = 'aaa';
        this.bbb = {
            ccc: 'ccc'
        };
    }
    afunc() {
        console.log('クラスAの関数だよ');
    }
    get aGetter() {
        return this.aaa;
    }
}

そのインスタンスを以下のように作り、スプレッド演算子でシャローコピー(のつもり)を取ると、

const a = new A()
const _a = { ...a }

_a.aaa_b.bbb.ccc にはアクセスできるんですが、_a.afunc()_a.aGetter にはアクセスできません。

また、instanceof の結果も変わります。

// Aクラスのインスタンスかどうか
console.log(a instanceof A) // => true
console.log(_a instanceof A) // => false

空オブジェクトにプロパティを詰め直しているイメージだと思うので、 instanceof が変わるのはまあそうだよねって感じですが、メソッドやGetterも取れなくなるのはちょっと予想外でした。

と思ったら以下のコードは動くんですよね

const c = {
  d() {
    console.log('d')
  },
}

const _c = { ...c }

_c.d() // => メソッドだけどアクセスできる!!

オブジェクトリテラルだといけるのかよどういうことなんだ・・・

ReactのpropやStore周りを書いていると、スプレッド演算子を使ったこんな感じのコードをよく書いているので、気をつけたいです。

シャローコピーを取る方法

以下のように書くとちゃんとシャローコピーを取れるっぽいです。 instanceof の結果もtrueになります

const _a = Object.assign(Object.create(Object.getPrototypeOf(a)),a)
2019/10/12 追記

コメントで教えていただきましたが、__proto__ にアクセスするのが良いかはおいておいて、以下でもいけるっぽいです。

const _a = { ...a, __proto__: a.__proto__ }

以上。


以下は検証コードです。暇な人はコピペして実行してみてください

"use strict";
class A {
    constructor() {
        this.aaa = 'aaa';
        this.bbb = {
            ccc: 'ccc'
        };
    }
    afunc() {
        console.log('クラスAの関数だよ');
    }
    get aGetter() {
        return this.aaa;
    }
}

class B extends A {
    constructor() {
        super(...arguments);
        this.ddd = 'ddd';
        this.eee = {
            fff: 'fff'
        };
    }
    bfunc() {
        console.log('クラスBの関数だよ');
    }
    get bGetter() {
        return this.ddd;
    }
}

const b = new B();
const _b = { ...b };
const __b = { ...Object.create(Object.getPrototypeOf(b)), ...b };
const ___b = Object.assign(Object.create(Object.getPrototypeOf(b)), b);

console.log(b);
console.log(_b);
console.log(__b);
console.log(___b);

// Bクラスのインスタンスかどうか
console.log(b instanceof B); // => true
console.log(_b instanceof B); // => false
console.log(__b instanceof B); // => false
console.log(___b instanceof B); // => true

// Aクラスのインスタンスかどうか
console.log(b instanceof A); // => true
console.log(_b instanceof A); // => false
console.log(__b instanceof A); // => false
console.log(___b instanceof A); // => true

// Bクラスメソッドへのアクセス
console.log(b.bfunc); // => 返ってくる
console.log(_b.bfunc); // => undefined
console.log(__b.bfunc); // => undefined
console.log(___b.bfunc); // => 返ってくる

// Aクラスメソッドへのアクセス
console.log(b.afunc); // => 返ってくる
console.log(_b.afunc); // => undefined
console.log(__b.afunc); // => undefined
console.log(___b.afunc); // => 返ってくる

// Bクラスのgetプロパティへのアクセス
console.log(b.bGetter); // => ddd
console.log(_b.bGetter); // => undefined
console.log(__b.bGetter); // => undefined
console.log(___b.bGetter); // => ddd

// Aクラスのgetプロパティへのアクセス
console.log(b.aGetter); // => aaa
console.log(_b.aGetter); // => undefined
console.log(__b.aGetter); // => undefined
console.log(___b.aGetter); // => aaa

4
1
6

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
4
1