JavaScript の Object.assign について理解する
はじめに
Object.assign
はオブジェクトをコピーするための関数です。今回は ECMAScript の仕様を通じてObject.assign
の理解を深めてみましたので、備忘録として残しておきます。
使い方
最初にObject.assign
の使い方に触れます。
Object.assign
の構文はObject.assign(target, ...sources)
という形式になっています。引数はtarget
というコピー先のオブジェクトとsources
という複数のコピー元のオブジェクトの2種類があり、戻り値としてはコピー先のオブジェクトが返却されます。
例えば、以下のようなname
とlocation
を持ったオブジェクトに対して、Object.assign
を使うと、コピー先のオブジェクトであるtarget
が{ name: 'tommykw', location: 'kobe' }
に更新されていることがわかります。
const target = {
name: 'tommykw',
location: 'tokyo',
};
const source = {
name: 'tommykw',
location: 'kobe',
};
Object.assign(target, source);
console.log(target); // { name: 'tommykw', location: 'kobe' }
console.log(source); // { name: 'tommykw', location: 'kobe' }
仕様を確認する
簡単に利用方法について確認したので、ここからは仕様を通じてさらに理解を深めます。
ECMAScriptの仕様は以下の通りで、Object.assign
に関しては大きく4つのステップに分解できます。
1. Let to be ? ToObject(target).
2. If only one argument was passed, return to.
3. For each element nextSource of sources, do
1. If nextSource is neither undefined nor null, then
1. Let from be ! ToObject(nextSource).
2. Let keys be ? from.[[OwnPropertyKeys]]().
3. For each element nextKey of keys, do
1. Let desc be ? from.[[GetOwnProperty]](nextKey).
2. If desc is not undefined and desc.[[Enumerable]] is true, then
1. Let propValue be ? Get(from, nextKey).
2. Perform ? Set(to, nextKey, propValue, true).
4. Return to.
順番に見ていきます。
1. Let to be ? ToObject(target).
Object.assign(target, ..sources)
のtarget
をオブジェクトに変換します。ToObject()の仕様は以下の通りで、target
がオブジェクトの場合はそのまま返却され、プリミティブであればボックス化されて返却されます。
ECMAScript® 2023 Language Specification. Table 16: ToObject Conversions.
さらに ? ToObject(target)
に注目してみると、ToObject(target)
の前に?
がありますが、これは何を意味するのでしょうか。? は例外が発生する可能性を示しています。この場合target
がNull
やUndefined
の場合は、TypeError の例外が発生します。
2. If only one argument was passed, return to.
Object.assign(target)
のように引数が1つの場合は変数to
を返却します。
実際に引数を1つだけ指定して戻り値を確認すると、コピー先のオブジェクトがそのまま返却されていることがわかります。
const target = {
name: 'tommykw',
};
const to = Object.assign(target);
console.log(to); // { name: 'tommykw' }
3. For each element nextSource of sources, do
ステップ3は、ぞれぞれのコピー元のオブジェクトをコピーしていきます。ここでは、オブジェクト元である各要素をnextSource
として展開していきます。
3-1. If nextSource is neither undefined nor null, then
nextSource
がUndefined
、Null
ではない場合に 3-1-1 へ進みます。
3-1-1. Let from be ! ToObject(nextSource).
nextSource
をオブジェクトに変換して、from
に内容を格納します。
先ほど?
がありましたが、! は例外が発生しないことを意味します。3-1 でnextSource
がUndefined
、Null
ではないことをチェックしているため、ToObject(nextSource)
で例外が発生しないことがわかります。
3-1-2. Let keys be ? from.[[OwnPropertyKeys]]().
[[OwnPropertyKeys]]とは、オブジェクトからプロパティキーをリストとして返します。つまりfrom
から独自のプロパティキーをkeys
に格納します。
ECMAScript® 2023 Language Specification. Table 4: Essential Internal Methods
3-1-3. For each element nextKey of keys, do
プロパティキーのリストの各要素であるnextKey
として展開していきます。
3-1-3-1. Let desc be ? from.[[GetOwnProperty]](nextKey).
from
から独自のプロパティを取得してnextKey
に対応した値を取得してdesc
に格納します。[[GetOwnProperty]]とはオブジェクトの独自プロパティを取得します。
ECMAScript® 2023 Language Specification. Table 4: Essential Internal Methods
3-1-3-2. If desc is not undefined and desc.[[Enumerable]] is true, then
[[Enumerable]]とは、プロパティが列挙可能かどうかを示します。ここでは、desc
の [[Enumerable]]
がtrue
かつ、desc
がUndefined
でない場合に 3-1-3-2-1 へ進みます。
ECMAScript® 2023 Language Specification. Table 3: Attributes of an Object property
プロパティの列挙可能とは、enumerable
で表現できます。例えば、以下のようにObject.defineProperty を使って明示的にenumerable
を定義すると、enumerable: false
の場合は空のオブジェクトが表示されますが、enumerable: true
の場合は、{ name: "tommykw" }
が表示されることがわかります。
const target = {
name: ''
};
Object.defineProperty(target, 'name', {
enumerable: false,
value: 'tommykw',
});
console.log(target); // { }
Object.defineProperty(target, 'name', {
enumerable: true,
value: 'tommykw',
});
console.log(target); // { name: "tommykw" }
3-1-3-2-1. Let propValue be ? Get(from, nextKey).
Get()とは、オブジェクトの特定のプロパティの値を取得できます。つまり、from
オブジェクトのnextKey
の値を取得してpropValue
に格納します。
3-1-3-2-2. Perform ? Set(to, nextKey, propValue, true).
Set()とは、オブジェクトの特定のキーに値を設定できます。つまり、to
オブジェクトの nextKey
にpropValue
の値を設定します。ちなみにSet()
の4つ目の引数は例外発生の要否を判定しており、true
の場合は TypeError 例外が発生します。
4. Return to.
ステップ3にて各要素であるnextSource
のコピーを繰り返した上、最後にto
を返却します。
終わりに
ECMAScript の仕様を通じてObject.assign
について理解を深めました。仕様を確認してみると思った以上に内部的にはいろんな処理があるんだなと思いました。引き続き ECMAScript の仕様について触れていきたいと思います。
Discussion