どうやって双方向バインディングmvmの原理を実現しますか?
13237 ワード
本文は何をしてあげられますか?
1、Vueの双方向データバインディング原理とコアコードモジュールを理解する
2、好奇心を和らげながら、双方向結合の実現方法を知る
原理と実現を説明するために、本明細書の関連コードは主にvueソースコードから抜粋し、簡略化された改造を行いました。比較的に粗末で、行列の処理、データの循環依存などを考慮していません。でも、これらはみんなの読書と理解に影響しません。
vueソースを読む時にもっと役に立ちます。
本論文のすべての関連コードは、githubの上にgithub.com/DMQ/mvmを見つけることができます。
皆さんはmvvm双方向バインディングに慣れていないと思います。一言でコードに合わないと、ここで最終的に実現される効果を見ましょう。vueと同じ文法です。もし双方向バインディングを理解していないなら、Googleを猛突きします。
現在いくつかの主流のmvc(vm)フレームワークは一方向データバインディングを実現していますが、私が理解している双方向データバインディングは一方向バインディングに基づいて、入力可能要素(input、textareなど)にchangeイベントを追加して、modelとviewを動的に修正しています。そのため、あまり気を使わなくてもいいです。シングルまたは双方向の結合が実現されます。
データバインディングを実現する方法は、大体以下の通りです。リリース者-予約者モード(backbone.js) 汚値検査(anglar.js) データハイジャック(vue.js) 配布者-購読者モード:普通はsub、pubを通じてデータとビューのバインディングの傍受を実現して、データ方式を更新するのはvm.set('property'、value)です。ここでは文章で話したのが比較的に詳しくて、興味があります。
このような方式は結局lowすぎます。Vm.property=valueという方式でデータを更新したいです。同時に自動的にビューを更新します。
汚値検査:anglar.jsは汚値検出によってデータに変更があるかどうかを判定してビューを更新するかを決定します。一番簡単な方法はset Interval()のタイミングポーリングによってデータが変動します。もちろんGoogleはこのようにlowしません。anglarは指定されたものがトリガされた時だけ汚損値検出に入ります。大体以下の通りです。 DOMイベント、例えばユーザがテキストを入力し、ボタンをクリックするなどです。ng-click) XHR応答イベント ブラウザLocation変更イベント Timerイベント は、digest()またはappy() を実行する。
データハイジャック:vue.jsはデータハイジャック結合リリース者-購読者モードを採用し、Object.definePropertyを通じて各属性のsetterをハイジャックし、getterはデータ変動時に購読者にメッセージを発表し、相応の傍受フィードバックをトリガする。
考えがまとまる
vueはデータハイジャックによってデータバインディングを行うことがわかっています。その中で最も核心的な方法はObject.definePropertyを通じて属性のハイジャックを実現し、データの変動を監督する目的を達成することです。この方法は本文の中で最も重要で基礎的な内容の一つです。mvmの双方向バインディングを実現するには、次のいくつかの点が必要です。1、データモニターObserverを実現するには、データオブジェクトのすべての属性をモニターすることができます。変更があれば最新の値を入手して、購読者2に通知し、コマンド解析器Compleを実現し、各要素ノードのコマンドをスキャンして解析します。コマンドテンプレートに従ってデータを交換します。また、対応する更新関数3をバインドし、ObserverとCommpileを接続するためのブリッジとして、各属性の変動の通知を購読し、受信し、コマンドバインディングの対応するコールバック関数を実行して、ビュー4、mvmエントリ関数を更新し、上記3つを統合することができます。
1、Observerを実現する
ok、構想はすでに整理し終わって、関連しているロジックとモジュールの機能も比較的に明確になりました。let's do itはObeject.defineProperty()を利用して属性の変動を監督することができると知っています。そうすると、observeのデータオブジェクトを再帰的に巡回します。 setterとgetterは、この対象のある値を付与すると、setterをトリガし、データの変化をモニターすることができます。関連コードはこのようにすることができます。
2、Compleを実現する
compleは主にテンプレートコマンドを解析して、テンプレートの変数をデータに置き換えて、レンダリングページビューを初期化して、各コマンドに対応するノードをバインドして更新関数を更新して、モニターデータの購読者を追加します。データが変更されたら、通知を受けて、より新しいビューを図に示します。img 3][img 3]
解析のプロセスを経ると、domノードを複数回操作し、性能と効率を向上させるために、まずvueインスタンスのルートノードのelをドキュメントの破片fragmentに変換して解析的にコンパイルし、解析が完了したら、fragmentを元のリアルdomノードに追加します。
これで簡単なCompleが完成しました。ここです。次にWatchという購読者の具体的な実現を見ます。
3、Watchを実現する
Watch購読者はObserverとCompleとの間の通信の架け橋として、主にやることは:1、自分の実用化時に属性購読器に自分を追加してください。2、自分自身にアップロード方法が必要です。ちょっと乱れているなら、前の考えを振り返って整理してもいいです。
ok、ウォッチももう実現しました。完全コード。基本的にvueの中でデータの結合に関する比較核心的ないくつかのモジュールもこのいくつかであり、ダッシュ完全コードは、Srcディレクトリでvueソースを見つけることができます。
最後にMVVMエントランスファイルの関連ロジックと実現を説明します。比較的簡単です。
4、MVVMの実現
MVMはデーターバインディングの入口として、Observer、Comple、Watchの3つを統合し、Observerを通じて自分のmodelデータの変化をモニターし、Compleを通じてテンプレートコマンドを解析し、最終的にはWatchを利用してObserverとCompleの間の通信橋を持ち上げて、データの変化->ビューの更新を達成する。ビューのインタラクティブ変化(input)->データmodelの変更による双方向バインディング効果です。
簡単なMVVMコンストラクタはこのようです。
明らかに私達の最初の期待に合わないです。私達が期待しているコール方式はこうです。var vm=new MVM({data:''kideng');vm.name='dmq';
したがって、ここではMVMのインスタンスに属性エージェントを追加する方法が必要であり、vmにアクセスする属性エージェントをvm._.にアクセスさせる。dataの属性、改造後のコードは以下の通りです。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。
1、Vueの双方向データバインディング原理とコアコードモジュールを理解する
2、好奇心を和らげながら、双方向結合の実現方法を知る
原理と実現を説明するために、本明細書の関連コードは主にvueソースコードから抜粋し、簡略化された改造を行いました。比較的に粗末で、行列の処理、データの循環依存などを考慮していません。でも、これらはみんなの読書と理解に影響しません。
vueソースを読む時にもっと役に立ちます。
本論文のすべての関連コードは、githubの上にgithub.com/DMQ/mvmを見つけることができます。
皆さんはmvvm双方向バインディングに慣れていないと思います。一言でコードに合わないと、ここで最終的に実現される効果を見ましょう。vueと同じ文法です。もし双方向バインディングを理解していないなら、Googleを猛突きします。
<div id="mvvm-app">
<input type="text" v-model="word">
<p>{{word}}</p>
<button v-on:click="sayHi">change model</button>
</div>
<script src="./js/observer.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compile.js"></script>
<script src="./js/mvvm.js"></script>
<script>
var vm = new MVVM({
el: '#mvvm-app',
data: {
word: 'Hello World!'
},
methods: {
sayHi: function() {
this.word = 'Hi, everybody!';
}
}
});
</script>
いくつかの双方向結合を実現する方法現在いくつかの主流のmvc(vm)フレームワークは一方向データバインディングを実現していますが、私が理解している双方向データバインディングは一方向バインディングに基づいて、入力可能要素(input、textareなど)にchangeイベントを追加して、modelとviewを動的に修正しています。そのため、あまり気を使わなくてもいいです。シングルまたは双方向の結合が実現されます。
データバインディングを実現する方法は、大体以下の通りです。
このような方式は結局lowすぎます。Vm.property=valueという方式でデータを更新したいです。同時に自動的にビューを更新します。
汚値検査:anglar.jsは汚値検出によってデータに変更があるかどうかを判定してビューを更新するかを決定します。一番簡単な方法はset Interval()のタイミングポーリングによってデータが変動します。もちろんGoogleはこのようにlowしません。anglarは指定されたものがトリガされた時だけ汚損値検出に入ります。大体以下の通りです。
データハイジャック:vue.jsはデータハイジャック結合リリース者-購読者モードを採用し、Object.definePropertyを通じて各属性のsetterをハイジャックし、getterはデータ変動時に購読者にメッセージを発表し、相応の傍受フィードバックをトリガする。
考えがまとまる
vueはデータハイジャックによってデータバインディングを行うことがわかっています。その中で最も核心的な方法はObject.definePropertyを通じて属性のハイジャックを実現し、データの変動を監督する目的を達成することです。この方法は本文の中で最も重要で基礎的な内容の一つです。mvmの双方向バインディングを実現するには、次のいくつかの点が必要です。1、データモニターObserverを実現するには、データオブジェクトのすべての属性をモニターすることができます。変更があれば最新の値を入手して、購読者2に通知し、コマンド解析器Compleを実現し、各要素ノードのコマンドをスキャンして解析します。コマンドテンプレートに従ってデータを交換します。また、対応する更新関数3をバインドし、ObserverとCommpileを接続するためのブリッジとして、各属性の変動の通知を購読し、受信し、コマンドバインディングの対応するコールバック関数を実行して、ビュー4、mvmエントリ関数を更新し、上記3つを統合することができます。
1、Observerを実現する
ok、構想はすでに整理し終わって、関連しているロジックとモジュールの機能も比較的に明確になりました。let's do itはObeject.defineProperty()を利用して属性の変動を監督することができると知っています。そうすると、observeのデータオブジェクトを再帰的に巡回します。 setterとgetterは、この対象のある値を付与すると、setterをトリガし、データの変化をモニターすることができます。関連コードはこのようにすることができます。
var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // , kindeng --> dmq
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
//
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
function defineReactive(data, key, val) {
observe(val); //
Object.defineProperty(data, key, {
enumerable: true, //
configurable: false, // define
get: function() {
return val;
},
set: function(newVal) {
console.log(' , ', val, ' --> ', newVal);
val = newVal;
}
});
}
これで各データの変化をモニターできます。変化をモニターしてから、購読者にどうやって通知しますか?だから、これからはメッセージ購読器を実現する必要があります。簡単です。配列を維持して、購読者を集めて、データの変動によってnotifyを触発して、購読者のudate方法を呼び出します。コードの改善後はこうです。
// ...
function defineReactive(data, key, val) {
var dep = new Dep();
observe(val); //
Object.defineProperty(data, key, {
// ...
set: function(newVal) {
if (val === newVal) return;
console.log(' , ', val, ' --> ', newVal);
val = newVal;
dep.notify(); //
}
});
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
問題が来ました。誰が購読者ですか?購読者はどうやって購読者を追加しますか?そうです。上の考えを整理しています。予約者はウオッチであるべきです。そして、var dep=new Dep()です。defineReactiveメソッドの内部で定義されているので、depを通して購読者を追加するには、クローズド内で操作しなければなりません。 ゲテの中で足を動かす:
// Observer.js
// ...
Object.defineProperty(data, key, {
get: function() {
// watcher, Dep target , watcher,
Dep.target && dep.addDep(Dep.target);
return val;
}
// ...
});
// Watcher.js
Watcher.prototype = {
get: function(key) {
Dep.target = this;
this.value = data[key]; // getter,
Dep.target = null;
}
}
ここでObserverが実現されました。モニターデータとデータの変化をカスタマイズ者に通知する機能を備えています。完全コードです。これからCopileを実現します。2、Compleを実現する
compleは主にテンプレートコマンドを解析して、テンプレートの変数をデータに置き換えて、レンダリングページビューを初期化して、各コマンドに対応するノードをバインドして更新関数を更新して、モニターデータの購読者を追加します。データが変更されたら、通知を受けて、より新しいビューを図に示します。img 3][img 3]
解析のプロセスを経ると、domノードを複数回操作し、性能と効率を向上させるために、まずvueインスタンスのルートノードのelをドキュメントの破片fragmentに変換して解析的にコンパイルし、解析が完了したら、fragmentを元のリアルdomノードに追加します。
function Compile(el) {
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el);
this.init();
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
init: function() { this.compileElement(this.$fragment); },
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(), child;
// fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
}
};
compleElement方法は、すべてのノードとそのサブノードを巡回してスキャン解析コンパイルを行い、対応するコマンドレンダリング関数を呼び出してデータレンダリングを行い、対応するコマンド更新関数を呼び出してバインディングし、コードとコメントの説明を詳しく見てください。
Compile.prototype = {
// ...
compileElement: function(el) {
var childNodes = el.childNodes, me = this;
[].slice.call(childNodes).forEach(function(node) {
var text = node.textContent;
var reg = /\{\{(.*)\}\}/; //
//
if (me.isElementNode(node)) {
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) {
me.compileText(node, RegExp.$1);
}
//
if (node.childNodes && node.childNodes.length) {
me.compileElement(node);
}
});
},
compile: function(node) {
var nodeAttrs = node.attributes, me = this;
[].slice.call(nodeAttrs).forEach(function(attr) {
// : v-xxx
// <span v-text="content"></span> v-text
var attrName = attr.name; // v-text
if (me.isDirective(attrName)) {
var exp = attr.value; // content
var dir = attrName.substring(2); // text
if (me.isEventDirective(dir)) {
// , v-on:click
compileUtil.eventHandler(node, me.$vm, exp, dir);
} else {
//
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
}
});
}
};
//
var compileUtil = {
text: function(node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
// ...
bind: function(node, vm, exp, dir) {
var updaterFn = updater[dir + 'Updater'];
//
updaterFn && updaterFn(node, vm[exp]);
// , watcher
new Watcher(vm, exp, function(value, oldValue) {
// , ,
updaterFn && updaterFn(node, value, oldValue);
});
}
};
//
var updater = {
textUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
}
// ...
};
ここでは再帰的巡回により、各ノードおよびサブノードが、{}表現宣言を含むテキストノードに解析的にコンパイルされることを保証する。命令の声明は、特定のプレフィクスのノード属性によってマークされることになっています。例えば、「span v-text=「content」other-atrのv-textはコマンドであり、other-atrはコマンドではなく、普通の属性です。モニターデータ、バインディング更新関数の処理は、compleUtil.bind()という方法で、new Watch()にフィードバックを追加してデータ変化の通知を受信する。これで簡単なCompleが完成しました。ここです。次にWatchという購読者の具体的な実現を見ます。
3、Watchを実現する
Watch購読者はObserverとCompleとの間の通信の架け橋として、主にやることは:1、自分の実用化時に属性購読器に自分を追加してください。2、自分自身にアップロード方法が必要です。ちょっと乱れているなら、前の考えを振り返って整理してもいいです。
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
// getter, dep , Observer
this.value = this.get();
}
Watcher.prototype = {
update: function() {
this.run(); //
},
run: function() {
var value = this.get(); //
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal); // Compile ,
}
},
get: function() {
Dep.target = this; //
var value = this.vm[exp]; // getter,
Dep.target = null; // ,
return value;
}
};
// Observer Dep,
Object.defineProperty(data, key, {
get: function() {
// watcher, Dep target , watcher,
Dep.target && dep.addDep(Dep.target);
return val;
}
// ...
});
Dep.prototype = {
notify: function() {
this.subs.forEach(function(sub) {
sub.update(); // update ,
});
}
};
Watchを実例化する際、get()メソッドを呼び出し、Dep.target=watch Instanceで現在のウォッチの例をマーキングし、属性定義のgetterメソッドを強制的に触発し、getterメソッドを実行すると、プロパティの購読器depに現在のウォッチのインスタンスを追加し、属性値が変化したときに、watch Instanceに更新通知が届きます。ok、ウォッチももう実現しました。完全コード。基本的にvueの中でデータの結合に関する比較核心的ないくつかのモジュールもこのいくつかであり、ダッシュ完全コードは、Srcディレクトリでvueソースを見つけることができます。
最後にMVVMエントランスファイルの関連ロジックと実現を説明します。比較的簡単です。
4、MVVMの実現
MVMはデーターバインディングの入口として、Observer、Comple、Watchの3つを統合し、Observerを通じて自分のmodelデータの変化をモニターし、Compleを通じてテンプレートコマンドを解析し、最終的にはWatchを利用してObserverとCompleの間の通信橋を持ち上げて、データの変化->ビューの更新を達成する。ビューのインタラクティブ変化(input)->データmodelの変更による双方向バインディング効果です。
簡単なMVVMコンストラクタはこのようです。
function MVVM(options) {
this.$options = options;
var data = this._data = this.$options.data;
observe(data, this);
this.$compile = new Compile(options.el || document.body, this)
}
しかし、ここで問題があります。コードの中から傍受のデータオブジェクトはoptions.dataであり、毎回ビューを更新する必要がある場合、var vm=new MVMを通過しなければなりません。vm._.data.name='dmq'このようにデータを変更します。明らかに私達の最初の期待に合わないです。私達が期待しているコール方式はこうです。var vm=new MVM({data:''kideng');vm.name='dmq';
したがって、ここではMVMのインスタンスに属性エージェントを追加する方法が必要であり、vmにアクセスする属性エージェントをvm._.にアクセスさせる。dataの属性、改造後のコードは以下の通りです。
function MVVM(options) {
this.$options = options;
var data = this._data = this.$options.data, me = this;
// , vm.xxx -> vm._data.xxx
Object.keys(data).forEach(function(key) {
me._proxy(key);
});
observe(data, this);
this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
_proxy: function(key) {
var me = this;
Object.defineProperty(me, key, {
configurable: false,
enumerable: true,
get: function proxyGetter() {
return me._data[key];
},
set: function proxySetter(newVal) {
me._data[key] = newVal;
}
});
}
};
ここでは主にObject.defineProperty()を利用してvmインスタンスオブジェクトの属性の読み書き権をハイジャックし、読み書きvmインスタンスの属性を読み書きvm.dataの属性値は、魚眼混珠の効果を達成します。以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。