Data Binding
Data Binding generally refers to the data will show the view. Currently mvvm distal frame mode is used to achieve double tied. There are several general ways:
- Publish and subscribe
- ng dirty check
- Data hijacking
vue, then uses the data hijacking and publishing subscribe combination. The data used is Object.defineProperty hijacking to achieve, can be triggered when the corresponding function to get and set data by binding get and set.
achieve
So we need a listener to listen Observe changes in the data. When data changes, we need a Watcher subscribers to update the view, we also need to compile a parser to parse commands and instructions to initialize the view.
-
- Observe listener: monitor data changes, notify subscribers
- Watcher subscribers: receive data changes, update view
- Compile parser: parsing command to initialize templates, binding subscribers
Observe
Attribute data for each monitor, since there may be a plurality Watcher, it is necessary to store the container.
function Sub() { this.subs = []; } Sub.prototype = { add(sub) { this.subs.push(sub); }, trigger() { this.subs.forEach(sub => { sub.update(); }) } }; Sub.target = null; function observe(data) { if (typeof data !== 'object' || !data) return; Object.keys(data).forEach(item => { let val = data[item]; let sub = new Sub(); Object.defineProperty(data, item, { enumerable: true, configurable: false, get() { if (Sub.target) { sub.add(Sub.target); } return val; }, set(newVal) { val = newVal; sub.trigger(); } }) }) }
Watcher
Watcher corresponding attribute added to the sub container, when the attribute change, to perform the update function.
function Watcher(vm, prop, callback) { this.vm = vm; this.prop = prop; this.callback = callback; Sub.target = this; let val = this.vm.$data[prop]; Sub.target = null; this.vaule = val; } Watcher.prototype.update = function () { let newValue = this.vm.$data[this.prop]; if (this.value !== newValue) { this.value = newValue; this.callback.call(this.vm, newValue); } }
Compile
Dom to obtain the instructions and initialization template, add watcher update the view.
function Compile(vm) { this.vm = vm; this.el = vm.$el; this.init(); } Compile.prototype.init = function () { let fragment = document.createDocumentFragment(); let child = this.el.firstChild; while(child) { fragment.append(child); child = this.el.firstChild; } let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (node.nodeType === 1) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { let name = attr.nodeName; if (name === 'v-model') { let prop = attr.nodeValue; let value = this.vm.$data[prop]; node.value = value; new Watcher(this.vm, prop, val => { node.value = val; }); node.addEventListener('input', e => { let newVal = e.target.value; if (value !== newVal) { this.vm.$data[prop] = newVal; } }) } }) } let reg = /\{\{(.*)\}\}/; let text = node.textContent; if (reg.test(text)) { let prop = RegExp.$1; let val = this.vm.$data[prop]; node.textContent = val; new Watcher(this.vm, prop, val => { node.textContent = val; }); } }) this.el.appendChild(fragment); }
Here, the basic idea has been implemented is completed, just to achieve a v-model command.
Finally, Observe Watcher and Compile, can become a complete mvvm up.
<div id="app"> <div>{{val}}</div> <input type="text" id="input" v-model="val"> </div> <script> function MyVue(options) { this.$options = options; this.$el = options.el; this.$data = options.data; this.init(); } MyVue.prototype.init = function () { observe(this.$data); new Compile(this); }; new MyVue({ el: document.getElementById('app'), data: { val: 123 } }) </script>
Of course, this is just a simple realization, did not consider the details, mainly the study of ideas.