preface
This article was written back in November 2019
I have been using Vue technology stack for 2 years. I am very familiar with various apis, attributes and built-in component encapsulation in it. I have always known that the principle of two-way data binding is realized by data hijacking combined with publish and subscribe. But the theory is always the theory, can not help but still move a little hand; Please feel free to laugh.
Refer to the article Jane books: https://www.jianshu.com/p/23180880d3aa
Github source code – to a little star I don’t mind oh biu ~ biu ~
An overview of the
Let’s take a look at a picture, I believe you have seen this legend in many blogs, post bar, forum and so on, so what does it mean? Let me tell you more.
Let me make a brief description of this picture:
First, create an instance object that triggers the compile parse directive and the Observer listener, respectively
The compile parse directive circularly recursively parses directives such as v-Model, initializes data binding data, and creates a subscriber watcher for each data attribute that is added to the component’s unique Dep subscriber
The observer listener uses the set and get methods in the description property of the object.defineProperty () method to listen for data changes.
The get method is triggered whenever an instance object is created and a DOM node is generated, so: When the compile parse directive is compiled, each data attribute in turn is added a subscriber watcher to the subject object Dep (Dep subscriber collection, we call it subscribers), source dep.depend()
The set method notifnotifydep subscribers that the data has changed and watcher triggers the corresponding update view. Source dep.update()
That’s the simple explanation. Okay, so without further ado, let’s go to the code:
Code practice
The parser Compile
If the node has v-Model, V-ON, etc., then the parser initializes the template data of the node, so that it can be displayed in the view. At the same time, the corresponding subscriber (Watcher) is initialized.
/* Step 1: create document fragment, hijack all dom nodes, redraw DOM node 2, redraw DOM node 2, initialize document fragment binding data to compile document 3, create a watcher */ for each node
function getDocumentFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
/* while (child = node.firstChild) = child = node.firstChild while (child) */
compile(child, vm);
flag.appendChild(child);
}
node.appendChild(flag);
}
function compile(node, vm) {
/* nodeType returns a number indicating the current nodeType: 1 Element for Element, 2 Attr for attribute Text, and 3 EntityReference for Text in the Element or attribute. For more information, see the documentation */
if (node.nodeType === 1) {
// Get the attr attribute of the current element
var attr = node.attributes;
for (let i = 0; i < attr.length; i++) {
// nodeName is the name of the attR attribute key, which matches the custom V-M
if (attr[i].nodeName === 'v-m') {
V -m = "test"
let name = attr[i].nodeValue;
// The current node input event
node.addEventListener('keyup'.function (e) {
vm[name] = e.target.value;
});
Data [name] = vm.data['test'] = MVVM
node.value = vm.data[name];
// Finally remove the v-M attribute from the tag
node.removeAttribute('v-m');
// Create a watcher for each node
new Watcher(vm, node, name, "input"); }}/* Continue to recursively call the document compilation to implement view updates; * /
if (child = node.firstChild) {
/* if (child = node.firstChild) = child = node.firstChild */compile(child, vm); }}if (node.nodeType === 3) {
let reg = / \ {\ {(. *) \} \} /;
if (reg.test(node.nodeValue)) {
let name = RegExp.$1.trim();
node.nodeValue = vm.data[name];
// Create a watcher for each node
new Watcher(vm, node, name, "text"); }}}Copy the code
Monitor the Observer
It is used to hijack and listen on all properties and notify subscribers if there are changes
/* Step 2: obtain a key observer for the current instance Object (data, current instance Object)
function observe(data, vm) {
Object.keys(data).forEach(function (key) {
defineReactive(vm, key, data[key]);
});
}
function defineReactive(vm, key, val) {
/* object.defineProperty obj Specifies the Object on which properties are to be defined. Prop The name of the property to define or modify. Descriptor Specifies the property descriptor to be defined or modified. There are many descriptors, including we're going to use set, get methods */
var dep = new Dep();
Object.defineProperty(vm, key, {
get: function () {
/* if (Dep.target) dep.addSub(Dep.target); See this code is no different. For every DOM node that is generated, it goes to the get method and adds a subscriber to the topic object Dep */ for each node
if (Dep.target) dep.addSub(Dep.target);
console.log(val)
return val;
},
set: function (newValue) {
if (newValue === val) return;
val = newValue;
console.log(val + "= >" + newValue)
// Notify all subscribersdep.notify(); }}); }Copy the code
The subscriber Watcher
Each Watcher is bound to an Update. The Watcher can be notified of property changes and perform the corresponding update to update the view.
/* Step 3: implement a watcher observer/subscriber add method update render View 2. The two methods used to collect subscriber messages for the subscriber prototype mount are addSub to add a subscriber notify the update method */ to notify the subscriber of data changes
function Watcher(vm, node, name, nodeType) {
Dep.target = this;
this.vm = vm;
this.node = node;
this.name = name;
this.nodeType = nodeType;
this.update();
console.log(Dep.target)
Dep.target = null;
}
Watcher.prototype = {
update: function () {
This. vm points to an instance object of the current DOM, rendering the page with a value assigned to nodeType */
if (this.nodeType === 'text') {
this.node.nodeValue = this.vm[this.name]
}
if (this.nodeType === 'input') {
this.node.value = this.vm[this.name]
}
}
}
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
Vue instance
/* Create a constructor and generate an instantiated object VM */
function Vue(o) {
this.id = o.el;
this.data = o.data;
observe(this.data, this);
getDocumentFragment(document.getElementById(this.id), this);
}
var vm = new Vue({
el: 'app'.data: {
msg: 'HiSen'.test: 'Hello,MVVM'}});Copy the code
Maybe you didn’t see why at the end, there was a time when I was like you, looking around, just a few pieces of code; In fact, I also reference, try to figure out, debugging, finally successful;
Suggestion: take down my source code, oneself run a run, have a look, is a mule is a horse out of the walk. [go for a walk]
Welcome to like, a little encouragement, a lot of growth
A link to the
- Front-end visualization platform implementation scheme
- Vue3 10 minutes takes you seconds to vue3
- Vue template compilation principle
- The vue constructor extends
- How does VUEX state management work
- Vue-router source analysis principle
- Vue [how to listen for array changes]
- Handwritten Vue response [object.defineProperty]
- Vue responsive source sharing