...
) [ES2018]this
this
.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};
}.deepEqual(
assertcreatePoint(9, 2),
x: 9, y: 2 }
{ ; )
プロパティを取得(読み込み)する方法は次のとおりです(A行)。
const jane = {
first: 'Jane',
last: 'Doe',
;
}
// Get property .first
.equal(jane.first, 'Jane'); // (A) assert
未知のプロパティを取得するとundefined
が生成されます。
.equal(jane.unknownProperty, undefined); assert
プロパティを設定(書き込み)する方法は次のとおりです。
const obj = {
prop: 1,
;
}.equal(obj.prop, 1);
assert.prop = 2; // (A)
obj.equal(obj.prop, 2); assert
設定によって既存のプロパティを変更しました。未知のプロパティを設定すると、新しいエントリが作成されます。
const obj = {}; // empty object
.deepEqual(
assertObject.keys(obj), []);
.unknownProperty = 'abc';
obj.deepEqual(
assertObject.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)
};
}.equal(jane.says('hello'), 'Jane says “hello”'); assert
メソッド呼び出しjane.says('hello')
の間、jane
はメソッド呼び出しのレシーバと呼ばれ、特別な変数this
に代入されます(this
の詳細については§28.4 「メソッドと特別な変数this」を参照)。これにより、メソッド.says()
はA行で兄弟プロパティ.first
にアクセスできます。
JavaScriptには2種類のアクセサがあります。
ゲッターは、メソッド定義の前に修飾子get
を付けることで作成されます。
const jane = {
first: 'Jane',
last: 'Doe',
full() {
get return `${this.first} ${this.last}`;
,
};
}
.equal(jane.full, 'Jane Doe');
assert.first = 'John';
jane.equal(jane.full, 'John Doe'); assert
セッターは、メソッド定義の前に修飾子set
を付けることで作成されます。
const jane = {
first: 'Jane',
last: 'Doe',
full(fullName) {
set const parts = fullName.split(' ');
this.first = parts[0];
this.last = parts[1];
,
};
}
.full = 'Richard Roe';
jane.equal(jane.first, 'Richard');
assert.equal(jane.last, 'Roe'); assert
練習問題:オブジェクトリテラルによるオブジェクトの作成
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
の最初のレベルは真のコピーです。そのレベルのプロパティを変更しても、オリジナルには影響しません。
.a = 2;
copy.deepEqual(
assert, { a: 1, b: {foo: true} }); // no change original
しかし、より深いレベルはコピーされません。例えば、.b
の値はオリジナルとコピーの間で共有されます。コピー内の.b
を変更すると、オリジナルも変更されます。
.b.foo = false;
copy.deepEqual(
assert, { a: 1, b: {foo: false} }); original
JavaScriptには、組み込みのディープコピーサポートがありません
オブジェクトのディープコピー(すべてのレベルがコピーされる)は、一般的に行うのが非常に困難です。そのため、JavaScriptには(今のところ)組み込みの操作がありません。このような操作が必要な場合は、自分で実装する必要があります。
コードの入力がデータを含むオブジェクトである場合、欠損プロパティがある場合に使用されるデフォルト値を指定することで、プロパティをオプションにすることができます。そのための一つのテクニックは、プロパティにデフォルト値を含むオブジェクトを使用することです。次の例では、そのオブジェクトがDEFAULTS
です。
const DEFAULTS = {foo: 'a', bar: 'b'};
const providedData = {foo: 1};
const allData = {...DEFAULTS, ...providedData};
.deepEqual(allData, {foo: 1, bar: 'b'}); assert
結果のオブジェクトallData
は、DEFAULTS
をコピーし、そのプロパティをprovidedData
のプロパティで上書きすることで作成されます。
しかし、デフォルト値を指定するためにオブジェクトは必要ありません。個別にオブジェクトリテラル内で指定することもできます。
const providedData = {foo: 1};
const allData = {foo: 'a', bar: 'b', ...providedData};
.deepEqual(allData, {foo: 1, bar: 'b'}); assert
これまでのところ、オブジェクトのプロパティ.foo
を変更する方法は1つしか見ていません。それは設定(A行)によってオブジェクトをミューテートすることです。つまり、このプロパティの変更方法は破壊的です。
const obj = {foo: 'a', bar: 'b'};
.foo = 1; // (A)
obj.deepEqual(obj, {foo: 1, bar: 'b'}); assert
スプレッド構文を使用すると、.foo
を非破壊的に変更できます。.foo
が異なる値を持つobj
のコピーを作成します。
const obj = {foo: 'a', bar: 'b'};
const updatedObj = {...obj, foo: 1};
.deepEqual(updatedObj, {foo: 1, bar: 'b'}); assert
練習問題:スプレッド構文による非破壊的なプロパティ更新(固定キー)
exercises/single-objects/update_name_test.mjs
this
メソッドを紹介するために使用された例を再訪しましょう。
const jane = {
first: 'Jane',
says(text) {
return `${this.first} says “${text}”`;
,
}; }
やや驚くべきことに、メソッドは関数です。
.equal(typeof jane.says, 'function'); assert
なぜそうなるのでしょうか?呼び出し可能値に関する章で学習したように、通常の関数はいくつかの役割を果たします。メソッドはその役割の1つです。したがって、内部的には、jane
はおおよそ以下のようになります。
const jane = {
first: 'Jane',
says: function (text) {
return `${this.first} says “${text}”`;
,
}; }
this
次のコードを考えてみましょう
const obj = {
someMethod(x, y) {
.equal(this, obj); // (A)
assert.equal(x, 'a');
assert.equal(y, 'b');
assert
};
}.someMethod('a', 'b'); // (B) obj
B行では、obj
はメソッド呼び出しのレシーバです。これは、obj.someMethod
に格納されている関数に、this
という名前の暗黙的(非表示)なパラメータを介して渡されます(A行)。
これは重要な点です。this
を理解する最善の方法は、通常の関数(したがってメソッドも)の暗黙のパラメータとして理解することです。
.call()
メソッドは関数であり、§25.7「関数のメソッド:.call()
、.apply()
、.bind()
」で見たように、関数自体にもメソッドがあります。そのメソッドの1つは.call()
です。このメソッドの動作を理解するために、例を見てみましょう。
前のセクションでは、次のメソッド呼び出しがありました。
.someMethod('a', 'b') obj
この呼び出しは、次の呼び出しと同等です。
.someMethod.call(obj, 'a', 'b'); obj
これは次の呼び出しと同等でもあります。
const func = obj.someMethod;
.call(obj, 'a', 'b'); func
.call()
は、通常は暗黙的なパラメータであるthis
を明示的にします。.call()
を介して関数を呼び出す場合、最初の引数はthis
であり、その後に通常の(明示的な)関数パラメータが続きます。
余談ですが、これは実際には2つの異なるドット演算子があることを意味します。
obj.prop
obj.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');
.equal(func(), 'Jane says “hello”'); assert
.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
.throws(
assert=> func('hello'), // (A)
()
{name: 'TypeError',
message: "Cannot read property 'first' of undefined",
; })
A行では、通常の関数呼び出しを行っています。通常の関数呼び出しでは、this
はundefined
です(厳格モードが有効な場合、ほとんど常に有効です)。したがって、A行は次の呼び出しと同等です。
.throws(
assert=> 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);
.equal(func2('hello'), 'Jane says “hello”'); assert
.bind()
は、func()
を呼び出すときにthis
が常にjane
になるようにします。
アロー関数を使用してメソッドを抽出することもできます。
const func3 = text => jane.says(text);
.equal(func3('hello'), 'Jane says “hello”'); assert
以下は、実際のWeb開発で見られるコードの簡略版です。
class ClickHandler {
constructor(id, elem) {
this.id = id;
.addEventListener('click', this.handleClick); // (A)
elem
}handleClick(event) {
alert('Clicked ' + this.id);
} }
A行では、メソッド.handleClick()
を正しく抽出していません。代わりに、次のようにする必要があります。
.addEventListener('click', this.handleClick.bind(this)); elem
残念ながら、メソッドの抽出の落とし穴を回避する簡単な方法はありません。メソッドを抽出するたびに、注意深く適切に行う必要があります。たとえば、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)
;
}),
};
}.throws(
assert=> 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;
;
}),
};
}.deepEqual(
assert.prefixStringArray(['a', 'b']),
prefixer'==> 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 === globalThis
this === undefined
this === module.exports
ヒント:トップレベルスコープでは
this
が存在しないと仮定しましょう
トップレベルのthis
は混乱を招きやすく、ほとんど役に立たないため、私はそうしています。
次の種類のオプションチェーン操作が存在します。
?.prop // optional static property access
obj?.[«expr»] // optional dynamic property access
obj?.(«arg0», «arg1») // optional function or method call func
大まかな考え方は次のとおりです。
undefined
でもnull
でもない場合、疑問符の後の操作を実行します。undefined
を返します。次のデータを考えてみましょう。
const persons = [
{surname: 'Zoe',
address: {
street: {
name: 'Sesame Street',
number: '123',
,
},
},
}
{surname: 'Mariner',
,
}
{surname: 'Carmen',
address: {
,
},
}; ]
オプションチェーンを使用して、安全に通りの名前を抽出できます。
const streetNames = persons.map(
=> p.address?.street?.name);
p .deepEqual(
assert, ['Sesame Street', undefined, undefined]
streetNames; )
ナル結合演算子を使用すると、undefined
の代わりにデフォルト値'(no street)'
を使用できます。
const streetNames = persons.map(
=> p.address?.street?.name ?? '(no name)');
p .deepEqual(
assert, ['Sesame Street', '(no name)', '(no name)']
streetNames; )
次の2つの式は同等です。
?.prop
o!== undefined && o !== null) ? o.prop : undefined (o
例
.equal(undefined?.prop, undefined);
assert.equal(null?.prop, undefined);
assert.equal({prop:1}?.prop, 1); assert
次の2つの式は同等です。
?.[«expr»]
o!== undefined && o !== null) ? o[«expr»] : undefined (o
例
const key = 'prop';
.equal(undefined?.[key], undefined);
assert.equal(null?.[key], undefined);
assert.equal({prop:1}?.[key], 1); assert
次の2つの式は同等です。
?.(arg0, arg1)
f!== undefined && f !== null) ? f(arg0, arg1) : undefined (f
例
.equal(undefined?.(123), undefined);
assert.equal(null?.(123), undefined);
assert.equal(String?.(123), '123'); assert
この演算子の左側が呼び出し可能でない場合、エラーが発生することに注意してください。
.throws(
assert=> true?.(123),
() TypeError);
なぜでしょうか?この考え方は、演算子が意図的な省略を許容することです。undefined
とnull
以外の呼び出し不可能な値はおそらくエラーであり、回避策を講じるのではなく、報告する必要があります。
プロパティアクセスと関数/メソッド呼び出しのチェーンでは、最初のオプション演算子が左側にundefined
またはnull
を検出すると、評価が停止します。
function isInvoked(obj) {
let invoked = false;
?.a.b.m(invoked = true);
objreturn invoked;
}
.equal(
assertisInvoked({a: {b: {m() {}}}}), true);
// The left-hand side of ?. is undefined
// and the assignment is not executed
.equal(
assertisInvoked(undefined), false);
この動作は、JavaScriptが演算子/関数を評価する前に常にすべてのオペランド/引数を評価する通常の演算子/関数とは異なります。これは短絡評価と呼ばれます。その他の短絡評価演算子:
a && b
a || b
c ? t : e
o?.[x]
とf?.()
にドットがあるのはなぜですか?次の2つのオプション演算子の構文は理想的ではありません。
?.[«expr»] // better: obj?[«expr»]
obj?.(«arg0», «arg1») // better: func?(«arg0», «arg1») func
残念ながら、よりエレガントではない構文は、理想的な構文(最初の式)と条件演算子(2番目の式)を区別することが複雑すぎるため、必要です。
?['a', 'b', 'c'].map(x => x+x)
obj? ['a', 'b', 'c'].map(x => x+x) : [] obj
null?.prop
がundefined
ではなくnull
を評価するのはなぜですか?演算子?.
は主にその右側に関するものです。プロパティ.prop
は存在しますか?存在しない場合、早期に停止します。したがって、その左側の情報を保持することはめったに役に立ちません。ただし、単一の「早期終了」値だけにすることで、事態が簡素化されます。
オブジェクトはレコードとして最適に機能します。しかし、ES6以前は、JavaScriptには辞書用のデータ構造がありませんでした(ES6でMapが導入されました)。そのため、オブジェクトは辞書として使用しなければならず、これは重要な制約を課しました。キーは文字列でなければなりませんでした(シンボルもES6で導入されました)。
最初に、辞書に関連するオブジェクトの機能、またレコードとしてのオブジェクトにも役立つ機能を見ていきます。このセクションは、オブジェクトを辞書として実際に使用するためのヒントで締めくくります(ネタバレ:可能であればMapを使用してください)。
これまでのところ、常にオブジェクトをレコードとして使用してきました。プロパティキーは、有効な識別子でなければならない固定トークンであり、内部的には文字列になりました。
const obj = {
mustBeAnIdentifier: 123,
;
}
// Get property
.equal(obj.mustBeAnIdentifier, 123);
assert
// Set property
.mustBeAnIdentifier = 'abc';
obj.equal(obj.mustBeAnIdentifier, 'abc'); assert
次のステップとして、プロパティキーのこの制限を超えます。このセクションでは、任意の固定文字列をキーとして使用します。次のサブセクションでは、キーを動的に計算します。
任意の文字列をプロパティキーとして使用するには、2つの技法があります。
まず、オブジェクトリテラルを介してプロパティキーを作成する場合、プロパティキーを引用符で囲むことができます(一重引用符または二重引用符)。
const obj = {
'Can be any string!': 123,
; }
次に、プロパティを取得または設定する場合、内部に文字列を含む角括弧を使用できます。
// Get property
.equal(obj['Can be any string!'], 123);
assert
// Set property
'Can be any string!'] = 'abc';
obj[.equal(obj['Can be any string!'], 'abc'); assert
これらの技法をメソッドにも使用できます。
const obj = {
'A nice method'() {
return 'Yes!';
,
};
}
.equal(obj['A nice method'](), 'Yes!'); assert
これまでのところ、プロパティキーはオブジェクトリテラル内の常に固定文字列でした。このセクションでは、プロパティキーを動的に計算する方法を学びます。これにより、任意の文字列またはシンボルを使用できます。
オブジェクトリテラルにおける動的に計算されたプロパティキーの構文は、プロパティへの動的なアクセスに触発されています。つまり、角括弧を使用して式を囲むことができます。
const obj = {
'Hello world!']: true,
['f'+'o'+'o']: 123,
[Symbol.toStringTag]: 'Goodbye', // (A)
[;
}
.equal(obj['Hello world!'], true);
assert.equal(obj.foo, 123);
assert.equal(obj[Symbol.toStringTag], 'Goodbye'); assert
計算されたキーの主なユースケースは、プロパティキーとしてシンボルを使用することです(A行)。
プロパティを取得および設定するための角括弧演算子は、任意の式で動作することに注意してください。
.equal(obj['f'+'o'+'o'], 123);
assert.equal(obj['==> foo'.slice(-3)], 123); assert
メソッドにも計算されたプロパティキーを持つことができます。
const methodKey = Symbol();
const obj = {
[methodKey]() {return 'Yes!';
,
};
}
.equal(obj[methodKey](), 'Yes!'); assert
この章の残りの部分では、主に固定プロパティキーを再び使用します(構文的により便利であるため)。しかし、すべての機能は任意の文字列とシンボルにも利用できます。
練習問題:スプレッド(計算されたキー)を使用したプロパティの非破壊的な更新
exercises/single-objects/update_property_test.mjs
in
演算子:指定されたキーを持つプロパティはありますか?in
演算子は、オブジェクトに指定されたキーを持つプロパティがあるかどうかを確認します。
const obj = {
foo: 'abc',
bar: false,
;
}
.equal('foo' in obj, true);
assert.equal('unknownKey' in obj, false); assert
真理値チェックを使用して、プロパティが存在するかどうかを確認することもできます。
.equal(
assert.foo ? 'exists' : 'does not exist',
obj'exists');
.equal(
assert.unknownKey ? 'exists' : 'does not exist',
obj'does not exist');
以前のチェックは、obj.foo
が真理値であり、欠損プロパティの読み取りがundefined
(偽値)を返すため機能します。
ただし、重要な注意点が1つあります。プロパティが存在するが、偽の値(`undefined`、`null`、`false`、`0`、`""`など)を持つ場合、真偽値チェックは失敗します。
.equal(
assert.bar ? 'exists' : 'does not exist',
obj'does not exist'); // should be: 'exists'
`delete`演算子を使用してプロパティを削除できます。
const obj = {
foo: 123,
;
}.deepEqual(Object.keys(obj), ['foo']);
assert
delete obj.foo;
.deepEqual(Object.keys(obj), []); assert
列挙可能 | 非列挙 | 文字列 | シンボル | |
---|---|---|---|---|
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,
: 2,
[enumerableSymbolKey]
}
// For non-enumerable properties, we need a more powerful tool
Object.defineProperties(obj, {
nonEnumStringKey: {
value: 3,
enumerable: false,
,
}: {
[nonEnumSymbolKey]value: 4,
enumerable: false,
,
};
})
.deepEqual(
assertObject.keys(obj),
'enumerableStringKey' ]);
[ .deepEqual(
assertObject.getOwnPropertyNames(obj),
'enumerableStringKey', 'nonEnumStringKey' ]);
[ .deepEqual(
assertObject.getOwnPropertySymbols(obj),
, nonEnumSymbolKey ]);
[ enumerableSymbolKey.deepEqual(
assertReflect.ownKeys(obj),
['enumerableStringKey', 'nonEnumStringKey',
, nonEnumSymbolKey,
enumerableSymbolKey; ])
`Object.defineProperties()`については、この章の後半で説明します。
`Object.values()`は、オブジェクトのすべての列挙可能なプロパティの値を列挙します。
const obj = {foo: 1, bar: 2};
.deepEqual(
assertObject.values(obj),
1, 2]); [
`Object.entries()`は、列挙可能なプロパティのキーと値のペアを列挙します。各ペアは、2要素の配列としてエンコードされます。
const obj = {foo: 1, bar: 2};
.deepEqual(
assertObject.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()`はオブジェクトを作成します。
.deepEqual(
assertObject.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',
;
}.deepEqual(
assertpick(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`のコピーを返します。
.deepEqual(
assertinvert({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') {
= key;
coercedKey else {
} = String(key);
coercedKey
}= value;
result[coercedKey]
}return result;
}
練習問題:`Object.entries()`と`Object.fromEntries()`
exercises/single-objects/omit_properties_test.mjs
プレーンオブジェクト(オブジェクトリテラルを使用して作成されたもの)を辞書として使用する場合、2つの落とし穴に注意する必要があります。
最初の落とし穴は、`in`演算子が継承されたプロパティも検出することです。
const dict = {};
.equal('toString' in dict, true); assert
`dict`は空として扱われるべきですが、`in`演算子は、そのプロトタイプである`Object.prototype`から継承したプロパティを検出します。
2番目の落とし穴は、特別な権限を持つ(オブジェクトのプロトタイプを設定する)ため、プロパティキー`__proto__`を使用できないことです。
const dict = {};
'__proto__'] = 123;
dict[// No property was added to dict:
.deepEqual(Object.keys(dict), []); assert
では、これらの2つの落とし穴をどのように回避すればよいでしょうか?
次のコードは、プロトタイプを持たないオブジェクトを辞書として使用する方法を示しています。
const dict = Object.create(null); // no prototype
.equal('toString' in dict, false); // (A)
assert
'__proto__'] = 123;
dict[.deepEqual(Object.keys(dict), ['__proto__']); assert
両方の落とし穴を回避しました。
練習問題:オブジェクトを辞書として使用
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(
,
targetbar: 2},
{baz: 3, bar: 4});
{
.deepEqual(
assert, { foo: 1, bar: 4, baz: 3 });
result// target was modified and returned:
.equal(result, target); assert
`Object.assign()`のユースケースは、スプレッドプロパティの場合と似ています。ある意味、破壊的にスプレッドします。
`Object.freeze(obj)`は、`obj`を完全に不変にします。プロパティの変更、プロパティの追加、プロトタイプの変更はできません。例:
const frozen = Object.freeze({ x: 2, y: 5 });
.throws(
assert=> { 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 };
.deepEqual(
assertObject.getOwnPropertyDescriptor(obj, 'foo'),
{value: 123,
writable: true,
enumerable: true,
configurable: true,
; })
そして、プロパティ`obj.bar`の属性は次のように設定します。
const obj = {
foo: 1,
bar: 2,
;
}
.deepEqual(Object.keys(obj), ['foo', 'bar']);
assert
// Hide property `bar` from Object.keys()
Object.defineProperty(obj, 'bar', {
enumerable: false,
;
})
.deepEqual(Object.keys(obj), ['foo']); assert
さらに読む
クイズ
クイズアプリを参照してください。