ホームページ >ウェブフロントエンド >jsチュートリアル >Javascriptオブジェクトの途中のキーを削除する_基礎知識
あなたもこれはできません、家に帰って農作業をしてください
ところで、delete の使い方についてお話しましょう
数週間前、Stoyan Stefanov の著書『オブジェクト指向 JavaScript』をチェックする機会がありました。この本は Amazon で高評価 (レビュー 12 件、星 5 つ) だったので、そのような本なのか知りたいと思っていました。この本は関数に関する章を読み始めました。この本の説明の仕方がとても気に入りました。また、例が非常に素晴らしく進歩的な方法でまとめられているので、一見したところ、初心者でもこれを習得できそうに見えました。しかし、すぐに、この章全体で発生した興味深い誤解に気付きました。関数の削除については、他にもいくつかの間違いがありましたが (関数宣言や関数式など)、ここでは説明しません。
この本の主張は次のとおりです。
「関数は通常の変数のように扱われ、別の変数にコピーしたり、削除したりすることもできます。」 この説明の後に例を示します:
欠落しているセミコロンを無視して、これらのコード行のどこにエラーがあるかわかりますか? 明らかに、エラーは、sum 変数の削除が成功しないこと、および typeof sum が返されないことです。 「未定義」も、JavaScript では変数の削除が不可能なためです。
それでは、この例で何が起こっているのでしょうか? それとも、特別な使用法なのでしょうか? このコードは実際には Firebug コンソールの出力であり、Stoyan はそれをツールとして使用したに違いありません。簡単なテストです。まるで Firebug が他の削除ルールに従っているかのようです。Stoyan が迷走したのは Firebug でした。それでは、何が起こっているのでしょうか?
この質問に答える前に、まず JavaScript での delete 演算子の仕組みを理解する必要があります。何が削除でき、何が削除できないのでしょうか。今日は、この質問について Firebug について詳しく説明します。 「奇妙な」動作を確認し、結局のところ、変数、関数の宣言、プロパティへの値の割り当て、およびそれらの削除の背後にあるものを詳しく見ていきます。ブラウザの互換性といくつかの最も悪名高いバグについても説明します。ES5 の厳密モードと、削除演算子の動作がどのように変わるかについても説明します。
ここでは JavaScript と ECMAScript を同じ意味で使用します。どちらも ECMAScript を意味します (明らかに Mozilla の JavaScript 実装について話している場合を除く)当然のことながら、削除に関する説明は Web 上でほとんどありません。おそらく、MDC の記事が理解するのに最適なリソースです。しかし、残念ながら、この件に関する興味深い詳細がいくつか欠けています。そう、忘れられているものの 1 つが原因です。 Firebug の奇妙な動作については、MSDN リファレンスはこれらの点ではほとんど役に立ちません。
理論
それでは、なぜオブジェクトのプロパティを削除できるのでしょうか:
注: 属性を削除できない場合、削除演算子は false のみを返します
これを理解するには、まず変数インスタンスとプロパティ プロパティに関するこれらの概念を理解する必要があります。これらの概念は、残念ながら JavaScript の本ではほとんど言及されていません。これらの概念については、次の数段落で簡単に説明します。 . これらの概念は理解するのが難しいです。 「なぜこれらのことがそのように機能するのか」に興味がない場合は、この章を飛ばしてください。
コードの種類:
ECMAScript には、グローバル コード、関数コード、および Eval コードという 3 つの異なるタイプの実行可能コードがあります。これらのタイプは、名前がほぼ同じであるため、簡単に説明します。
ソース コードの一部がプログラムとして表示されると、それはグローバル環境で実行され、ブラウザ環境ではグローバル コードとみなされ、通常、スクリプト要素のコンテンツはプログラムとして解釈されるため、グローバルとして実行されます。コード。
関数内で直接実行されるコードは、明らかに関数コードとみなされます。ブラウザでは、通常、イベント属性 (
など) の内容が関数コードに解釈されます。
最後に、組み込み関数 eval に適用されたコード テキストは Eval コードとして解釈されます。この型が特別である理由がすぐにわかります。
実行コンテキスト:
ECMAScript コードが実行されるとき、それは通常、特定の実行コンテキストで発生します。実行コンテキストは、スコープ (Scope) と変数のインスタンス化 (Variable instantiation) がどのように機能するかを理解するのに役立つ、やや抽象的なエンティティの概念です。関数が実行されると、それに対応する実行コンテキストが存在します。グローバル コードが実行されると、プログラム コントロールが実行コンテキストに入ります。グローバルコードなど
ご覧のとおり、実行コンテキストは論理的にスタックを形成できます。まず、独自の実行コンテキストを持つグローバル コードがあり、そのコードが実行コンテキストを引き連れて関数を呼び出すことがあります。この関数は別の関数などを呼び出すことができます。関数が再帰的に呼び出された場合でも、呼び出されるたびに新しい実行コンテキストに入ります。
アクティブ化オブジェクト/変数オブジェクト:各実行コンテキストには、いわゆる変数オブジェクトが関連付けられています。実行コンテキストと同様に、変数オブジェクトは抽象エンティティであり、変数インスタンスを記述するために使用されるメカニズムです。ソース コードは通常、この変数オブジェクトにプロパティとして追加されます。
プログラム制御がグローバル コードの実行コンテキストに入ると、グローバル オブジェクトが変数オブジェクトとして使用されます。これが、グローバルとして宣言された関数変数がグローバル オブジェクトのプロパティになる理由です。
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo // true
typeof GLOBAL_OBJECT.bar // "関数"
GLOBAL_OBJECT.bar === バー; // true
関数コードで宣言された変数と関数だけがアクティブ オブジェクトのプロパティになるわけではありません。これは、各関数パラメータ (対応する仮パラメータの名前に対応) および特別な Arguments オブジェクト (引数付き) でも行われます。アクティブなオブジェクトは内部記述メカニズムであり、プログラム コードからアクセスできないことに注意してください。
変数に何が起こるか (変数がプロパティになる) についてはほぼ理解できました。理解する必要がある唯一の概念は、各プロパティが 0 個以上の属性を持つことができることです。次のセット: ReadOnly、DontEnum、DontDelete、および Internal。これらはフラグとして考えることができます。属性は属性に存在する場合と存在しない場合があります。今日の説明では、DontDelete のみに興味があります。
宣言された変数と関数が変数オブジェクト (または関数コードのアクティブ オブジェクト、またはグローバル コードのグローバル オブジェクト) の属性になると、これらの属性は DontDelete 属性を使用して作成されます。ただし、明示的なプロパティは作成されます。 by (または暗黙的) プロパティの割り当てには DontDelete 属性がありません。これが、一部のプロパティを削除できるのですが、他のプロパティは削除できないのです。
これがすべてです (DontDelete): この属性の特別な特性。この属性を削除できるかどうかを制御するために使用されます。一部の組み込みオブジェクト属性は DontDelete を含むように指定されているため、削除できないことに注意してください。たとえば、特殊な引数変数 (または、現在ではアクティブなオブジェクトのプロパティ) には DontDelete プロパティがあります。関数インスタンスの length プロパティにも DontDelete プロパティがあります。
関数のパラメータに対応する属性も作成時から DontDelete 属性を持っているため、削除できません。
未宣言の割り当て:
宣言されていない代入は、グローバル オブジェクトの前にスコープ チェーン内の他の場所でプロパティが既に見つかっていない限り、グローバル オブジェクトにプロパティを作成することを覚えているかもしれません。これで、プロパティの代入と変数について理解できました。宣言の違いについては説明しました。後者は DontDelete プロパティを設定しますが、前者は設定しません。なぜ未宣言の代入によって削除可能なプロパティが作成されるのかを理解する必要があります。
注意: プロパティはプロパティの作成時に決定され、その後の割り当てでは既存のプロパティのプロパティは変更されません。この違いを理解することが重要です。
Firebug の混乱:
Firebug では何が起こったのでしょうか? コンソールで宣言された変数はなぜ削除できるのでしょうか? これは、以前に学習したことに違反しませんか? 前に述べたように、Eval コードは変数を宣言するときに特殊な動作をします。 Eval で宣言された変数は、実際には DontDelete 属性のないプロパティとして作成されます。
また、関数コード内で呼び出された場合も同様に:
eval('var foo = 1;');
foo; // 1
foo を削除します。 // true
typeof foo; // "未定義"
})();
これは、Firebug の異常な動作の基礎です。コンソール内のすべてのテキストは、グローバル コードや関数コードではなく、Eval コードとして解析され、実行されます。明らかに、ここで宣言されたすべての変数は、最終的に DontDelete 属性のないプロパティになります。すべて簡単に削除できます。グローバル コードと Firebug コンソールの違いを理解する必要があります。
Eval 経由で変数を削除します:
この興味深い eval 動作は、ECMAScript の別の側面と組み合わせることで、技術的には「削除不可能な」属性を削除できる可能性があります。関数宣言の特徴は、同じ実行コンテキスト内で同じ名前の変数をオーバーライドできることです。
関数宣言がどのように優先され、同じ名前の変数 (つまり、変数オブジェクト内の同じプロパティ) をオーバーライドするかに注目してください。これは、関数宣言が変数宣言の後にインスタンス化され、許可されるためです。それらをオーバーライドします (変数宣言)。関数宣言はプロパティの値を置き換えるだけでなく、そのプロパティの属性も置き換えます。eval を介して関数を宣言した場合、その関数はそれを元の属性に置き換える必要があります。また、eval で宣言された変数は DontDelete 属性のないプロパティを作成するため、この新しい関数をインスタンス化すると、実際には既存の DontDelete 属性がプロパティから削除され、A プロパティを削除できるようになります (そして、明らかに、そのプロパティをポイントします)。値を新しく作成した関数に代入します)。
残念ながら、この「チート」は現在の実装では機能しません。おそらく、ここに何かが欠けているか、実装者が気づいていないほど動作が曖昧なのかもしれません。
ブラウザの互換性:
理論的にどのように機能するかを理解することは役に立ちますが、実践することが最も重要です。変数/プロパティの作成/削除に関しては、ブラウザは標準に従っていますか? 答えは次のとおりです。ほとんどの場合、「はい」です。
私は、グローバル コード、関数コード、および Eval コードでのテストを含む、削除演算子とブラウザの互換性をテストするための簡単なテスト セットを作成しました。このテスト セットは、削除演算子の戻り値と属性値を (必要に応じて) チェックしました。 delete の戻り値は、実際の結果ほど重要ではありません。 delete が false ではなく true を返すかどうかはあまり重要ではありません。重要なのは、 DontDelete 属性を持つ属性が削除されないことです。その逆も同様です。
最近のブラウザは一般的に互換性が高く、前述した eval 機能を除き、次のブラウザはすべてのテスト セットに合格しました: Opera 7.54、Firefox 1.0、Safari 3.1.2、Chrome 4。
Safari 2.x および 3.0.4 では、関数パラメータの削除に問題があります。これらのプロパティは DontDelete を使用せずに作成されているようです。そのため、Safari 2.x にはさらに問題があります。非参照型変数の削除 (例: delete. 1) 例外をスローします。関数宣言は削除可能なプロパティを作成します (ただし、不思議なことに、変数宣言は削除できません)。eval の変数宣言は削除できなくなります。
Safari と同様に、Konqueror (4.3 ではなく 3.5) は、非参照型 (delete 1 など) を削除するときに例外をスローし、誤って関数変数を削除可能にしてしまいます。
翻訳者注:
Chrome、Firefox、IE の最新バージョンをテストし、失敗した 23 と 24 を除いて基本的にパスを維持しました。Nokia E72 の内蔵ブラウザに加えて、UC と一部のモバイル ブラウザもテストしました。失敗 15 と 16 を除いて、他のほとんどの内蔵ブラウザはデスクトップ ブラウザと同じ効果がありますが、Blackberry Curve 8310/8900 の内蔵ブラウザがテスト 23 に合格できることには言及する価値があります。これには驚きました。
Gecko DontDelete バグ:
Gecko 1.8.x ブラウザ - Firefox 2.x、Camino 1.x、Seamonkey 1.x など - 非常に興味深いバグがあり、プロパティに明示的に割り当てると、属性が作成されている場合でも DontDelete 属性が削除されます。変数宣言または関数宣言を通じて。
驚くべきことに、Internet Explorer 5.5 ~ 8 は、非参照型の削除 (例: delete 1) が例外をスローすることを除いて、完全なテストに合格します (古い Safari と同様)。しかし、IE ではさらに深刻なバグがあります。それほど明らかではありませんが、これらのバグはグローバル オブジェクトに関連しています。
IE のバグ:
この章全体は Internet Explorer のバグについてです。驚きですね!
IE (少なくとも IE 6 ~ 8) では、次の式は例外をスローします (グローバル コードで実行される場合):
this.x = 1;
delete x; // TypeError: オブジェクトはこのアクションをサポートしていません
これも同様ですが、別の例外をスローするため、事態はより興味深いものになります。
var x = 1;
delete this.x; // TypeError: 'this.x' を削除できません
IE では、グローバル コード内の変数宣言ではグローバル オブジェクトにプロパティが作成されないようです。割り当て (this.x = 1) によってプロパティを作成し、それを delete x によって削除すると、プロパティの作成を宣言するとエラーがスローされます。 (var x = 1) その後、delete this.x で削除すると、別のエラーがスローされます。
しかし、それだけではありません。明示的な割り当てによってプロパティを作成すると、実際には削除時に例外がスローされます。作成されるプロパティには DontDelete 属性があるように見えますが、これはもちろんそうではありません。
this.x = 1;
delete this.x; // TypeError: オブジェクトはこのアクションをサポートしていません
typeof x; // "number" (まだ存在しますが、削除されるべきではありませんでした!)
delete x; // TypeError: オブジェクトはこのアクションをサポートしていません
typeof x; // "数値" (再度削除されませんでした)
ここで、IE では、宣言されていない割り当て (グローバル オブジェクトにプロパティを作成する必要がある) が実際に削除可能なプロパティを作成すると仮定します。
x を削除します。 // true
typeof x; // "未定義"
ただし、グローバル コードのこの参照を通じてこの属性を削除すると (this.x を削除)、同様のエラーがポップアップ表示されます。
delete this.x; // TypeError: 'this.x' を削除できません
この動作を一般化したい場合、delete this.x を使用してグローバル コードから変数を削除すると、問題のプロパティが明示的な割り当て (this.x = 1) によって作成されると、プロパティがエラーをスローするようです。未宣言の代入 (x = 1) または宣言 (var x = 1) によって作成された場合、削除すると別のエラーがスローされます。
一方、
私は 9 月にこの問題について再度考え、Garrett Smith が IE では次のように提案しました。
「グローバル変数オブジェクトは JScript オブジェクトとして実装され、グローバル オブジェクトはホストによって実装されます。」
ギャレットは、エリック・リッパートのブログエントリーを参考として使用しました。
いくつかのテストを実装することで、この理論を多かれ少なかれ確認できます。this と window は同じオブジェクト (=== 演算子を信頼できる場合) を指しているように見えますが、変数オブジェクト (関数宣言が指定されているオブジェクト) を指していることに注意してください。ある) は、this が指すものとは異なります。
this.getBase() === this.getBase() // true
window.getBase() === this.getBase() // true
window.getBase() === getBase() // false
誤解:
なぜ物事がそのように機能するのかを理解することの美しさは、ウェブ上で削除演算子に関するいくつかの誤解を見てきました。たとえば、Stackoverflow でのこの回答 (驚くほど高い評価を得ています) は、
「ターゲット オペランドがオブジェクト プロパティではない場合、削除は何も行われません。」
削除操作の動作の中核を理解したので、この回答の間違いは明らかになります。削除は変数とプロパティを区別せず(実際、削除の場合、両方とも参照型です)、実際にはDontDeleteのみを気にします。属性 (および属性自体が存在するかどうか)。
さまざまな誤解が互いに反論されているのを見るのは非常に興味深いものです。同じスレッドで、ある人は最初に変数を削除するだけを提案しました (評価で宣言されない限り効果はありません)、別の人はバグ修正を提供しましたdelete を使用してグローバル コードで変数を削除できるが、関数コードでは使用できないことを説明します。
インターネット上での JavaScript の解釈には細心の注意を払ってください。理想的なアプローチは、問題の性質を常に理解することです ;)。
削除とホストオブジェクト:削除のアルゴリズムはおおよそ次のとおりです:
オペランドが参照型でない場合、true を返します
オブジェクトにこの名前の直接プロパティがない場合は、true を返します (ご存知のとおり、オブジェクトはアクティブ オブジェクトまたはグローバル オブジェクトにすることができます)
属性が存在するが DontDelete 属性がある場合は、false
を返します。
それ以外の場合は、属性を削除して true を返します
ただし、ホスト オブジェクトに対する削除演算子の動作は予測不可能であり、実際にはこの動作に問題はありません。(標準に従って) ホスト オブジェクトは読み取り (内部 [[Get]])、書き込みなどの関数を実行できます。 (内部 [[Put]] メソッド) および delete (内部 [[Delete]] メソッド) いくつかの演算子は任意の動作を実装します。この猶予により、ホスト オブジェクトが混乱の原因となります。
typeof window.alert; // "関数"
この話の教訓は、ホスト オブジェクトを決して信頼しないでください。
ES5 厳密モード:
では、厳密モードの ECMAScript 5 では、delete 演算子の式が変数、関数パラメータ、または関数識別子への直接参照である場合に、構文エラーがほとんど発生しません。 、プロパティに内部属性 [[Configurable]] == false がある場合、型エラーがスローされます。
また、宣言されていない変数 (または未解決の参照) を削除すると、構文エラーがスローされます。
「厳密に使用する」;
delete i_dont_exist; // 構文エラー
未宣言の代入は、厳密モードの未宣言の変数と同様に動作します (ただし、今回は構文エラーではなく参照エラーが発生します):
「厳密に使用する」;
i_dont_exist = 1 // 参照エラー
;
もうお分かりいただけると思いますが、変数、関数宣言、パラメータの削除は非常に多くの混乱を引き起こすため、すべての制限は多かれ少なかれ意味があります。厳密モードでは削除を黙って無視するのではなく、より積極的でより記述的な手段が講じられます。
概要:
このブログ投稿は非常に長くなったので、delete による配列オブジェクトの削除やその意味などについては説明しません。専用の説明については MDC の記事を参照してください (または標準を読んでください)。独自の実験を行ってください)。
JavaScript での削除の仕組みを簡単にまとめます。
変数と関数の宣言は、アクティブ オブジェクトまたはグローバル オブジェクトのプロパティです
属性にはいくつかの特性があり、その中の DontDelete は属性を削除できるかどうかを決定する特性です
。
グローバル コードまたは関数コード内の変数および関数の宣言は、常に DontDelete 属性を持つプロパティを作成します。
関数パラメータは常にアクティブなオブジェクトのプロパティであり、DontDelete を持ちます。
Eval コードで宣言された変数と関数は、常に DontDelete プロパティなしで作成されます。
新しいプロパティには、作成時に属性がありません (もちろん、DontDelete もありません)。
ホスト オブジェクトは、削除操作にどのように反応するかを決定できます。
ここで説明されている内容をさらに詳しく知りたい場合は、ECMA-262 第 3 版の仕様を参照してください。
この記事を楽しんでいただき、何か新しいことを学んでいただければ幸いです。ご質問、ご提案、修正は大歓迎です。