...) [ES2018]thisthis.call().bind()thisの落とし穴:メソッドの抽出thisの落とし穴:thisの意図しないシャドウイングthisの値(上級)in演算子:指定されたキーを持つプロパティが存在するか?Object.values()によるプロパティ値の一覧表示Object.entries()によるプロパティエントリのリスト表示 [ES2017]Object.fromEntries()によるオブジェクトの組み立て [ES2019].toString().valueOf()Object.assign() [ES6]本書では、JavaScriptのオブジェクト指向プログラミング(OOP)のスタイルを4つのステップで紹介します。本章ではステップ1を、次の章ではステップ2〜4を扱います。(図8参照)
JavaScriptにおいて
オブジェクトはJavaScriptにおいて2つの役割を果たします
レコード:レコードとしてのオブジェクトは、固定数のプロパティを持ち、そのキーは開発時に既知です。それらの値は異なる型を持つことができます。
ディクショナリ:ディクショナリとしてのオブジェクトは、可変数のプロパティを持ち、そのキーは開発時に既知ではありません。それらの値はすべて同じ型です。
これらの役割は、本章におけるオブジェクトの説明に影響を与えます
まず、オブジェクトのレコードとしての役割を探求しましょう。
オブジェクトリテラルは、レコードとしてのオブジェクトを作成する1つの方法です。これはJavaScriptの際立った特徴です。クラスは不要で、直接オブジェクトを作成できます。以下は例です。
const jane = {
first: 'Jane',
last: 'Doe', // optional trailing comma
};この例では、波括弧{}で始まるオブジェクトリテラルを使ってオブジェクトを作成しました。その中には、2つのプロパティ(キーと値のペア)を定義しました。
firstで値が'Jane'です。lastで値が'Doe'です。ES5以降、オブジェクトリテラルでは末尾のカンマが許可されています。
後でプロパティキーを指定する他の方法を見ることになりますが、この方法では、JavaScriptの変数名の規則に従う必要があります。例えば、first_nameはプロパティキーとして使用できますが、first-nameは使用できません。ただし、予約語は使用できます。
const obj = {
if: true,
const: true,
};オブジェクトに対する様々な操作の効果を確認するために、本章のこの部分では時々Object.keys()を使用します。これはプロパティキーを一覧表示します。
> Object.keys({a:1, b:2})
[ 'a', 'b' ]プロパティの値が変数名で定義されており、その名前がキーと同じである場合は、キーを省略できます。
function createPoint(x, y) {
return {x, y};
}
assert.deepEqual(
createPoint(9, 2),
{ x: 9, y: 2 }
);プロパティを取得(読み込み)する方法は次のとおりです(A行)。
const jane = {
first: 'Jane',
last: 'Doe',
};
// Get property .first
assert.equal(jane.first, 'Jane'); // (A)未知のプロパティを取得するとundefinedが生成されます。
assert.equal(jane.unknownProperty, undefined);プロパティを設定(書き込み)する方法は次のとおりです。
const obj = {
prop: 1,
};
assert.equal(obj.prop, 1);
obj.prop = 2; // (A)
assert.equal(obj.prop, 2);設定によって既存のプロパティを変更しました。未知のプロパティを設定すると、新しいエントリが作成されます。
const obj = {}; // empty object
assert.deepEqual(
Object.keys(obj), []);
obj.unknownProperty = 'abc';
assert.deepEqual(
Object.keys(obj), ['unknownProperty']);次のコードは、オブジェクトリテラルを使用して.says()メソッドを作成する方法を示しています。
const jane = {
first: 'Jane', // data property
says(text) { // method
return `${this.first} says “${text}”`; // (A)
}, // comma as separator (optional at end)
};
assert.equal(jane.says('hello'), 'Jane says “hello”');メソッド呼び出しjane.says('hello')の間、janeはメソッド呼び出しのレシーバと呼ばれ、特別な変数thisに代入されます(thisの詳細については§28.4 「メソッドと特別な変数this」を参照)。これにより、メソッド.says()はA行で兄弟プロパティ.firstにアクセスできます。
JavaScriptには2種類のアクセサがあります。
ゲッターは、メソッド定義の前に修飾子getを付けることで作成されます。
const jane = {
first: 'Jane',
last: 'Doe',
get full() {
return `${this.first} ${this.last}`;
},
};
assert.equal(jane.full, 'Jane Doe');
jane.first = 'John';
assert.equal(jane.full, 'John Doe');セッターは、メソッド定義の前に修飾子setを付けることで作成されます。
const jane = {
first: 'Jane',
last: 'Doe',
set full(fullName) {
const parts = fullName.split(' ');
this.first = parts[0];
this.last = parts[1];
},
};
jane.full = 'Richard Roe';
assert.equal(jane.first, 'Richard');
assert.equal(jane.last, 'Roe'); 練習問題:オブジェクトリテラルによるオブジェクトの作成
exercises/single-objects/color_point_object_test.mjs
...) [ES2018]関数呼び出し内では、スプレッド構文 (...) はイテラブルオブジェクトの反復可能な値を、引数に変換します。
オブジェクトリテラル内では、スプレッドプロパティは別のオブジェクトのプロパティを現在のオブジェクトに追加します。
> const obj = {foo: 1, bar: 2};
> {...obj, baz: 3}
{ foo: 1, bar: 2, baz: 3 }プロパティキーが衝突する場合は、最後に記述されたプロパティが「勝ちます」。
> const obj = {foo: 1, bar: 2, baz: 3};
> {...obj, foo: true}
{ foo: true, bar: 2, baz: 3 }
> {foo: true, ...obj}
{ foo: 1, bar: 2, baz: 3 }undefinedやnullを含むすべての値はスプレッド可能です。
> {...undefined}
{}
> {...null}
{}
> {...123}
{}
> {...'abc'}
{ '0': 'a', '1': 'b', '2': 'c' }
> {...['a', 'b']}
{ '0': 'a', '1': 'b' }文字列と配列の.lengthプロパティはこの種の操作からは隠されています(列挙可能ではありません。§28.8.3 「プロパティ属性とプロパティディスクリプタ [ES5]」で詳細を参照してください)。
スプレッド構文を使用して、オブジェクトoriginalのコピーを作成できます。
const copy = {...original};注意:コピーは浅いコピーです。copyは、originalのすべてのプロパティ(キーと値のペア)の複製を持つ新しいオブジェクトです。しかし、プロパティ値がオブジェクトの場合は、それ自体がコピーされません。それらはoriginalとcopyの間で共有されます。例を見てみましょう。
const original = { a: 1, b: {foo: true} };
const copy = {...original};copyの最初のレベルは真のコピーです。そのレベルのプロパティを変更しても、オリジナルには影響しません。
copy.a = 2;
assert.deepEqual(
original, { a: 1, b: {foo: true} }); // no changeしかし、より深いレベルはコピーされません。例えば、.bの値はオリジナルとコピーの間で共有されます。コピー内の.bを変更すると、オリジナルも変更されます。
copy.b.foo = false;
assert.deepEqual(
original, { a: 1, b: {foo: false} }); JavaScriptには、組み込みのディープコピーサポートがありません
オブジェクトのディープコピー(すべてのレベルがコピーされる)は、一般的に行うのが非常に困難です。そのため、JavaScriptには(今のところ)組み込みの操作がありません。このような操作が必要な場合は、自分で実装する必要があります。
コードの入力がデータを含むオブジェクトである場合、欠損プロパティがある場合に使用されるデフォルト値を指定することで、プロパティをオプションにすることができます。そのための一つのテクニックは、プロパティにデフォルト値を含むオブジェクトを使用することです。次の例では、そのオブジェクトがDEFAULTSです。
const DEFAULTS = {foo: 'a', bar: 'b'};
const providedData = {foo: 1};
const allData = {...DEFAULTS, ...providedData};
assert.deepEqual(allData, {foo: 1, bar: 'b'});結果のオブジェクトallDataは、DEFAULTSをコピーし、そのプロパティをprovidedDataのプロパティで上書きすることで作成されます。
しかし、デフォルト値を指定するためにオブジェクトは必要ありません。個別にオブジェクトリテラル内で指定することもできます。
const providedData = {foo: 1};
const allData = {foo: 'a', bar: 'b', ...providedData};
assert.deepEqual(allData, {foo: 1, bar: 'b'});これまでのところ、オブジェクトのプロパティ.fooを変更する方法は1つしか見ていません。それは設定(A行)によってオブジェクトをミューテートすることです。つまり、このプロパティの変更方法は破壊的です。
const obj = {foo: 'a', bar: 'b'};
obj.foo = 1; // (A)
assert.deepEqual(obj, {foo: 1, bar: 'b'});スプレッド構文を使用すると、.fooを非破壊的に変更できます。.fooが異なる値を持つobjのコピーを作成します。
const obj = {foo: 'a', bar: 'b'};
const updatedObj = {...obj, foo: 1};
assert.deepEqual(updatedObj, {foo: 1, bar: 'b'}); 練習問題:スプレッド構文による非破壊的なプロパティ更新(固定キー)
exercises/single-objects/update_name_test.mjs
thisメソッドを紹介するために使用された例を再訪しましょう。
const jane = {
first: 'Jane',
says(text) {
return `${this.first} says “${text}”`;
},
};やや驚くべきことに、メソッドは関数です。
assert.equal(typeof jane.says, 'function');なぜそうなるのでしょうか?呼び出し可能値に関する章で学習したように、通常の関数はいくつかの役割を果たします。メソッドはその役割の1つです。したがって、内部的には、janeはおおよそ以下のようになります。
const jane = {
first: 'Jane',
says: function (text) {
return `${this.first} says “${text}”`;
},
};this次のコードを考えてみましょう
const obj = {
someMethod(x, y) {
assert.equal(this, obj); // (A)
assert.equal(x, 'a');
assert.equal(y, 'b');
}
};
obj.someMethod('a', 'b'); // (B)B行では、objはメソッド呼び出しのレシーバです。これは、obj.someMethodに格納されている関数に、thisという名前の暗黙的(非表示)なパラメータを介して渡されます(A行)。
これは重要な点です。thisを理解する最善の方法は、通常の関数(したがってメソッドも)の暗黙のパラメータとして理解することです。
.call()メソッドは関数であり、§25.7「関数のメソッド:.call()、.apply()、.bind()」で見たように、関数自体にもメソッドがあります。そのメソッドの1つは.call()です。このメソッドの動作を理解するために、例を見てみましょう。
前のセクションでは、次のメソッド呼び出しがありました。
obj.someMethod('a', 'b')この呼び出しは、次の呼び出しと同等です。
obj.someMethod.call(obj, 'a', 'b');これは次の呼び出しと同等でもあります。
const func = obj.someMethod;
func.call(obj, 'a', 'b');.call()は、通常は暗黙的なパラメータであるthisを明示的にします。.call()を介して関数を呼び出す場合、最初の引数はthisであり、その後に通常の(明示的な)関数パラメータが続きます。
余談ですが、これは実際には2つの異なるドット演算子があることを意味します。
obj.propobj.prop()それらは、(2)が(1)に続く関数呼び出し演算子()だけではないという点で異なります。(2)はさらにthisの値を提供します。
.bind().bind()は、関数オブジェクトのもう1つのメソッドです。次のコードでは、.bind()を使用して、メソッド.says()をスタンドアロン関数func()に変換します。
const jane = {
first: 'Jane',
says(text) {
return `${this.first} says “${text}”`; // (A)
},
};
const func = jane.says.bind(jane, 'hello');
assert.equal(func(), 'Jane says “hello”');.bind()を介してthisをjaneに設定することは、ここで非常に重要です。そうでなければ、A行でthisが使用されているため、func()は正しく動作しません。次のセクションでは、その理由を説明します。
thisの落とし穴:メソッドの抽出これで、関数とメソッドについてかなり理解できたので、メソッドとthisに関する最大の落とし穴を見てみましょう。オブジェクトから抽出したメソッドを関数呼び出しすると、注意しないと失敗する可能性があります。
次の例では、メソッドjane.says()を抽出し、変数funcに格納し、func()を関数呼び出しすると失敗します。
const jane = {
first: 'Jane',
says(text) {
return `${this.first} says “${text}”`;
},
};
const func = jane.says; // extract the method
assert.throws(
() => func('hello'), // (A)
{
name: 'TypeError',
message: "Cannot read property 'first' of undefined",
});A行では、通常の関数呼び出しを行っています。通常の関数呼び出しでは、thisはundefinedです(厳格モードが有効な場合、ほとんど常に有効です)。したがって、A行は次の呼び出しと同等です。
assert.throws(
() => jane.says.call(undefined, 'hello'), // `this` is undefined!
{
name: 'TypeError',
message: "Cannot read property 'first' of undefined",
});これを修正するにはどうすればよいでしょうか?メソッド.says()を抽出するには.bind()を使用する必要があります。
const func2 = jane.says.bind(jane);
assert.equal(func2('hello'), 'Jane says “hello”');.bind()は、func()を呼び出すときにthisが常にjaneになるようにします。
アロー関数を使用してメソッドを抽出することもできます。
const func3 = text => jane.says(text);
assert.equal(func3('hello'), 'Jane says “hello”');以下は、実際のWeb開発で見られるコードの簡略版です。
class ClickHandler {
constructor(id, elem) {
this.id = id;
elem.addEventListener('click', this.handleClick); // (A)
}
handleClick(event) {
alert('Clicked ' + this.id);
}
}A行では、メソッド.handleClick()を正しく抽出していません。代わりに、次のようにする必要があります。
elem.addEventListener('click', this.handleClick.bind(this));残念ながら、メソッドの抽出の落とし穴を回避する簡単な方法はありません。メソッドを抽出するたびに、注意深く適切に行う必要があります。たとえば、thisをバインドするか、アロー関数を使用することです。
練習問題:メソッドの抽出
exercises/single-objects/method_extraction_exrc.mjs
thisの落とし穴:thisの偶発的なシャドウイング
thisの偶発的なシャドウイングは、通常の関数でのみ問題になります
アロー関数はthisをシャドウイングしません。
次の問題を考えてみましょう。通常の関数内では、通常の関数自身が独自のthisを持つため、周囲のスコープのthisにアクセスできません。言い換えれば、内部スコープの変数は外部スコープの変数を隠します。これはシャドウイングと呼ばれます。次のコードはその例です。
const prefixer = {
prefix: '==> ',
prefixStringArray(stringArray) {
return stringArray.map(
function (x) {
return this.prefix + x; // (A)
});
},
};
assert.throws(
() => prefixer.prefixStringArray(['a', 'b']),
/^TypeError: Cannot read property 'prefix' of undefined$/);A行では、.prefixStringArray()のthisにアクセスしたいと考えています。しかし、周囲の通常の関数には独自のthisがあり、メソッドのthisをシャドウイング(アクセスをブロック)しているため、アクセスできません。前者のthisの値は、コールバックが関数呼び出しされているためundefinedです。これがエラーメッセージの説明です。
この問題を解決する最も簡単な方法は、アロー関数を使用することです。アロー関数には独自のthisがないため、何もシャドウイングしません。
const prefixer = {
prefix: '==> ',
prefixStringArray(stringArray) {
return stringArray.map(
(x) => {
return this.prefix + x;
});
},
};
assert.deepEqual(
prefixer.prefixStringArray(['a', 'b']),
['==> a', '==> b']);thisを別の変数(A行)に格納して、シャドウイングされないようにすることもできます。
prefixStringArray(stringArray) {
const that = this; // (A)
return stringArray.map(
function (x) {
return that.prefix + x;
});
},別の方法として、.bind()(A行)を使用して、コールバックのthisを固定することもできます。
prefixStringArray(stringArray) {
return stringArray.map(
function (x) {
return this.prefix + x;
}.bind(this)); // (A)
},最後に、.map()を使用すると、コールバックの呼び出し時に使用するthis(A行)を指定できます。
prefixStringArray(stringArray) {
return stringArray.map(
function (x) {
return this.prefix + x;
},
this); // (A)
},thisの偶発的なシャドウイングの落とし穴を回避する§25.3.4「推奨事項:通常の関数よりも特殊化された関数を優先する」のアドバイスに従うと、thisの偶発的なシャドウイングの落とし穴を回避できます。これが要約です。
匿名のインライン関数としてアロー関数を使用します。これらは、thisを暗黙のパラメータとして持たず、シャドウイングしません。
名前付きのスタンドアロン関数宣言には、アロー関数または関数宣言のいずれかを使用できます。後者を使用する場合は、本体にthisが記述されていないことを確認してください。
thisの値(上級)さまざまなコンテキストにおけるthisの値は何でしょうか?
呼び出し可能なエンティティ内では、thisの値は、呼び出し可能なエンティティがどのように呼び出され、どのような種類の呼び出し可能なエンティティであるかによって異なります。
this === undefined(厳格モードの場合)thisは周囲のスコープと同じです(レキシカルthis)thisは呼び出しのレシーバですnew:thisは新しく作成されたインスタンスを参照します一般的なトップレベルスコープでもthisにアクセスできます。
<script>要素:this === globalThisthis === undefinedthis === module.exports ヒント:トップレベルスコープでは
thisが存在しないと仮定しましょう
トップレベルのthisは混乱を招きやすく、ほとんど役に立たないため、私はそうしています。
次の種類のオプションチェーン操作が存在します。
obj?.prop // optional static property access
obj?.[«expr»] // optional dynamic property access
func?.(«arg0», «arg1») // optional function or method call大まかな考え方は次のとおりです。
undefinedでもnullでもない場合、疑問符の後の操作を実行します。undefinedを返します。次のデータを考えてみましょう。
const persons = [
{
surname: 'Zoe',
address: {
street: {
name: 'Sesame Street',
number: '123',
},
},
},
{
surname: 'Mariner',
},
{
surname: 'Carmen',
address: {
},
},
];オプションチェーンを使用して、安全に通りの名前を抽出できます。
const streetNames = persons.map(
p => p.address?.street?.name);
assert.deepEqual(
streetNames, ['Sesame Street', undefined, undefined]
);ナル結合演算子を使用すると、undefinedの代わりにデフォルト値'(no street)'を使用できます。
const streetNames = persons.map(
p => p.address?.street?.name ?? '(no name)');
assert.deepEqual(
streetNames, ['Sesame Street', '(no name)', '(no name)']
);次の2つの式は同等です。
o?.prop
(o !== undefined && o !== null) ? o.prop : undefined例
assert.equal(undefined?.prop, undefined);
assert.equal(null?.prop, undefined);
assert.equal({prop:1}?.prop, 1);次の2つの式は同等です。
o?.[«expr»]
(o !== undefined && o !== null) ? o[«expr»] : undefined例
const key = 'prop';
assert.equal(undefined?.[key], undefined);
assert.equal(null?.[key], undefined);
assert.equal({prop:1}?.[key], 1);次の2つの式は同等です。
f?.(arg0, arg1)
(f !== undefined && f !== null) ? f(arg0, arg1) : undefined例
assert.equal(undefined?.(123), undefined);
assert.equal(null?.(123), undefined);
assert.equal(String?.(123), '123');この演算子の左側が呼び出し可能でない場合、エラーが発生することに注意してください。
assert.throws(
() => true?.(123),
TypeError);なぜでしょうか?この考え方は、演算子が意図的な省略を許容することです。undefinedとnull以外の呼び出し不可能な値はおそらくエラーであり、回避策を講じるのではなく、報告する必要があります。
プロパティアクセスと関数/メソッド呼び出しのチェーンでは、最初のオプション演算子が左側にundefinedまたはnullを検出すると、評価が停止します。
function isInvoked(obj) {
let invoked = false;
obj?.a.b.m(invoked = true);
return invoked;
}
assert.equal(
isInvoked({a: {b: {m() {}}}}), true);
// The left-hand side of ?. is undefined
// and the assignment is not executed
assert.equal(
isInvoked(undefined), false);この動作は、JavaScriptが演算子/関数を評価する前に常にすべてのオペランド/引数を評価する通常の演算子/関数とは異なります。これは短絡評価と呼ばれます。その他の短絡評価演算子:
a && ba || bc ? t : eo?.[x]とf?.()にドットがあるのはなぜですか?次の2つのオプション演算子の構文は理想的ではありません。
obj?.[«expr»] // better: obj?[«expr»]
func?.(«arg0», «arg1») // better: func?(«arg0», «arg1»)残念ながら、よりエレガントではない構文は、理想的な構文(最初の式)と条件演算子(2番目の式)を区別することが複雑すぎるため、必要です。
obj?['a', 'b', 'c'].map(x => x+x)
obj ? ['a', 'b', 'c'].map(x => x+x) : []null?.propがundefinedではなくnullを評価するのはなぜですか?演算子?.は主にその右側に関するものです。プロパティ.propは存在しますか?存在しない場合、早期に停止します。したがって、その左側の情報を保持することはめったに役に立ちません。ただし、単一の「早期終了」値だけにすることで、事態が簡素化されます。
オブジェクトはレコードとして最適に機能します。しかし、ES6以前は、JavaScriptには辞書用のデータ構造がありませんでした(ES6でMapが導入されました)。そのため、オブジェクトは辞書として使用しなければならず、これは重要な制約を課しました。キーは文字列でなければなりませんでした(シンボルもES6で導入されました)。
最初に、辞書に関連するオブジェクトの機能、またレコードとしてのオブジェクトにも役立つ機能を見ていきます。このセクションは、オブジェクトを辞書として実際に使用するためのヒントで締めくくります(ネタバレ:可能であればMapを使用してください)。
これまでのところ、常にオブジェクトをレコードとして使用してきました。プロパティキーは、有効な識別子でなければならない固定トークンであり、内部的には文字列になりました。
const obj = {
mustBeAnIdentifier: 123,
};
// Get property
assert.equal(obj.mustBeAnIdentifier, 123);
// Set property
obj.mustBeAnIdentifier = 'abc';
assert.equal(obj.mustBeAnIdentifier, 'abc');次のステップとして、プロパティキーのこの制限を超えます。このセクションでは、任意の固定文字列をキーとして使用します。次のサブセクションでは、キーを動的に計算します。
任意の文字列をプロパティキーとして使用するには、2つの技法があります。
まず、オブジェクトリテラルを介してプロパティキーを作成する場合、プロパティキーを引用符で囲むことができます(一重引用符または二重引用符)。
const obj = {
'Can be any string!': 123,
};次に、プロパティを取得または設定する場合、内部に文字列を含む角括弧を使用できます。
// Get property
assert.equal(obj['Can be any string!'], 123);
// Set property
obj['Can be any string!'] = 'abc';
assert.equal(obj['Can be any string!'], 'abc');これらの技法をメソッドにも使用できます。
const obj = {
'A nice method'() {
return 'Yes!';
},
};
assert.equal(obj['A nice method'](), 'Yes!');これまでのところ、プロパティキーはオブジェクトリテラル内の常に固定文字列でした。このセクションでは、プロパティキーを動的に計算する方法を学びます。これにより、任意の文字列またはシンボルを使用できます。
オブジェクトリテラルにおける動的に計算されたプロパティキーの構文は、プロパティへの動的なアクセスに触発されています。つまり、角括弧を使用して式を囲むことができます。
const obj = {
['Hello world!']: true,
['f'+'o'+'o']: 123,
[Symbol.toStringTag]: 'Goodbye', // (A)
};
assert.equal(obj['Hello world!'], true);
assert.equal(obj.foo, 123);
assert.equal(obj[Symbol.toStringTag], 'Goodbye');計算されたキーの主なユースケースは、プロパティキーとしてシンボルを使用することです(A行)。
プロパティを取得および設定するための角括弧演算子は、任意の式で動作することに注意してください。
assert.equal(obj['f'+'o'+'o'], 123);
assert.equal(obj['==> foo'.slice(-3)], 123);メソッドにも計算されたプロパティキーを持つことができます。
const methodKey = Symbol();
const obj = {
[methodKey]() {
return 'Yes!';
},
};
assert.equal(obj[methodKey](), 'Yes!');この章の残りの部分では、主に固定プロパティキーを再び使用します(構文的により便利であるため)。しかし、すべての機能は任意の文字列とシンボルにも利用できます。
練習問題:スプレッド(計算されたキー)を使用したプロパティの非破壊的な更新
exercises/single-objects/update_property_test.mjs
in演算子:指定されたキーを持つプロパティはありますか?in演算子は、オブジェクトに指定されたキーを持つプロパティがあるかどうかを確認します。
const obj = {
foo: 'abc',
bar: false,
};
assert.equal('foo' in obj, true);
assert.equal('unknownKey' in obj, false);真理値チェックを使用して、プロパティが存在するかどうかを確認することもできます。
assert.equal(
obj.foo ? 'exists' : 'does not exist',
'exists');
assert.equal(
obj.unknownKey ? 'exists' : 'does not exist',
'does not exist');以前のチェックは、obj.fooが真理値であり、欠損プロパティの読み取りがundefined(偽値)を返すため機能します。
ただし、重要な注意点が1つあります。プロパティが存在するが、偽の値(`undefined`、`null`、`false`、`0`、`""`など)を持つ場合、真偽値チェックは失敗します。
assert.equal(
obj.bar ? 'exists' : 'does not exist',
'does not exist'); // should be: 'exists'`delete`演算子を使用してプロパティを削除できます。
const obj = {
foo: 123,
};
assert.deepEqual(Object.keys(obj), ['foo']);
delete obj.foo;
assert.deepEqual(Object.keys(obj), []);| 列挙可能 | 非列挙 | 文字列 | シンボル | |
|---|---|---|---|---|
Object.keys() |
✔ |
✔ |
||
Object.getOwnPropertyNames() |
✔ |
✔ |
✔ |
|
Object.getOwnPropertySymbols() |
✔ |
✔ |
✔ |
|
Reflect.ownKeys() |
✔ |
✔ |
✔ |
✔ |
表19の各メソッドは、パラメーターの独自のプロパティキーを含む配列を返します。メソッド名から、以下の区別が行われていることがわかります。
次のセクションでは、列挙可能という用語について説明し、各メソッドを示します。
列挙可能性は、プロパティの属性です。非列挙可能なプロパティは、一部の操作(たとえば、`Object.keys()`(表19参照)やスプレッドプロパティ)では無視されます。デフォルトでは、ほとんどのプロパティは列挙可能です。次の例は、それを変更する方法を示しています。また、プロパティキーを列挙するさまざまな方法も示しています。
const enumerableSymbolKey = Symbol('enumerableSymbolKey');
const nonEnumSymbolKey = Symbol('nonEnumSymbolKey');
// We create enumerable properties via an object literal
const obj = {
enumerableStringKey: 1,
[enumerableSymbolKey]: 2,
}
// For non-enumerable properties, we need a more powerful tool
Object.defineProperties(obj, {
nonEnumStringKey: {
value: 3,
enumerable: false,
},
[nonEnumSymbolKey]: {
value: 4,
enumerable: false,
},
});
assert.deepEqual(
Object.keys(obj),
[ 'enumerableStringKey' ]);
assert.deepEqual(
Object.getOwnPropertyNames(obj),
[ 'enumerableStringKey', 'nonEnumStringKey' ]);
assert.deepEqual(
Object.getOwnPropertySymbols(obj),
[ enumerableSymbolKey, nonEnumSymbolKey ]);
assert.deepEqual(
Reflect.ownKeys(obj),
[
'enumerableStringKey', 'nonEnumStringKey',
enumerableSymbolKey, nonEnumSymbolKey,
]);`Object.defineProperties()`については、この章の後半で説明します。
`Object.values()`は、オブジェクトのすべての列挙可能なプロパティの値を列挙します。
const obj = {foo: 1, bar: 2};
assert.deepEqual(
Object.values(obj),
[1, 2]);`Object.entries()`は、列挙可能なプロパティのキーと値のペアを列挙します。各ペアは、2要素の配列としてエンコードされます。
const obj = {foo: 1, bar: 2};
assert.deepEqual(
Object.entries(obj),
[
['foo', 1],
['bar', 2],
]); 練習問題:`Object.entries()`
exercises/single-objects/find_key_test.mjs
オブジェクトの独自の(継承されていない)プロパティは、常に次の順序で列挙されます。
次の例は、プロパティキーがこれらのルールに従ってどのようにソートされるかを示しています。
> Object.keys({b:0,a:0, 10:0,2:0})
[ '2', '10', 'b', 'a' ] プロパティの順序
ECMAScript仕様では、プロパティの順序付けについてより詳細に説明されています。
[キー、値]ペアのイテラブルが与えられると、`Object.fromEntries()`はオブジェクトを作成します。
assert.deepEqual(
Object.fromEntries([['foo',1], ['bar',2]]),
{
foo: 1,
bar: 2,
}
);`Object.fromEntries()`は、`Object.entries()`の逆を行います。
両方を示すために、Underscoreライブラリの2つのツール関数を次の小節で実装します。
`pick`は、引数として指定されたキーを持つプロパティのみを持つ、`object`のコピーを返します。
const address = {
street: 'Evergreen Terrace',
number: '742',
city: 'Springfield',
state: 'NT',
zip: '49007',
};
assert.deepEqual(
pick(address, 'street', 'number'),
{
street: 'Evergreen Terrace',
number: '742',
}
);`pick()`は次のように実装できます。
function pick(object, ...keys) {
const filteredEntries = Object.entries(object)
.filter(([key, _value]) => keys.includes(key));
return Object.fromEntries(filteredEntries);
}`invert`は、すべてのプロパティのキーと値が入れ替えられた、`object`のコピーを返します。
assert.deepEqual(
invert({a: 1, b: 2, c: 3}),
{1: 'a', 2: 'b', 3: 'c'}
);`invert()`は次のように実装できます。
function invert(object) {
const reversedEntries = Object.entries(object)
.map(([key, value]) => [value, key]);
return Object.fromEntries(reversedEntries);
}次の関数は、`Object.fromEntries()`の簡略版です。
function fromEntries(iterable) {
const result = {};
for (const [key, value] of iterable) {
let coercedKey;
if (typeof key === 'string' || typeof key === 'symbol') {
coercedKey = key;
} else {
coercedKey = String(key);
}
result[coercedKey] = value;
}
return result;
} 練習問題:`Object.entries()`と`Object.fromEntries()`
exercises/single-objects/omit_properties_test.mjs
プレーンオブジェクト(オブジェクトリテラルを使用して作成されたもの)を辞書として使用する場合、2つの落とし穴に注意する必要があります。
最初の落とし穴は、`in`演算子が継承されたプロパティも検出することです。
const dict = {};
assert.equal('toString' in dict, true);`dict`は空として扱われるべきですが、`in`演算子は、そのプロトタイプである`Object.prototype`から継承したプロパティを検出します。
2番目の落とし穴は、特別な権限を持つ(オブジェクトのプロトタイプを設定する)ため、プロパティキー`__proto__`を使用できないことです。
const dict = {};
dict['__proto__'] = 123;
// No property was added to dict:
assert.deepEqual(Object.keys(dict), []);では、これらの2つの落とし穴をどのように回避すればよいでしょうか?
次のコードは、プロトタイプを持たないオブジェクトを辞書として使用する方法を示しています。
const dict = Object.create(null); // no prototype
assert.equal('toString' in dict, false); // (A)
dict['__proto__'] = 123;
assert.deepEqual(Object.keys(dict), ['__proto__']);両方の落とし穴を回避しました。
練習問題:オブジェクトを辞書として使用
exercises/single-objects/simple_dict_test.mjs
`Object.prototype`は、オブジェクトが言語によってどのように扱われるかを構成するためにオーバーライドできるいくつかの標準メソッドを定義しています。重要なものの2つは次のとおりです。
.toString().valueOf()`.toString()`は、オブジェクトが文字列に変換される方法を決定します。
> String({toString() { return 'Hello!' }})
'Hello!'
> String({})
'[object Object]'`.valueOf()`は、オブジェクトが数値に変換される方法を決定します。
> Number({valueOf() { return 123 }})
123
> Number({})
NaN以下の小節では、いくつかの高度なトピックの概要を簡単に説明します。
`Object.assign()`はツールメソッドです。
Object.assign(target, source_1, source_2, ···)この式は、`source_1`のすべてのプロパティを`target`に、次に`source_2`のすべてのプロパティなどを割り当てます。最後に、`target`を返します。例:
const target = { foo: 1 };
const result = Object.assign(
target,
{bar: 2},
{baz: 3, bar: 4});
assert.deepEqual(
result, { foo: 1, bar: 4, baz: 3 });
// target was modified and returned:
assert.equal(result, target);`Object.assign()`のユースケースは、スプレッドプロパティの場合と似ています。ある意味、破壊的にスプレッドします。
`Object.freeze(obj)`は、`obj`を完全に不変にします。プロパティの変更、プロパティの追加、プロトタイプの変更はできません。例:
const frozen = Object.freeze({ x: 2, y: 5 });
assert.throws(
() => { frozen.x = 7 },
{
name: 'TypeError',
message: /^Cannot assign to read only property 'x'/,
});注意点が1つあります。`Object.freeze(obj)`は浅い凍結です。つまり、`obj`のプロパティのみが凍結され、プロパティに格納されているオブジェクトは凍結されません。
詳細情報
オブジェクトの凍結やその他のロックダウン方法の詳細については、Deep JavaScriptを参照してください。
オブジェクトがプロパティで構成されているのと同様に、プロパティは属性で構成されています。プロパティの値は、いくつかの属性の1つに過ぎません。その他には次のものがあります。
プロパティ属性を処理するための操作の1つを使用する場合、属性はプロパティ記述子を介して指定されます。各プロパティが1つの属性を表すオブジェクトです。たとえば、プロパティ`obj.foo`の属性は次のように読み取ります。
const obj = { foo: 123 };
assert.deepEqual(
Object.getOwnPropertyDescriptor(obj, 'foo'),
{
value: 123,
writable: true,
enumerable: true,
configurable: true,
});そして、プロパティ`obj.bar`の属性は次のように設定します。
const obj = {
foo: 1,
bar: 2,
};
assert.deepEqual(Object.keys(obj), ['foo', 'bar']);
// Hide property `bar` from Object.keys()
Object.defineProperty(obj, 'bar', {
enumerable: false,
});
assert.deepEqual(Object.keys(obj), ['foo']);さらに読む
クイズ
クイズアプリを参照してください。