When I learned about VUE, the biggest feeling I had was how convenient two-way data binding was, without manipulating the DOM, the view would change as the data changed. So I feel it is necessary for those studying VUE to understand how it is implemented
1. Vue bidirectional binding principle
var obj = {};
Object.defineProperty(obj, 'name', {
get: function() {
console.log('I was taken.')
return val;
},
set: function (newVal) {
console.log('I'm set.')
}
})
obj.name = 'fei'; // Obj is triggered when the name attribute is setsetVar val = obj. Name; // When obj's name property is obtained, the get method is triggeredCopy the code
We already know that Vue uses data hijacking to bind data. The core method is to hijacking attributes via Object.defineProperty(). When setting or fetching, we can use get or set methods if other triggering functions, This method is undoubtedly one of the most important and basic contents in this article.
2. Implement the simplest two-way binding
We know that data hijacking can be done with Object.defineProperty(), where the set method is triggered when the property is assigned.
<! doctype html> <html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="demo"></div>
<input type="text" id="inp">
<script>
var obj = {};
var demo = document.querySelector('#demo')
var inp = document.querySelector('#inp')
Object.defineProperty(obj, 'name', {
get: function() {
return val;
},
set: function(newVal) {// Inp. value = newVal; demo.innerHTML = newVal; } }) inp.addEventListener('input'.function(e) {// Assign the name attribute of obj to trigger the attributesetMethod obj.name = e.target.value; }); obj.name ='fei'; // Obj is triggered when the name attribute is setsetThis method </script> </body> </ HTML >Copy the code
This is the simplest implementation code, and of course there are a lot of problems.
3. How is VUE implemented
3.1 Observer is used to implement data hijacking with Object.defineProperty() for a loop of properties defined in data in each VUE to take advantage of the setter and getter in it, and then notify the subscriber, who will trigger its update method. Update the view.
3.2 We introduce why subscribers are required. In VUE, V-model, V-name, {{}}, etc., can display data. That is to say, if an attribute passes these three instructions, the HTML view of the corresponding three instructions must be changed whenever this attribute is changed. Thus, vUE adds a subscriber to a Dep every time there is such an instruction that might use bidirectional binding. The subscriber simply updates the data corresponding to its own instruction, i.e. V-model =’name’ and {{name}} have two corresponding subscribers, each managing its own place. Each time the set method of the property fires, the subscribers in the Dep are updated in a loop.
4. Vue code implementation
4.1 Observer implementation, which basically applies Object.defineProperty() to each vUE attribute as follows:
function defineReactive (obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function() {// Add subscriber watcher to topic object Depif(dep.target) {// JS's browser single-threaded feature ensures that the same listener uses dep.addSub(dep.target) at the same time; }return val;
},
set: function (newVal) {
if(newVal === val) return; val = newVal; console.log(val); Dep.notify (); // DeP loops through the respective update methods to update the view}})}function observe(obj, vm) {
Object.keys(obj).forEach(function(key) { defineReactive(vm, key, obj[key]); })}Copy the code
4.2 Compile:
The purpose of compile is to parse various instructions into real HTML.
function Compile(node, vm) {
if(node) {
this.$frag = this.nodeToFragment(node, vm);
return this.$frag;
}
}
Compile.prototype = {
nodeToFragment: function(node, vm) {
var self = this;
var frag = document.createDocumentFragment();
var child;
while(child = node.firstChild) { console.log([child]) self.compileElement(child, vm); frag.append(child); // Add all child nodes to fragment}return frag;
},
compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; // Node type is element (input element here)if(node.nodeType === 1) { var attr = node.attributes; // Parse attributesfor(var i = 0; i < attr.length; i++ ) {
if(attr[i].nodeName == 'v-model'Var name = attr[I]. NodeValue; var name = attr[I]. Node.addeventlistener (node.adDeventListener ('input'.function(e) {// Assign the corresponding data attribute to trigger the attributesetMethods [name] = vm e. arget. Value; }); new Watcher(vm, node, name,'value'); // Create a new watcher, which triggers the function to add subscribers to the deP array of the corresponding property,}}; } // The node type is textif(node.nodeType === 3) {
if(reg.test(node.nodeValue)) {
var name = RegExp.The $1; // Get the matching string name = name.trim(); new Watcher(vm, node, name,'nodeValue'); }}}}Copy the code
4.3 watcher implementation
function Watcher(vm, node, name, type) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.type = type;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function() { this.get(); this.node[this.type] = this.value; // The subscriber performs the corresponding operation}, // get the attribute value of data:function() {
console.log(1)
this.value = this.vm[this.name]; //触发相应属性的get
}
}Copy the code
4.4 Implement Dep to add subscribers for each attribute
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) { sub.update(); }}})Copy the code
This completes the two-way binding of the entire data.
Comb 5.
First we implement data hijacking with Object.defineProperty() for each vue attribute, assigning each attribute a management array DEP of the subscriber collection; V-model will add a subscriber, {{}} will add a subscriber, v-bind will add a subscriber, {{}} will add a subscriber, v-bind will add a subscriber, {{}} will add a subscriber, v-bind will add a subscriber, {{}} will add a subscriber, v-bind will add a listener for input, change the value will assign a value to the attribute, trigger the set method for the attribute, The subscriber array DEP is notified in the set method, and the subscriber array loops through each subscriber’s UPDATE method to update the view.