[問題780] JS:ES6`クラス `を理解していません

画像

序文

久しぶりです。JSシリーズがわからないのでまたお会いしましょう。今日の記事はこれとオブジェクトのプロトタイプの最後の記事です。フロントエンドの朝の読書コラムの著者である@JoeHetfieldがもたらした翻訳と共有を引き続き共有します。


ここから始まります〜


この本の後半(第4章から第6章)に重要なメッセージがある場合、クラスはコードのオプションの設計パターンであり(不要)、JavaScriptなどの[[プロトタイプ]]言語で使用されているということです。それを実装することは常に厄介です。


この恥ずかしさの大部分は文法に関するものですが、これに限定されるものではありません。4第5章では、コードを台無しにする面倒な.prototype参照から、明示的な仮想多型まで、多くの醜い構文を調べます。チェーンのさまざまなレベルでメソッドに同じ名前を付けて、達成しようとする場合メソッドから高レベルのメソッドへの多態的な参照。.constructorは、「XXによって構築された」と誤って解釈され、信頼性の低い定義と別の醜い構文になりました。


しかし、クラスの設計に関する質問ははるかに深いものです。第4章では、従来のクラス指向言語では、クラスには実際には親クラスから子クラスへ、および子クラスからインスタンスへのコピーアクションがあることを指摘しています。[[プロトタイプ]]では、アクションはコピーではなく、その逆です。 -コミッションリンク。


OLOOのスタイルと動作の委任は、[[Prototype]]を非表示にする代わりに受け入れます。それらの単純さを比較すると、JSのクラスの問題が強調されています。


クラス

これらの問題について再度議論する必要はありません。ES6クラスのメカニズムに注意を向けられるように、これらの質問を繰り返して、心の中で新鮮に保つためにここにいます。ここでは、それがどのように機能するかを示し、クラスがこれらの「クラス」の問題のいずれかを実際に解決するかどうかを確認します。


第6章のウィジェット/ボタンの例をもう一度見てみましょう。

image.png

構文的に見栄えがする以外に、ES6は他に何を解決しますか?


コードを台無しにするために.prototypeへの参照が(ある意味で読み続けてください!)なくなりました。

ボタンは、.prototypeによってリンクされたオブジェクトをObject.create(..)に置き換えるか、__ proto__とObject.setPrototypeOf(..)を使用して設定するのではなく、ウィジェットから直接「継承」(つまり、拡張)として宣言されます。それ。


super(..)を使用すると、比較的多態的になる非常に便利な機能が得られるため、チェーンの特定のレベルのメソッドは、チェーンの上位レベルの同じ名前のメソッドを参照できます。第4章では、コンストラクターについて奇妙な現象があります。コンストラクターはクラスに属していないため、クラスとは関係がありません。super(..)には、この問題の解決策が含まれています-super()は、コンストラクター内で期待どおりに機能します。

クラスリテラル構文は、属性を指定するためのインスピレーションをあまり与えません(メソッドのみ)。これは何かを制限しているように見えますが、ほとんどの場合、属性(状態)がチェーンの最後の「インスタンス」の外側に存在すると予想されます。これは通常、間違いであり、驚くべきことです(この状態は暗黙的に存在するため)すべての「インスタンス」で「共有」)。したがって、クラス構文は間違いを防ぐと言うこともできます。


extendsは、ArrayやRegExpなどの組み込みオブジェクト(サブ)タイプを非常に自然な方法で拡張することもできます。クラス..extendsなしでこれを行うことは、常に非常に複雑で苛立たしい作業であり、最も熟練したフレームワークの作成者だけがこの問題を正しく解決しました。さて、それは一片のケーキです!


正直なところ、最も明白な(構文上の)問題、および古典的なプロトタイプスタイルのコードの驚くべき場所については、これらは確かに実質的な解決策です。


クラスピット

ただし、それがすべての利点というわけではありません。JSのデザインパターンとして「クラス」を使用することには、まだいくつかの深くて非常に厄介な問題があります。


まず第一に、クラス構文は、JSがES6に新しい「クラス」メカニズムを持っていることをあなたに納得させるかもしれません。しかし、そうではありません。クラスは主に、既存の[[プロトタイプ]](委任)メカニズムの単なる構文上の砂糖です!


これは、従来のクラス指向言語のように、宣言時にクラスが実際に静的にコピーされないことを意味します。「親クラス」のメソッドを(意図的または意図せずに)変更/置換した場合、子の「クラス」および/またはインスタンスは、宣言時にコピーを取得しなかったため、「影響を受ける」ことになります。 [[Prototype]]に基づくリアルタイム委任モデルを使用します。

画像


この動作は、「実際のクラス」からコピーすることを期待するのではなく、デリゲートの性質をすでに知っている場合にのみ合理的と思われます。自分自身に問いかけなければならないのは、なぜクラスとは根本的に異なるものにクラス構文を選択するのかということです。


ES6のクラス構文により、従来のクラスとデリゲートオブジェクトの違いを観察および理解することがより困難になりませんか?


クラス構文は、クラスの属性メンバーを宣言する方法を提供しません(メソッドの場合のみ)。したがって、オブジェクト間で共有されている状態を追跡する必要がある場合は、次のような醜い.prototype構文に戻ることになります。

画像


ここでの最大の問題は、.prototypeを実装の詳細として公開するため(リーク!)、クラス構文の本来の意図を裏切ることです。


さらに、まだ驚くべき罠に直面しています。this.count++は、共有状態を更新する代わりに、2つのオブジェクトc1とc2に個別の隠蔽プロパティ.countを暗黙的に作成します。このクラスは、(おそらく)構文サポートがないことを除いて、この問題について私たちに何の慰めも与えません。


さらに、意図しないカバーは依然として災害です。

image.png

スーパーがどのように機能するかについて、いくつかの微妙な質問もあります。superはこれと同様の方法でバインドされると考えるかもしれません(第2章)。つまり、superは常に[[Prototype]]チェーン内の現在のメソッドの位置にバインドされます。より高いレベル。


ただし、パフォーマンスの問題があるため(このバインディングはすでに非常にパフォーマンスを消費します)、superは動的にバインドされません。宣言されると、いくらか「静的に」バインドされます。大したことじゃないですよね?


うーん...そうかもしれないし、そうでないかもしれない。ほとんどのJS開発者のように、さまざまな方法で(クラス定義から)さまざまなオブジェクトに関数を割り当て始めると、これらすべての場合に、基盤となるスーパーメカニズムが機能することに気付かない可能性があります。毎回再バインドする必要がありました。


また、各割り当てに使用する構文によっては、superを正しくバインドできない場合があります(少なくとも期待どおりではありません)。そのため、(執筆時点で、TC39が議論しています)この質問)は、toMethod(..)を使用してsuperを手動でバインドする必要があります(これをbind(..)でバインドする必要があるのと少し似ています-第2章を参照してください)。


以前は、メソッドをさまざまなオブジェクトに割り当てて、暗黙のバインディングルールを通じてこのダイナミクスを自動的に利用していました(第2章を参照)。しかし、スーパーを使用する方法では、同じことが起こらない可能性があります。


ここでスーパーがどのように動作するかを検討してください(DおよびEの場合):

画像


(かなり合理的に!)呼び出されたときにsuperが自動的にバインドされると思う場合、super()はEがDに委任することを自動的に認識すると予想されるため、super()を使用するE.foo()を呼び出す必要があります。 D.foo()。


こんな感じじゃない。実用性のパフォーマンス上の理由から、superはこのようにバインディング(つまり、動的バインディング)を遅らせません。代わりに、呼び出されたときに[[HomeObject]]。[[Prototype]]から派生し、宣言されたときに[[HomeObject]]は実際には静的にバインドされます。


この特定の例では、メソッド[[HomeObject]]はまだCであり、C。[[Prototype]]はPであるため、super()は引き続きP.foo()に解決されます。


このようなトラップを手動で解決する方法があるかもしれません。このシナリオでは、toMethod(..)を使用してメソッド[[HomeObject]]をバインド/再バインドします(オブジェクト[[Prototype]]を一緒に設定します!)。

画像

注:toMethod()はこのメソッドのクローンを作成し、最初のパラメーターをhomeObjectとして受け取り(そのためEを渡します)、2番目のパラメーター(オプション)を使用して新しいメソッドの名前を設定します(「foo」は保持しないでください)。変化する)。


このシナリオに加えて、開発者を罠にかける他の極端な状況があるかどうかはまだわかりません。いずれにせよ、目を覚まし続ける必要があります。エンジンが自動的にスーパーを決定し、手動で処理する必要があります。ああ!


静的は動的よりも優れていますか?

しかし、ES6の最大の問題は、これらすべてのトラップが、クラスの種類があなたを文法に導くことを意味することです。これは、クラスを宣言すると、それが何かの静的な定義であることを意味するようです(従来のクラスのように) (将来的にインスタンス化されます)。Cがオブジェクトであり、直接対話できる具体的なものであるという事実を完全に忘れています。


従来のクラス指向言語では、後でクラスの定義を調整することはないため、クラス設計パターンはそのような機能を提供しません。しかし、JSの最も強力な部分の1つは、動的であり、オブジェクトの定義は(不変にしない限り)固定および可変ではないものであるということです。


クラスは、.prototype構文の使用を強制したり、superの落とし穴を考慮したりすることによって、そのようなことを行うべきではないことを暗示しているようです。そして、この動的メカニズムがもたらす可能性のあるすべての落とし穴をほとんどサポートしていません。


言い換えれば、クラスは次のように言っているようです。「動的が悪すぎるので、これは良い考えではないかもしれません。静的構文のように見えるものを静的にエンコードします。」


JavaScriptについてのなんて悲しいコメント:動的は難しすぎるので、静的であるふりをしましょう(実際にはそうではありません!)。


これが、ES6のクラスが文法的な頭痛の種の解決策のふりをする理由ですが、実際には水がより濁っており、JSの明確で簡潔な理解を形成するのが困難です。


注:.bind(..)ツールを使用してハードバウンド関数を作成する場合(第2章を参照)、この関数を通常の関数のようにES6拡張で拡張することはできません。


レビュー

classは、JSのクラス/継承の設計パターンの問題を修正するふりをします。しかし、彼は実際には反対のことをしました。それは多くの問題を隠し、他の微妙で危険なことをもたらします。


Classは、JavaScript言語を20年近く苦しめてきた「class」問題に新たな貢献をしました。いくつかの点で、それは解決するよりも多くの質問をします、そして[[Prototype]]メカニズムの優雅さと単純さに加えて、それは全体的に非常に不自然な一致のように感じます。


結論:ES6クラスが[[Prototype]]の堅牢な使用を困難にし、JSオブジェクトメカニズムの最も重要なプロパティであるオブジェクト間のリアルタイム委任リンクを非表示にする場合、クラスによって引き起こされる問題が解決するよりも難しいと考えるべきではありません。もっと、そしてそれを反パターンとして軽視しますか?


私はあなたがこの質問に答えるのを本当に助けることができません。しかし、この本がこの質問をこれまで経験したことのない深さで完全に探求し、この質問に自分で答えるのに必要な情報を提供してくれることを願っています。


最後に、これとオブジェクトプロトタイプの最後の記事ですが、接続を改善するには、次の記事が必要になる場合があります。

[問題773]あなたはJSを知らない:行動の委任

[問題772] JS:プロトタイプを理解していない

[問題767] JSを理解していない:「クラス」のオブジェクトを混合(難読化)する


または、[jsがわからない]と返信して、@ JoeHetfieldを確認してください


おすすめ

転載: blog.51cto.com/15080028/2595037