このセクションの最初の章で、プロトタイプを設定する最新の方法があることを述べました。
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…) を使用してください。