このセクションの最初の章では、プロトタイプをセットアップするための最新の方法があると述べました。
obj.__proto__
を使用したプロトタイプの設定または読み取りは、時代遅れであり、ある程度非推奨であると考えられています (ブラウザーのみを対象とした、JavaScript 標準のいわゆる「付録 B」に移行されました)。
プロトタイプを取得/設定する最新の方法は次のとおりです。
Object.getPrototypeOf(obj) – obj
の[[Prototype]]
を返します。
Object.setPrototypeOf(obj, proto) – obj
の[[Prototype]]
をproto
に設定します。
__proto__
の唯一の使用法は、眉をひそめられていませんが、新しいオブジェクトを作成するときのプロパティとして使用されます: { __proto__: ... }
。
ただし、これには特別な方法もあります。
Object.create(proto[, descriptors]) – [[Prototype]]
として指定されたproto
とオプションのプロパティ記述子を使用して空のオブジェクトを作成します。
例えば:
動物 = { にしてみましょう 食べる:本当 }; // 動物をプロトタイプとして新しいオブジェクトを作成します let Rabbit = Object.create(animal); // {__proto__:animal} と同じ アラート(rabbit.eats); // 真実 alert(Object.getPrototypeOf(ウサギ) === 動物); // 真実 Object.setPrototypeOf(ウサギ, {}); // ウサギのプロトタイプを {} に変更します
Object.create
メソッドは、オプションの 2 番目の引数であるプロパティ記述子を持っているため、もう少し強力です。
次のように、そこで新しいオブジェクトに追加のプロパティを提供できます。
動物 = { にしてみましょう 食べる:本当 }; let Rabbit = Object.create(animal, { ジャンプ: { 値: true } }); アラート(rabbit.jumps); // 真実
記述子の形式は、「プロパティ フラグと記述子」の章で説明されているものと同じです。
Object.create
を使用すると、 for..in
でプロパティをコピーするよりも強力なオブジェクトのクローン作成を実行できます。
let clone = Object.create( Object.getPrototypeOf(obj)、Object.getOwnPropertyDescriptors(obj) );
この呼び出しは、すべてのプロパティ (列挙可能および非列挙可能、データ プロパティ、セッター/ゲッターなど) を含む、 obj
の真に正確なコピーを、適切な[[Prototype]]
を使用して作成します。
[[Prototype]]
を管理する方法はたくさんあります。どうしてそうなったのですか?なぜ?
それは歴史的な理由によるものです。
プロトタイプの継承は黎明期から言語に存在していましたが、それを管理する方法は時間の経過とともに進化しました。
コンストラクター関数のprototype
プロパティは、非常に古くから機能しています。これは、指定されたプロトタイプを使用してオブジェクトを作成する最も古い方法です。
その後、2012 年にObject.create
標準に登場しました。指定されたプロトタイプを使用してオブジェクトを作成する機能は提供されましたが、それを取得/設定する機能は提供されませんでした。一部のブラウザーは、開発者に柔軟性を与えるために、ユーザーがいつでもプロトタイプを取得/設定できる非標準の__proto__
アクセサーを実装しました。
その後、2015 年に、 __proto__
と同じ機能を実行するために、 Object.setPrototypeOf
とObject.getPrototypeOf
が標準に追加されました。 __proto__
事実上あらゆる場所に実装されていたため、一種の非推奨となり、標準の付録 B、つまり非ブラウザ環境のオプションに移行しました。
その後、2022 年に、オブジェクト リテラル{...}
(付録 B から移動) で__proto__
使用することが正式に許可されましたが、ゲッター/セッターobj.__proto__
としては使用できませんでした (付録 B にまだあります)。
__proto__
関数getPrototypeOf/setPrototypeOf
に置き換えられたのはなぜですか?
__proto__
部分的に修復され、 {...}
での使用が許可されたのに、ゲッター/セッターとしては許可されなかったのはなぜですか?
これは興味深い質問であり、 __proto__
がなぜ悪いのかを理解する必要があります。
そしてすぐに答えが得られます。
速度が重要な場合は、既存のオブジェクトの[[Prototype]]
を変更しないでください
技術的には、 [[Prototype]]
いつでも取得/設定できます。しかし、通常はオブジェクトの作成時に一度設定するだけで、それ以降は変更しません。 rabbit
animal
から継承しており、それは変更されません。
また、JavaScript エンジンはこのために高度に最適化されています。 Object.setPrototypeOf
またはobj.__proto__=
を使用してプロトタイプを「オンザフライ」で変更すると、オブジェクト プロパティ アクセス操作の内部最適化が中断されるため、非常に時間がかかります。したがって、自分が何をしているのかよくわかっていない場合、または JavaScript の速度がまったく問題にならない場合を除き、これは避けてください。
ご存知のとおり、オブジェクトはキーと値のペアを格納するための連想配列として使用できます。
…しかし、ユーザーが提供したキー (ユーザーが入力した辞書など) を保存しようとすると、興味深い不具合が見られます。 "__proto__"
を除くすべてのキーが正常に動作します。
例を確認してください。
obj = {} にします。 let key = プロンプト("キーは何ですか?", "__proto__"); obj[key] = "何らかの値"; アラート(obj[キー]); // [オブジェクト Object]、「何らかの値」ではありません!
ここで、ユーザーが__proto__
と入力すると、4 行目の割り当ては無視されます。
これは開発者以外の人にとっては確かに驚くべきことかもしれませんが、私たちにとっては非常に理解できます。 __proto__
プロパティは特別です。オブジェクトまたはnull
いずれかでなければなりません。文字列をプロトタイプにすることはできません。そのため、 __proto__
への文字列の代入は無視されます。
しかし、そのような動作を実装するつもりはありませんでした。キーと値のペアを保存したいのですが、 "__proto__"
という名前のキーが適切に保存されませんでした。それはバグです!
ここでの結果はひどいものではありません。ただし、他の場合には、 obj
に文字列の代わりにオブジェクトを格納する場合があり、その場合はプロトタイプが実際に変更されます。その結果、実行はまったく予期しない形で失敗することになります。
さらに悪いことに、通常、開発者はそのような可能性についてまったく考えません。そのため、特に JavaScript がサーバー側で使用されている場合、そのようなバグは気づきにくくなり、脆弱性になることさえあります。
obj.toString
に代入するときに予期しない事態が発生する可能性もあります。
どうすればこの問題を回避できるでしょうか?
まず、プレーン オブジェクトの代わりにストレージとしてMap
使用するように切り替えるだけで、すべて問題なく実行できます。
let Map = new Map(); let key = プロンプト("キーは何ですか?", "__proto__"); map.set(key, "何らかの値"); アラート(マップ.get(キー)); // 「何らかの値」 (意図したとおり)
…しかし、 Object
構文はより簡潔であるため、多くの場合、より魅力的です。
幸いなことに、言語の作成者はずっと前にその問題について考えていたため、私たちはオブジェクトを使用することができます。
ご存知のとおり、 __proto__
はオブジェクトのプロパティではなく、 Object.prototype
のアクセサー プロパティです。
したがって、 obj.__proto__
が読み取られるか設定されると、対応する getter/setter がそのプロトタイプから呼び出され、 [[Prototype]]
を取得/設定します。
このチュートリアル セクションの冒頭で述べたように、 __proto__
[[Prototype]]
にアクセスする方法であり、 [[Prototype]]
自体ではありません。
さて、オブジェクトを連想配列として使用し、そのような問題を回避したい場合は、ちょっとしたトリックを使用することでそれを行うことができます。
obj = Object.create(null); とします。 // または: obj = { __proto__: null } let key = プロンプト("キーは何ですか?", "__proto__"); obj[key] = "何らかの値"; アラート(obj[キー]); // 「何らかの値」
Object.create(null)
プロトタイプなしで空のオブジェクトを作成します ( [[Prototype]]
はnull
です)。
したがって、 __proto__
には継承された getter/setter はありません。現在は通常のデータ プロパティとして処理されるため、上記の例は正しく機能します。
このようなオブジェクトは、通常のプレーン オブジェクト{...}
よりもさらに単純であるため、「非常にプレーンな」オブジェクトまたは「純粋な辞書」オブジェクトと呼ぶことができます。
欠点は、そのようなオブジェクトには組み込みオブジェクト メソッド ( toString
など) がないことです。
obj = Object.create(null); とします。 アラート(obj); // エラー (toString なし)
…しかし、連想配列の場合は通常これで問題ありません。
ほとんどのオブジェクト関連のメソッドはObject.keys(obj)
のようなObject.something(...)
であることに注意してください。これらはプロトタイプにないため、そのようなオブジェクトに対して引き続き動作します。
let chineseDictionary = Object.create(null); chineseDictionary.hello = "你好"; chineseDictionary.bye = "再见"; アラート(Object.keys(chineseDictionary)); // こんにちは、さようなら
指定されたプロトタイプを使用してオブジェクトを作成するには、次を使用します。
Object.create
すべての記述子を含むオブジェクトをシャローコピーする簡単な方法を提供します。
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
リテラル構文: { __proto__: ... }
、複数のプロパティを指定できます
または Object.create(proto[, descriptors]) を使用すると、プロパティ記述子を指定できます。
プロトタイプを取得/設定する最新の方法は次のとおりです。
Object.getPrototypeOf(obj) – obj
の[[Prototype]]
を返します ( __proto__
getter と同じ)。
Object.setPrototypeOf(obj, proto) – obj
の[[Prototype]]
をproto
に設定します ( __proto__
setter と同じ)。
組み込みの__proto__
getter/setter を使用してプロトタイプを取得/設定することは推奨されません。これは現在仕様の付録 B に含まれています。
Object.create(null)
または{__proto__: null}
で作成されたプロトタイプのないオブジェクトについても説明しました。
これらのオブジェクトは、(おそらくユーザーが生成した) キーを保存するための辞書として使用されます。
通常、オブジェクトは組み込みメソッドと__proto__
getter/setter をObject.prototype
から継承するため、対応するキーが「占有」され、潜在的に副作用が発生します。 null
プロトタイプでは、オブジェクトは本当に空です。
重要度: 5
key/value
ペアを保存するために、 Object.create(null)
として作成されたオブジェクトdictionary
があります。
そこにメソッドdictionary.toString()
を追加します。これにより、キーのカンマ区切りのリストが返されます。 toString
オブジェクト上のfor..in
に表示されるべきではありません。
これがどのように機能するかは次のとおりです。
let Dictionary = Object.create(null); // Dictionary.toString メソッドを追加するコード // データを追加します Dictionary.apple = "アップル"; Dictionary.__proto__ = "テスト"; // ここでは __proto__ は通常のプロパティ キーです // apple と __proto__ のみがループ内にあります for(辞書にキーを入れます) { アラート(キー); // 「リンゴ」、次に「__proto__」 } // toString が動作中 アラート(辞書); // "リンゴ,__proto__"
このメソッドは、 Object.keys
を使用してすべての列挙可能なキーを取得し、そのリストを出力できます。
toString
列挙不可能にするには、プロパティ記述子を使用して定義しましょう。 Object.create
の構文を使用すると、プロパティ記述子を 2 番目の引数としてオブジェクトに提供できます。
let Dictionary = Object.create(null, { toString: { // toString プロパティを定義します value() { // 値は関数です return Object.keys(this).join(); } } }); Dictionary.apple = "アップル"; Dictionary.__proto__ = "テスト"; // apple と __proto__ がループ内にあります for(辞書にキーを入れます) { アラート(キー); // 「リンゴ」、次に「__proto__」 } // toString によるプロパティのカンマ区切りリスト アラート(辞書); // "リンゴ,__proto__"
記述子を使用してプロパティを作成する場合、そのフラグはデフォルトでfalse
になります。したがって、上記のコードでは、 dictionary.toString
は列挙可能ではありません。
確認するには、「プロパティ フラグと記述子」の章を参照してください。
重要度: 5
新しいrabbit
オブジェクトを作成しましょう。
関数 ウサギ(名前) { this.name = 名前; } Rabbit.prototype.sayHi = function() { アラート(この名前); }; let Rabbit = new Rabbit("ウサギ");
これらの呼び出しは同じことを行うのでしょうか?
うさぎ.sayHi(); Rabbit.prototype.sayHi(); Object.getPrototypeOf(ウサギ).sayHi(); Rabbit.__proto__.sayHi();
最初の呼び出しにはthis == rabbit
があり、他の呼び出しには、 this
Rabbit.prototype
と等しくなります。これは、実際にはドットの前のオブジェクトであるためです。
したがって、最初の呼び出しのみRabbit
が表示され、他の呼び出しはundefined
表示します。
関数 ウサギ(名前) { this.name = 名前; } Rabbit.prototype.sayHi = function() { アラート( this.name ); } let Rabbit = new Rabbit("ウサギ"); うさぎ.sayHi(); // うさぎ Rabbit.prototype.sayHi(); // 未定義 Object.getPrototypeOf(ウサギ).sayHi(); // 未定義 Rabbit.__proto__.sayHi(); // 未定義