起源
数日前、私はいくつかの人気のあるMINI MVVMフレームワークの実装を見ていました(AngularjsやEmberjsなどの重いフレームワークではなく、Avalon.js、Vue.jsなどの軽いフレームワークなど)。一般に、人気のある最新のMVVMフレームワークは、フレームワーク自体のセールスポイントであるデータの双方向の結合(2ウェイデータ結合)を排除します(ember.jsはデータの双方向の結合をサポートしていないようです。)、各フレームワークの双方向データ結合の実装方法はそれほど一貫していません。たとえば、Anguarjsは内部的にダーティチェックを使用しますが、Avalon.jsの内部実装の本質はプロパティアクセサを設定することです。
ここで、各フレームワークによる双方向データ結合の具体的な実装について議論するつもりはありません。フロントエンドで双方向データバインディングを実装するいくつかの一般的な方法についてのみ説明し、avalon.jsの技術的選択に焦点を当てて、双方向データ結合を実装します。
双方向データ結合の従来の実装
まず、フロントエンドの双方向データの拘束力について話しましょう。簡単に言えば、フレームワークのコントローラーレイヤー(ここのコントローラーレイヤーは一般的な用語です。ビュー動作を制御し、モデル層を接続するミドルウェアとして理解できます)とUIディスプレイレイヤー(ビューレイヤー)を双方向データチャネルを確立します。これらの2つのレイヤーのいずれかが変更されると、他のレイヤーは、対応する変更をすぐに自動的に行います(またはすぐに表示されます)。
一般的に、この双方向のデータ結合関係(コントローラー層とディスプレイ層の間の相関プロセス)を実現するために、現在、フロントエンドには3つの方法があります。
1。汚れたチェック
2。観察メカニズム
3。プロパティアクセサをカプセル化します
汚れたチェック
Angularjs(ここでは、Angularjs 2.xxバージョンを表していないAngularjs 1.xxバージョンを特に指します)は、双方向データ結合の技術的実装であると言います。一般的な原則は、Angularjsがシーケンスを維持し、このシーケンスで監視する必要があるすべての属性を配置することです。特定の特定のイベントが発生した場合(これはタイミングではなく、いくつかの特別なイベントによってトリガーされることに注意してください)、AngularJは$ Digestメソッドを呼び出します。このメソッド内のロジックは、すべてのウォッチャーを通過し、監視されている属性を比較し、メソッド呼び出しの前後に属性値が変更されたかどうかを比較することです。変更された場合、対応するハンドラーが呼び出されます。インターネット上には、この記事など、Angularjsの双方向データバインディングの実装原則を分析する多くの記事があります。
この方法の欠点は明らかです。特に、単一ページの監視の数が数桁に達する場合、トラバースとトレーニングウォッチャーは非常にパフォーマンスを消費します。
観察メカニズム
ブロガーには、object.observe()によって提起されたデータのバインディングの変更を再版および翻訳した記事がありました。これは、object.observeメソッドの使用を指し、ecmascript7でオブジェクト(またはそのプロパティ)を監視および観察します。変更されると、対応するハンドラーが実行されます。
これは、現在の属性データの変更を監視するための最も完璧な方法です。言語(ブラウザ)ネイティブサポートはこれに勝るものではありません。唯一の後悔は、現在のサポートの幅が十分ではなく、完全に促進する必要があることです。
カプセル化プロパティアクセター
PHPには__get()や__set()メソッドなど、PHPには魔法のメソッドの概念があります。 JavaScriptには同様の概念がありますが、それは魔法の方法ではなく、アクセサと呼ばれます。サンプルコードを見てみましょう。
var data = {name: "erik"、getname:function(){return this.name;}、setname:function(name){this.name = name;}};上記のコードから、データのgetName()やsetName()メソッドなど、飛躍を垣間見ることができます。 Data.nameのアクセサ(またはアクセサ)と見なすことができます。
実際、上記のコードでは、より厳格な場合、data.nameプロパティに直接アクセスすることは許可されていません。 data.nameへのすべての読み取りと書き込みは、data.getname()およびdata.setname()メソッドを介して渡す必要があります。したがって、プロパティが直接読み書きを許可していないが、アクセサを介して読み書きする必要があると想像してみてください。もちろん、プロパティの価値の変更の監視など、プロパティのアクセサー方法を書き換えることにより、いくつかの追加を行う必要があります。これは、プロパティアクセターを使用して双方向のデータバインディングを行う原則です。
もちろん、この方法には欠点もあります。最も顕著なのは、属性監視が追加されるたびに、対応するアクセサア方法をこの属性に追加する必要があることです。そうしないと、この属性の変更はキャプチャされません。
Object.DefinePropertyメソッド
国内のMVVMフレームワークAVALON.JSデータの実装双方向のバインディングの原則は、プロパティアクセサです。しかし、もちろん、上記の例コードほど独創的ではありません。 ECMAScript 5.1(ECMA-262)で定義されている標準プロパティオブジェクトを使用します。国内市場の状況に対応して、一部はobject.definePropertyをサポートしていません。ローエンドブラウザーのサポートを徐々に放棄した他のMVVMフレームワークとは異なり、低レベルのブラウザはVBScriptを完璧な互換性に使用します。
まず、MDNでobject.definePropertyメソッドを定義しましょう。
object.defineProperty()メソッドは、オブジェクト上の新しいプロパティを直接定義するか、オブジェクト上の既存のプロパティを変更してオブジェクトを返します。
意味は明確であり、Object.DefinePropertyメソッドは、オブジェクトプロパティを定義したり、既存のオブジェクトプロパティを変更する直接的な方法を提供します。メソッドのプロトタイプは次のとおりです。
object.defineProperty(obj、prop、descriptor)
で、
OBJ、変更するオブジェクト
プロップ、変更された属性名
記述子、変更される属性の関連する説明
記述子はオブジェクトを渡す必要があり、そのデフォルト値は次のとおりです
/*** @{param} Descriptor*/{configurable:false、列挙可能:false、writable:false、value:null、set:undefined、get:undefined}プロパティが構成可能かどうかを構成可能。構成可能な意味には、属性を削除できるかどうか、属性の書き込み、列挙、構成可能なプロパティを変更できます。
属性が列挙可能かどうか、列挙可能。列挙可能の意味には、それが... inで通過できるかどうか、およびobject.keys()メソッドを介して取得できるかどうか。
属性が書き直される可能性があるかどうかは書き込み可能です。書き換えの意味には、属性を再割り当てできるかどうかが含まれます。
値、プロパティのデフォルト値。
セット、プロパティライター(今のところ、それだけです)。属性が再割り当てされると、このメソッドは自動的に呼び出されます。
財産の読者を取得します(今のところ、それだけです)。プロパティにアクセスして読み取られたら、この方法は自動的に呼び出されます。
これがサンプルコードです、
var o = {}; object.defineProperty(o、 'name'、{value: 'erik'}); console.log(object.getownPropertyDescriptor(o、 'name')); // object {value: "erik"、writable:false、列挙可能:false、configureable:false} object.defineProperty(o、 'age'、{value:26、configurable:true、writable:true}); console.log(o.age); // 26o.age = 18; console.log(o.age); // 18。年齢プロパティは書き換え可能なconsole.log(object.keys(o)); // []。名前も年齢のプロパティも列挙可能なオブジェクトではありません。DefineProperty(o、 'sex'、{value: 'male'、writable:false}); o.sex = 'female'; //ここでの割り当ては、実際には効果のないconsole.log(o.sex)です。 // 'MALE'; Delete O.Sex; // false、プロパティ削除アクションも無効です上記の例の後、通常の状況では、object.definePropert()の使用は比較的単純です。
ただし、さらに注意が必要なことが1つあります。 object.defineProperty()メソッドがプロパティを設定する場合、プロパティはアクセサア属性(設定および取得)と書き込みまたは値の属性を同時に宣言することはできません。つまり、特定のプロパティが書き込み可能な属性または値の属性で設定されている場合、このプロパティは取得と設定を宣言できず、その逆も同様です。
Object.defineProperty()がプロパティを宣言すると、同じプロパティに対して2種類以上のアクセス制御が許可されていないためです。
サンプルコード、
var o = {}、myname = 'erik'; object.defineProperty(o、 'name'、{value:myname、set:function(name){myname = name;}、get:function(){return myname;}});上記のコードは問題ないようですが、実際に実行されたときにエラーが報告され、エラーは次のように報告されます。
TypeError:無効なプロパティ。プロパティは両方ともアクセサーを持つことはできませんし、書くことができるか、値を持っています、#<オブジェクト>
ここでの名前属性は値属性を宣言しているため、セットと取得属性を同時に取得します。どちらも名前属性の2つの読み取りおよび書き込みコントロールを提供します。ここで値機能が宣言されていないが、書き込み可能な機能が宣言されている場合、結果は同じになり、エラーが報告されます。