The principle of two-way binding of Vue data is achieved through data hijacking combined with the "publisher-subscriber" model. First, the data is monitored, and then the subscriber is notified when the monitored properties change. Whether to update, if updated, the corresponding update function will be executed to update the view.
The two-way data binding principle of Vue is achieved through data hijacking combined with the publisher-subscriber model. First, the data is monitored, and then when When the monitored properties change, the subscriber is told whether to update. If updated, the corresponding update function will be executed to update the view
[Recommended course: Vue tutorial】
MVC mode
The previous MVC mode was one-way binding, that is, the Model was bound to the View. When we update the Model with JavaScript code, the View will automatically update
MVVM mode
MVVM mode is Model–View– ViewModel mode. It realizes that changes in View are automatically reflected in ViewModel and vice versa. The understanding of two-way binding is that when the user updates the View, the Model's data is automatically updated. This situation is two-way binding. To be more detailed, on the basis of one-way binding, a change (input) event is added to the input elements input, textare, etc. (the change event is triggered, and the status of the View is updated) to dynamically modify the model.
Two-way binding principle
Vue data two-way binding is achieved through data hijacking combined with the publisher-subscriber model. Implementation
We already know that to implement two-way binding of data, we must first hijack and monitor the data, so we need to set up a listener Observer to monitor all properties. If the attribute changes, you need to tell the subscriber Watcher to see if it needs to be updated. Because there are many subscribers, we need a message subscriber Dep to specifically collect these subscribers, and then manage them uniformly between the Observer and the Watcher. Next, we also need an instruction parser Compile to scan and parse each node element, initialize the relevant instructions (such as v-model, v-on) into a subscriber Watcher, and replace the template data or bind it Corresponding function, when the subscriber Watcher receives the change of the corresponding attribute, it will execute the corresponding update function to update the view.
So next we perform the following three steps to achieve two-way binding of data:
(1) Implement a listener Observer to hijack and monitor all properties. If there are changes , notify subscribers.
(2) Implement a subscriber Watcher. Each Watcher is bound to an update function. The watcher can receive notification of attribute changes and execute the corresponding function to update the view.
(3) Implement a parser Compile that can scan and parse the relevant instructions of each node (v-model, v-on and other instructions). If the node has v-model, v-on and other instructions, The parser Compile initializes the template data of this type of node so that it can be displayed on the view, and then initializes the corresponding subscriber (Watcher).
Implementing an Observer
Observer is a data listener, and its core implementation method is Object.defineProperty(). If you want to monitor all properties, you can traverse all property values through recursion and process them with Object.defineProperty()
The following code implements an Observer.
function Observer(data) { this.data = data; this.walk(data); } Observer.prototype = { walk: function(data) { var self = this; //这里是通过对一个对象进行遍历,对这个对象的所有属性都进行监听 Object.keys(data).forEach(function(key) { self.defineReactive(data, key, data[key]); }); }, defineReactive: function(data, key, val) { var dep = new Dep(); // 递归遍历所有子属性 var childObj = observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function getter () { if (Dep.target) { // 在这里添加一个订阅者 console.log(Dep.target) dep.addSub(Dep.target); } return val; }, // setter,如果对一个对象属性值改变,就会触发setter中的dep.notify(), 通知watcher(订阅者)数据变更,执行对应订阅者的更新函数,来更新视图。 set: function setter (newVal) { if (newVal === val) { return; } val = newVal; // 新的值是object的话,进行监听 childObj = observe(newVal); dep.notify(); } }); } };function observe(value, vm) { if (!value || typeof value !== 'object') { return; } return new Observer(value); };// 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数 function Dep () { this.subs = []; } Dep.prototype = { /** * [订阅器添加订阅者] * @param {[Watcher]} sub [订阅者] */ addSub: function(sub) { this.subs.push(sub); }, // 通知订阅者数据变更 notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } }; Dep.target = null;
In Observer, when I looked at other people’s source code, one thing I didn’t understand was where Dep.target
came from. I believe some people will have the same problem as me. question. Don't worry here. When you write about Watcher, you will find that Dep.target
comes from Watcher.
Implementing a Watcher
Watcher is a subscriber. Used to process the update message sent by the Observer and execute the update function bound to the Watcher.
The following code implements a Watcher
function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); // 将自己添加到订阅器的操作} Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; // 缓存自己 var value = this.vm.data[this.exp] // 强制执行监听器里的get函数 Dep.target = null; // 释放自己 return value; } };
In the process of studying the code, I think the most complicated thing is to understand the parameters of these functions. Later, after I output these parameters, the functions These functions are also easy to understand. vm is the SelfValue object to be written later, which is equivalent to an object of new Vue in Vue. exp is the attribute value of the node node's v-model or v-on:click
and other instructions.
As can be seen from the above code, in the getter function of Watcher, Dep.target
points to itself, which is the Watcher object. In the getter function,
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数。 这里获取vm.data[this.exp] 时,会调用Observer中Object.defineProperty中的get函数 get: function getter () { if (Dep.target) { // 在这里添加一个订阅者 console.log(Dep.target) dep.addSub(Dep.target); } return val; },
thus adds the watcher to the subscriber, which solves the problem of where the above Dep.target
comes from.
Implement a Compile
Compile主要的作用是把new SelfVue 绑定的dom节点,(也就是el标签绑定的id)遍历该节点的所有子节点,找出其中所有的v-指令和" {{}} ".
(1)如果子节点含有v-指令,即是元素节点,则对这个元素添加监听事件。(如果是v-on,则node.addEventListener('click')
,如果是v-model,则node.addEventListener('input'))
。接着初始化模板元素,创建一个Watcher绑定这个元素节点。
(2)如果子节点是文本节点,即" {{ data }} ",则用正则表达式取出" {{ data }} "中的data,然后var initText = this.vm[exp]
,用initText去替代其中的data。
实现一个MVVM
可以说MVVM是Observer,Compile以及Watcher的“boss”了,他需要安排给Observer,Compile以及Watche做的事情如下
(1)Observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify
(2)Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数
(3)Watcher一方面接收Observer通过dep传递过来的数据变化,一方面通知Compile进行view update。
最后,把这个MVVM抽象出来,就是vue中Vue的构造函数了,可以构造出一个vue实例。
最后写一个html测试一下我们的功能
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>self-vue</title></head><style> #app { text-align: center; }</style><body> <div id="app"> <h2>{{title}}</h2> <input v-model="name"> <h1>{{name}}</h1> <button v-on:click="clickMe">click me!</button> </div></body><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 type="text/javascript"> var app = new SelfVue({ el: '#app', data: { title: 'hello world', name: 'canfoo' }, methods: { clickMe: function () { this.title = 'hello world'; } }, mounted: function () { window.setTimeout(() => { this.title = '你好'; }, 1000); } });</script></html>
先执行mvvm中的new SelfVue(...),在mvvm.js中,
observe(this.data); new Compile(options.el, this);
先初始化一个监听器Observer,用于监听该对象data属性的值。
然后初始化一个解析器Compile,绑定这个节点,并解析其中的v-," {{}} "指令,(每一个指令对应一个Watcher)并初始化模板数据以及初始化相应的订阅者,并把订阅者添加到订阅器中(Dep)。这样就实现双向绑定了。
如果v-model绑定的元素,
<input v-model="name">
即输入框的值发生变化,就会触发Compile中的
node.addEventListener('input', function(e) { var newValue = e.target.value; if (val === newValue) { return; } self.vm[exp] = newValue; val = newValue; });
self.vm[exp] = newValue;
这个语句会触发mvvm中SelfValue的setter,以及触发Observer对该对象name属性的监听,即Observer中的Object.defineProperty()
中的setter。setter中有通知订阅者的函数dep.notify
,Watcher收到通知后就会执行绑定的更新函数。
最后的最后就是效果图啦:
The above is the detailed content of How to understand the principle of two-way binding of Vue data. For more information, please follow other related articles on the PHP Chinese website!