このセクションの最初の章で、プロトタイプを設定する最新の方法があることを述べました。
obj.__proto__
を使用してプロトタイプを設定または読み取ることは、時代遅れであり、やや非推奨(JavaScript標準のいわゆる「Annex B」に移動され、ブラウザ専用)とみなされています。
プロトタイプを取得/設定する最新の方法は次のとおりです
- Object.getPrototypeOf(obj) –
obj
の[[Prototype]]
を返します。 - Object.setPrototypeOf(obj, proto) –
obj
の[[Prototype]]
をproto
に設定します。
非推奨とされていない __proto__
の唯一の使い方は、新しいオブジェクトを作成するときのプロパティとして使用する { __proto__: ... }
です。
ただし、これにも特別なメソッドがあります
- Object.create(proto[, descriptors]) – 指定された
proto
を[[Prototype]]
として持つ空のオブジェクトと、オプションのプロパティ記述子を作成します。
例:
let animal = {
eats: true
};
// create a new object with animal as a prototype
let rabbit = Object.create(animal); // same as {__proto__: animal}
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // true
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
Object.create
メソッドは、オプションの第2引数としてプロパティ記述子を持つため、少し強力です。
次のように、新しいオブジェクトに追加のプロパティを提供できます
let animal = {
eats: true
};
let rabbit = Object.create(animal, {
jumps: {
value: true
}
});
alert(rabbit.jumps); // true
記述子は、プロパティフラグと記述子の章で説明されているものと同じ形式です。
Object.create
を使用して、for..in
でプロパティをコピーするよりも強力なオブジェクトクローンを作成できます。
let clone = Object.create(
Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)
);
この呼び出しは、列挙可能および列挙不可能なすべてのプロパティ、データプロパティとセッター/ゲッターを含む、すべてを、正しい [[Prototype]]
で、obj
の完全に正確なコピーを作成します。
簡単な歴史
[[Prototype]]
を管理する方法はたくさんあります。これはどうして起きたのでしょうか?なぜ?
これは歴史的な理由によるものです。
プロトタイプ継承は言語の初期から存在していましたが、それを管理する方法は時間の経過とともに進化しました。
- コンストラクタ関数の
prototype
プロパティは、非常に古い時代から機能しています。これは、指定されたプロトタイプを持つオブジェクトを作成する最も古い方法です。 - その後、2012年に
Object.create
が標準に登場しました。これにより、指定されたプロトタイプを持つオブジェクトを作成する機能が提供されましたが、それを取得/設定する機能は提供されませんでした。一部のブラウザは、ユーザーがいつでもプロトタイプを取得/設定できるように、開発者に柔軟性を提供するために、非標準の__proto__
アクセサーを実装しました。 - その後、2015年に、
__proto__
と同じ機能を実行するために、Object.setPrototypeOf
とObject.getPrototypeOf
が標準に追加されました。__proto__
は事実上どこにでも実装されていたため、非推奨となり、標準の Annex B に組み込まれました。つまり、ブラウザ以外の環境ではオプションです。 - その後、2022年に、オブジェクトリテラル
{...}
で__proto__
を使用することが正式に許可されました(Annex B から移動)。ただし、getter/setterobj.__proto__
としては使用できません(まだ Annex B にあります)。
なぜ __proto__
は関数 getPrototypeOf/setPrototypeOf
に置き換えられたのでしょうか?
なぜ __proto__
は部分的に回復し、{...}
での使用が許可されたのでしょうか。ただし、getter/setter としては許可されないのでしょうか?
それは興味深い質問であり、__proto__
がなぜ悪いのかを理解する必要があります。
すぐに答えを得るでしょう。
[[Prototype]]
を変更しないでください技術的には、いつでも [[Prototype]]
を取得/設定できます。ただし、通常はオブジェクト作成時に一度だけ設定し、それ以上変更しません。rabbit
は animal
から継承し、それは変更されません。
そして、JavaScriptエンジンはこれに対して高度に最適化されています。Object.setPrototypeOf
または obj.__proto__=
を使用して「オンザフライ」でプロトタイプを変更すると、オブジェクトプロパティアクセス操作の内部最適化が壊れるため、非常に遅い操作になります。そのため、何をしているのかを理解しているか、JavaScriptの速度がまったく問題にならない場合を除いて、避けてください。
「非常にシンプルな」オブジェクト
ご存知のように、オブジェクトはキー/値のペアを格納する連想配列として使用できます。
…ただし、ユーザーが提供する キーを格納しようとすると(たとえば、ユーザーが入力した辞書)、興味深い不具合が発生する可能性があります。"__proto__"
を除くすべてのキーは正常に機能します。
例をご覧ください
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // [object Object], not "some value"!
ここで、ユーザーが __proto__
を入力すると、4行目の割り当ては無視されます!
それは開発者でない人にとっては確かに驚くべきことですが、私たちにとっては非常に理解できます。__proto__
プロパティは特別です。オブジェクトまたは null
のいずれかである必要があります。文字列はプロトタイプになることはできません。そのため、文字列を __proto__
に割り当てると無視されます。
しかし、私たちはそのような動作を実装するつもりはなかったはずです。キー/値のペアを格納したいだけであり、"__proto__"
という名前のキーは適切に保存されませんでした。つまり、これはバグです!
ここでは、結果はそれほどひどくありません。しかし、他のケースでは、obj
に文字列の代わりにオブジェクトを格納することがあり、その場合、プロトタイプが実際に変更されます。その結果、実行がまったく予期しない方法で間違ってしまうことになります。
さらに悪いことに、通常、開発者はそのような可能性をまったく考えません。そのため、このようなバグは気づきにくく、特にJavaScriptがサーバー側で使用されている場合、脆弱性につながる可能性もあります。
obj.toString
に割り当てる際にも予期しないことが起こる可能性があります。これは組み込みのオブジェクトメソッドであるためです。
この問題を回避するにはどうすればよいでしょうか?
まず、プレーンなオブジェクトの代わりにストレージに Map
を使用するように切り替えるだけで、すべて問題ありません
let map = new Map();
let key = prompt("What's the key?", "__proto__");
map.set(key, "some value");
alert(map.get(key)); // "some value" (as intended)
…しかし、Object
構文はより簡潔であるため、より魅力的であることがよくあります。
幸いなことに、言語作成者がずっと前にその問題について考えていたため、オブジェクトを使用することができます。
ご存知のように、__proto__
はオブジェクトのプロパティではなく、Object.prototype
のアクセサープロパティです
そのため、obj.__proto__
が読み取られたり設定されたりすると、対応する getter/setter がそのプロトタイプから呼び出され、[[Prototype]]
を取得/設定します。
このチュートリアルセクションの冒頭で述べたように、__proto__
は [[Prototype]]
にアクセスする方法であり、[[Prototype]]
自体ではありません。
ここで、連想配列としてオブジェクトを使用し、そのような問題から解放されたい場合は、ちょっとしたトリックを使ってそれを行うことができます
let obj = Object.create(null);
// or: obj = { __proto__: null }
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // "some value"
Object.create(null)
は、プロトタイプを持たない空のオブジェクトを作成します([[Prototype]]
は null
です)
したがって、__proto__
の継承された getter/setter はありません。これで、通常のデータプロパティとして処理されるため、上記の例は正しく動作します。
このようなオブジェクトは、通常のプレーンなオブジェクト {...}
よりもさらにシンプルであるため、「非常にシンプル」または「純粋な辞書」オブジェクトと呼ぶことができます。
欠点は、このようなオブジェクトには、toString
などの組み込みのオブジェクトメソッドがないことです。
let obj = Object.create(null);
alert(obj); // Error (no toString)
…しかし、それは通常、連想配列には問題ありません。
ほとんどのオブジェクト関連メソッドは Object.keys(obj)
のような Object.something(...)
であることに注意してください。それらはプロトタイプにはないため、このようなオブジェクトでも引き続き機能します
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "你好";
chineseDictionary.bye = "再见";
alert(Object.keys(chineseDictionary)); // hello,bye
まとめ
-
指定されたプロトタイプを持つオブジェクトを作成するには、次を使用します
- リテラル構文:
{ __proto__: ... }
、複数のプロパティを指定できます - または Object.create(proto[, descriptors])、プロパティ記述子を指定できます。
Object.create
は、すべての記述子を持つオブジェクトをシャローコピーする簡単な方法を提供しますlet clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
- リテラル構文:
-
プロトタイプを取得/設定する最新の方法は次のとおりです
- Object.getPrototypeOf(obj) –
obj
の[[Prototype]]
を返します(__proto__
ゲッターと同じ)。 - Object.setPrototypeOf(obj, proto) –
obj
の[[Prototype]]
をproto
に設定します(__proto__
セッターと同じ)。
- Object.getPrototypeOf(obj) –
-
組み込みの
__proto__
ゲッター/セッターを使用してプロトタイプを取得/設定することは推奨されていません。現在、仕様の Annex B にあります。 -
Object.create(null)
または{__proto__: null}
で作成されたプロトタイプのないオブジェクトについても説明しました。これらのオブジェクトは、任意の(おそらくユーザーが生成した)キーを格納するための辞書として使用されます。
通常、オブジェクトは
Object.prototype
から組み込みメソッドと__proto__
ゲッター/セッターを継承し、対応するキーを「占有」し、潜在的に副作用を引き起こします。null
プロトタイプを使用すると、オブジェクトは真に空になります。
コメント
<code>
タグを使用してください。複数行の場合は、<pre>
タグで囲んでください。10行を超える場合は、サンドボックス (plnkr, jsbin, codepen…) を使用してください。