元のリンク:https: //www.jianshu.com/p/e7ebb1500613
1.原則:
1. Vueの双方向データバインディングは、公開モードとサブスクライブモードを組み合わせたデータハイジャックによって実現されます。つまり、データとビューが同期され、データが変更され、ビューが変更され、ビューが変更され、それに応じてデータも変更されます。
2.コア:VUE双方向データバインディングに関して、そのコアはObject.defineProperty()メソッドです。
3. Object.defineProperty()メソッドを導入します(
1)Object.defineProperty(obj、prop、descriptor)。この文法には、obj(プロパティを定義するオブジェクト)prop(プロパティを定義するオブジェクト)の3つのパラメーターがあります。定義または変更)プロパティ)記述子(特定の変更メソッド)
(2)簡単に言えば、このメソッドは値を定義するために使用されます。呼び出すときはgetメソッドを使用し、このプロパティに値を割り当てるときはsetメソッドを使用します。
setメソッドとgetメソッドの予備的な理解
次に、最初にjs双方向データバインディングを実装して、このメソッドに慣れてください。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" id="a">
<span id="b"></span>
</div>
</body>
<script>
var obj = {}; //定义一个空对象
var val = 'zhao'; //赋予初始值
Object.defineProperty(obj, 'val', {//定义要修改对象的属性
get: function () {
return val;
},
set: function (newVal) {
val = newVal;//定义val等于修改后的内容
document.getElementById('a').value = val;//让文本框的内容等于val
document.getElementById('b').innerHTML = val;//让span的内容等于val
}
});
document.addEventListener('keyup', function (e) {//当在文本框输入内容时让对象里你定义的val等于文本框的值
obj.val = e.target.value;
})
</script>
</html>
このようにして、jsの双方向データバインディングを実現し、この方法を予備的に理解することができます。
この例の効果は次のとおりです。テキストボックスの入力テキストが変更されると、同じテキストコンテンツが表示されます。スパンで同期的に;これは達成されますモデル=>ビューとビュー=>モデルの双方向バインディング。
setメソッドは、イベントリスナーキーアップを追加することによってトリガーされます。setはアクセサープロパティを変更すると同時に、domスタイルを変更し、spanタグのテキストを変更します。
3つ目は、真の双方向バインディングの原則を実現することです。
1.効果を実現する
思考の方向を判断できるように、vue双方向データバインディングがどのように実行されるかを見てみましょう。
画像
画像
2.タスク分割
タスクを分割すると、考え方がより明確になります。
(1)vueのデータのコンテンツを入力テキストボックスとテキストノードにバインドします。
(2)テキストボックスのコンテンツが変更されると、vueインスタンスのデータも次のようになります。同時に変更
(3)データの内容が変わると、入力ボックスとテキストノードの内容も変わります
3.タスク1-バインディングコンテンツを開始する
最初にDocuemntFragment(フラグメント化されたドキュメント)の概念を理解しましょう 。10個のノードを作成すると、各ノードがドキュメントに挿入されると、domノードコンテナーと考えることができます。ブラウザーがトリガーされます。リフロー。これは、ブラウザーが10回リフローする必要があることを意味し、リソースを消費します。
断片化されたドキュメントを使用するということは、最初に10個のノードすべてをコンテナーに入れ、最後にコンテナーをドキュメントに直接挿入することを意味します。ブラウザは一度だけリフローしました。
注:もう1つの非常に重要な機能は、appendChidメソッドを使用して元のdomツリーのノードをDocumentFragmentに追加すると、元のノードが削除されることです。
例:
アプリに2つの子ノード(1つの要素ノードと1つのテキストノード)があることがわかります。
ただし、DocumentFragmentを介してデータを乗っ取ると
画像
画像
画像
注:断片化されたドキュメントはすべての子ノードをハイジャックし、IDがappであるdivにはコンテンツがありません。
同時に、私たちは主にしばらくの間の状態を判断する必要があります。子ノードがあるかどうかを判断します。appendChildを追加するたびに、ノードの最初の子ノードがハイジャックされるため、ノードのノードが1つ少なくなります。子ノードがなくなるまで、子は未定義になり、ループは終了します。
コンテンツのバインドを実現する
には、2つの問題を考慮する必要があります。1つは入力にバインドする方法であり、もう1つはテキストノードにバインドする方法です。
この考え方はここにあります。DocumentFragmentでdivのすべての子ノードを取得し、各ノードを処理して、vmインスタンスに関連するコンテンツがあるかどうかを確認します。ある場合は、このノードのコンテンツを変更します。次に、それをDocumentFragmentに追加し直します。
まず、各ノードを処理する関数を記述します。v-model属性にバインドされた入力がある場合、または{ {xxx}}のテキストノードが 表示された場合は、コンテンツをvmインスタンスのデータのコンテンツに置き換えます。
画像
次に、断片化されたドキュメントにノードを追加すると、各ノードが処理されます。
画像
Vueのインスタンス化関数を作成する
画像
レンダリングは次のとおりです。
画像
コンテンツを入力ボックスとテキストノードに正常にバインドしました。
4.タスク2- [view => modelを実現します。
このタスクでは、入力ボックス、入力ボックスの質問、および入力ボックスがデータをどのように変更するかを検討します。イベントリスナーのキーアップ、入力などを介して最新の値を取得し、取得した最新の値をObject.definePropertyを介してインスタンスvmのテキストに割り当て、Object.definePropertyを介してvmインスタンスのデータの下にテキストを渡します。アクセサ属性として設定するため、vm.textに値を割り当てるとセットがトリガーされます。集合関数の1つの関数は、データ内のテキストを更新することであり、もう1つは、タスクが繰り返されるまで待機することです。
まず、事後監視機能を実装します。新しい値が割り当てられると、その値は変更されます
画像
次に、オブザーバーを実装して、インスタンスのすべての属性値を監視します。
画像
コンパイル関数を書き直します。アクセサ属性の変更によりアクセス方法も変更されていることに注意してください。同時に、インスタンスのテキスト値をいつでも更新するためのイベントリスナーが追加されています。
画像
サンプル関数では、データ内のすべての属性値を監視し、監視の追加に注意してください
画像
最後に、入力のコンテンツを変更することで入力のデータを変更できますが、単一のページは更新されません。
画像
画像
4.タスク3の実現-[モデル=>ビュー]
vmインスタンスのプロパティを変更することにより、入力ボックスの内容とテキストノードの内容を変更する必要があります。
ここで注意が必要な問題があります。入力ボックスを変更すると、vmインスタンスの属性が1対1で変更されます。
ただし、1対多のページ上の複数の場所でデータの属性を使用する場合があります。言い換えれば、1つのモデルの値を変更すると、複数のビューの値が変更される可能性があります。
これには、新しい知識ポイントを導入する必要があります。
サブスクリプション/パブリッシャーモード
サブスクリプションパブリッシングモード(オブザーバーモードとも呼ばれます)は、1対多の関係を定義し、複数のオブザーバーが特定のサブジェクトオブジェクトを同時に監視できるようにします。すべてのオブザベーションは、サブジェクトオブジェクトはuserobjectを変更します。
パブリッシャーは通知を送信します=>サブジェクトオブジェクトは通知を受信してサブスクライバーにプッシュします=>サブスクライバーは対応する操作を実行します
1
例:
画像
前述の集合関数の2つ目の機能は、「テキストが変更されました!」という通知操作をサブスクライバーに通知することです。テキストノードがサブスクライバーになります。メッセージを受信したら、すぐに更新操作を実行します。
要約すると、Vueが新しい場合は常に、主に2つのことを行います。1つはデータの監視(observe(data))で、もう1つはHTMLのコンパイル(nodeToFragement(id))です。
データを監視する過程で、データ内の属性ごとにテーマオブジェクトの深さを生成します。
HTMLをコンパイルする過程で、データバインディングに関連するノードごとにサブスクライバーウォッチャーが生成され、ウォッチャーは対応する属性のdepコンテナーに自分自身を追加します。
入力ボックスの内容を変更する=>イベントコールバック関数の属性値を変更する=>属性のsetメソッドをトリガーする。
次に達成したいことは、通知を送信するdep.notify()=>サブスクライバーの更新メソッドをトリガーする=>ビューを更新することです。
ここでの重要なロジックは、関連する属性のdepにウォッチャーを追加する方法です。
注:ウォッチャーサブスクライバーを追加するように直接割り当て操作を変更しました
画像
では、Watcherは何をすべきでしょうか?
画像
まず、グローバル変数Dep.targetに自分を割り当てます。
次に、updateメソッドが実行され、次にgetメソッドが実行されます。getメソッドはvmのアクセサー属性を読み取り、アクセサー属性のgetメソッドをトリガーします。getメソッドでは、ウォッチャーがのdepに追加されます。対応するアクセサ属性;
ここでも、属性の値を取得してから、ビューを更新します。
最後に、Dep.targetを空に設定します。これはグローバル変数であり、ウォッチャーとdepの間の唯一のブリッジであるため、Dep.targetが常に1つの値のみを持つことを保証する必要があります。
画像
画像
結局、この双方向のデータバインディング機能を実現しました。とても面倒ですが、何度か呼び出すと間違いなく役立つと思います。
最後に、エディターはソースコードを全員に添付します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<input type="text" v-model="text" /> {
{text}}
</div>
</body>
<script type="text/javascript">
// 编译函数
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/; // 来匹配{
{xxx}}中的xxx
//如果是元素节点
if(node.nodeType === 1) {
var attr = node.attributes;
//解析元素节点的所有属性
for(let i = 0; i < attr.length; i++) {
if(attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue //看看是与哪一个数据相关
node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
vm[name] = e.target.value
})
node.value = vm.data[name]; //将data中的值赋予给该node
node.removeAttribute('v-model')
}
}
}
//如果是文本节点
if(node.nodeType === 3) {
if(reg.test(node.nodeValue)) {
var name = RegExp.$1; //获取到匹配的字符串
name = name.trim();
// node.nodeValue = vm[name]; //将data中的值赋予给该node
new Watcher(vm, node, name) //绑定一个订阅者
}
}
}
// 在向碎片化文档中添加节点时,每个节点都处理一下
function nodeToFragment(node, vm) {
var fragment = document.createDocumentFragment();
var child;
while(child = node.firstChild) {
compile(child, vm);
fragment.appendChild(child);
}
return fragment
}
// Vue构造函数
// 观察data中的所有属性值,注意增添了observe
function Vue(options) {
this.data = options.data;
observe(this.data, this)
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this)
//处理完所有节点后,重新把内容添加回去
document.getElementById(id).appendChild(dom)
}
// 实现一个响应式监听属性的函数。一旦有赋新值就发生变化
function defineReactive(obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function() {
if(Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set: function(newVal) {
if(newVal === val) {
return
}
val = newVal;
console.log('新值' + val);
//一旦更新立马通知
dep.notify();
}
})
}
// 实现一个观察者,对于一个实例 每一个属性值都进行观察。
function observe(obj, vm) {
for(let key of Object.keys(obj)) {
defineReactive(vm, key, obj[key]);
}
}
// Watcher监听者
function Watcher(vm, node, name) {
Dep.target = this;
this.vm = vm;
this.node = node;
this.name = name;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update() {
this.get();
this.node.nodeValue = this.value //更改节点内容的关键
},
get() {
this.value = this.vm[this.name] //触发相应的get
}
}
// dep构造函数
function Dep() {
this.subs = []
}
Dep.prototype = {
addSub(sub) {
this.subs.push(sub)
},
notify() {
this.subs.forEach(function(sub) {
sub.update();
})
}
}
var vm = new Vue({
el: 'app',
data: {
text: '赵刚'
}
})
</script>
</html>