Es 6のオブジェクトの拡張
11021 ワード
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint()
// {x:1, y:10}
CommonJSモジュールは変数のセットを出力し、簡潔な書き方を使用するのに適しています.
let ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
//
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
ES 6は,文字通りの量でオブジェクトを定義する場合に,メソッド2(式)をオブジェクトの属性名とし,すなわち式を括弧内に置くことを可能にする.
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
次は別の例です.
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
列挙可能性
オブジェクトの各プロパティには、プロパティの動作を制御する記述オブジェクト(Descriptor)があります.
Object.getOwnPropertyDescriptor
メソッドでは、プロパティの記述オブジェクトを取得できます.let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
オブジェクトを記述する
enumerable
のプロパティは、「列挙可能」と呼ばれ、false
の場合、現在のプロパティが無視されるアクションがあります.現在、
enumerable
がfalse
のプロパティを無視する4つの操作があります.for...in
ループ:オブジェクト自体と継承された列挙可能な属性のみを巡回します.Object.keys()
:オブジェクト自体のすべての列挙可能な属性のキー名を返します.JSON.stringify()
:オブジェクト自体の列挙可能な属性のみをシリアル化します.Object.assign()
:enumerable
がfalse
の属性を無視し、オブジェクト自体の列挙可能な属性のみをコピーします.この4つの操作のうち、最初の3つはES 5であり、最後の
Object.assign()
はES 6が追加された.このうち、for...in
のみが継承された属性を返し、他の3つの方法は継承された属性を無視し、オブジェクト自体の属性のみを処理します.実際、「列挙可能」(enumerable
)という概念を導入する最初の目的は、いくつかの属性がfor...in
操作を回避できるようにすることであり、そうでなければすべての内部属性と方法が遍歴される.例えば、オブジェクトプロトタイプのtoString
方法、および配列のlength
属性は、「列挙可能」によってfor...in
遍歴を回避することである.Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
上記のコードでは、
toString
およびlength
の属性のenumerable
はいずれもfalse
であるため、for...in
はプロトタイプから継承された2つの属性に遍歴しない.また、ES 6では、すべてのClassのプロトタイプの方法は枚挙にいとまがないと規定されています.
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false
総じて、操作に継承されたプロパティを導入すると、問題が複雑化し、多くの場合、オブジェクト自体のプロパティだけに関心を持ちます.したがって、
for...in
サイクルではなく、Object.keys()
サイクルで代用します.属性の遍歴
ES 6には,オブジェクトの属性を巡回する5つの方法がある.
(1)for...in
for...in
は、オブジェクト自体と継承された列挙可能な属性(Symbol属性を含まない)をループします.(2)Object.keys(obj)
Object.keys
は、オブジェクト自体の(継承されていない)すべての列挙可能な属性(Symbol属性を含まない)のキー名を含む配列を返します.(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
は、オブジェクト自体のすべての属性(Symbol属性は含まれないが、列挙できない属性を含む)のキー名を含む配列を返します.(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
は、オブジェクト自体のすべてのSymbol属性のキー名を含む配列を返します.(5)Reflect.ownKeys(obj)
Reflect.ownKeys
は、オブジェクト自体のすべてのキー名を含む配列を返します.キー名がSymbolまたは文字列であるかどうかにかかわらず、列挙可能かどうかにかかわらず.以上の5つの方法は,オブジェクトのキー名を遍歴し,同じ属性遍歴の順序規則を遵守する.
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
上記のコードでは、
Reflect.ownKeys
メソッドは、パラメータオブジェクトのすべての属性を含む配列を返します.この配列の属性順序は、まず数値属性2
および10
であり、次いで文字列属性b
およびa
であり、最後にSymbol属性である.superキーワード
this
キーワードは常に関数が存在する現在のオブジェクトを指し、ES 6はまた、現在のオブジェクトのプロトタイプオブジェクトを指す別の類似キーワードsuper
を追加したことを知っています.const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
上記のコードでは、オブジェクト
obj.find()
メソッドのうち、super.foo
によってプロトタイプオブジェクトproto
のfoo
属性が参照される.なお、
super
キーワードがプロトタイプオブジェクトを表す場合は、オブジェクトのメソッドにのみ使用でき、他の場所で使用するとエラーが発生します.//
const obj = {
foo: super.foo
}
//
const obj = {
foo: () => super.foo
}
//
const obj = {
foo: function () {
return super.foo
}
}
JavaScriptエンジンでは、ここの
super
はオブジェクトのメソッドに使用されていないため、上記の3つのsuper
の使い方は間違っています.1つ目の書き方はsuper
が属性に使用され、2つ目と3つ目の書き方はsuper
が1つの関数に使用され、foo
の属性に割り当てられます.現在、JavaScriptエンジンに確認できるのは、オブジェクトメソッドの略記法のみであり、オブジェクトのメソッドを定義している.JavaScriptエンジンの内部では、
super.foo
はObject.getPrototypeOf(this).foo
(プロパティ)またはObject.getPrototypeOf(this).foo.call(this)
(メソッド)に等しい.const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
上記のコードでは、
super.foo
はプロトタイプオブジェクトproto
のfoo
メソッドを指すが、バインドされたthis
は現在のオブジェクトobj
であるため、world
が出力される.かいぞうわりあて
オブジェクトのデフォーマ値は、オブジェクトから値を取得するために使用されます.ターゲットオブジェクト自体のすべての遍歴可能な(enumerable)が読み込まれていない属性に相当し、指定したオブジェクトに割り当てられます.すべてのキーと値は、新しいオブジェクトにコピーされます.
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
上記のコードでは、変数
z
は、解構賦値が存在するオブジェクトである.等号の右側にあるすべての未読み取りキー(a
およびb
)を取得し、値とともにコピーします.解構賦値は、等号の右側がオブジェクトであることを要求するため、等号の右側が
undefined
またはnull
である場合、オブジェクトに変換できないため、エラーが発生します.let { x, y, ...z } = null; //
let { x, y, ...z } = undefined; //
解構賦値は最後のパラメータでなければなりません.そうしないと、エラーが発生します.
let { ...x, y, z } = obj; //
let { x, ...y, ...z } = obj; //
上記のコードでは、解構付与値は最後のパラメータではないので、エラーが発生します.
なお、解構賦値のコピーは浅いコピーであり、すなわち、キーの値が複合タイプの値(配列、オブジェクト、関数)である場合、解構賦値コピーはこの値のコピーではなく、この値の参照である.
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
上記のコードでは、
x
は、オブジェクトobj
のa
属性をコピーした解構賦値が存在するオブジェクトである.a
プロパティは、オブジェクトを参照します.このオブジェクトの値を変更すると、オブジェクトに対する参照の解釈に影響します.また,拡張演算子の解構賦値は,プロトタイプオブジェクトから継承された属性をコピーすることはできない.
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
上記のコードでは、オブジェクト
o3
はo2
をコピーしたが、o2
自体の属性のみがコピーされ、そのプロトタイプオブジェクトo1
の属性はコピーされなかった.次は別の例です.
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
上記のコードでは、変数
x
は単純な解構賦値であるため、オブジェクトo
が継承した属性を読み取ることができる.変数y
とz
は拡張演算子の解構賦値であり、オブジェクトo
自身の属性のみを読み取ることができるため、変数z
は賦値に成功し、変数y
は値を取れない.ES 6では、変数宣言文では、拡張演算子の後ろには変数名が必要であり、解賦値式ではないため、上記のコードには中間変数newObj
が導入されており、以下に書くとエラーが報告される.let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
割り当てを解く1つの用途は、関数のパラメータを拡張し、他の操作を導入することです.
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// x y
//
return baseFunction(restConfig);
}
上記のコードでは、元の関数
baseFunction
はa
とb
をパラメータとして受け入れ、関数wrapperFunction
はbaseFunction
に基づいて拡張され、余分なパラメータを受け入れ、元の関数の動作を保持することができる.拡張演算子
オブジェクトの拡張演算子(
...
)は、パラメータオブジェクトのすべての遍歴可能な属性を取り出し、現在のオブジェクトにコピーするために使用されます.let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
配列は特殊なオブジェクトであるため、オブジェクトの拡張演算子も配列に使用できます.
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
オブジェクトの拡張演算子は、
Object.assign()
メソッドを使用するのと同じです.let aClone = { ...a };
//
let aClone = Object.assign({}, a);
上の例はオブジェクトインスタンスのプロパティをコピーしただけで、オブジェクトを完全にクローンし、オブジェクトのプロトタイプのプロパティをコピーしたい場合は、次のように書くことができます.
//
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
//
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
//
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
上記のコードでは、書き方1の
__proto__
属性はブラウザ以外の環境では必ずしも配置されていないため、書き方2と書き方3を推奨します.拡張演算子は、2つのオブジェクトを結合するために使用できます.
let ab = { ...a, ...b };
//
let ab = Object.assign({}, a, b);
ユーザーがカスタマイズしたプロパティを拡張演算子の後ろに置くと、拡張演算子の内部の同名のプロパティが上書きされます.
let aWithOverrides = { ...a, x: 1, y: 2 };
//
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
//
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
//
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
上記のコードでは、
a
オブジェクトのx
プロパティとy
プロパティが、新しいオブジェクトにコピーされると上書きされます.これにより、既存のオブジェクト部分のプロパティを変更するのに便利です.
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
};
上記のコードでは、
newVersion
オブジェクトがname
プロパティをカスタマイズし、その他のプロパティはすべてpreviousVersion
オブジェクトからコピーされます.カスタムアトリビュートを拡張演算子の前に置くと、新しいオブジェクトを設定するデフォルトのアトリビュート値になります.
let aWithDefaults = { x: 1, y: 2, ...a };
//
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
//
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
配列の拡張演算子と同様に、オブジェクトの拡張演算子の後に式を付けることができます.
const obj = {
...(x > 1 ? {a: 1} : {}),
b: 2,
};
拡張演算子の後ろに空のオブジェクトがある場合、効果はありません.
{...{}, a: 1}
// { a: 1 }
拡張演算子のパラメータが
null
またはundefined
の場合、この2つの値は無視され、エラーは報告されません.let emptyObject = { ...null, ...undefined }; //
拡張演算子のパラメータオブジェクトに、値取り関数
get
がある場合、この関数は実行されます.// , x ,
let aWithXGetter = {
...a,
get x() {
throw new Error('not throw yet');
}
};
// , x
let runtimeError = {
...a,
...{
get x() {
throw new Error('throw now');
}
}
};