This article is based on x station in the first half of 2021 each major factory web core interview question analysis (second phase) – Road white, video more easy to understand!
Read about the principle of VUE responsiveness, with this note to record, to deepen the impression of vUE responsiveness principle
Before we can explain vue2’s response formula principle, we need to understand a few concepts, or core classes, and their respective roles
- Observe class, add getters/setters to data properties, hijack data, and distribute updates.
- There are many types of Watcher classes, such as computed Watcher and user Watcher.
- The Dep class is used to collect the dependencies of the current reactive object, each of which has a Dep instance. Dep.subs =watcher[], which is the Watcher dependency collected by the current responsive object DEP instance. When the data changes, the dep.notify method is triggered, which iterates through the subs array, calling each Watcher’s update method for a responsive update.
Well, after a brief understanding of the role of each core class, next to implement one by one, the file structure is as follows.
Index.html -- -- -- -- -- -- -- -- the HTML file, provide the root element index, js -- -- -- -- -- -- -- -- introduction of vue. Js. Add parameters watch. Js -- -- -- -- -- -- -- -- watcher class dep. Js -- -- -- -- -- -- -- -- dep class. Observe js -- -- -- -- -- -- -- -- the observer class compiler. Js -- -- -- -- -- -- -- -- used to compile instructions, such as v - HTML, V-model, V-text vue.js -------- main fileCopy the code
The first step is to finish the initial code of the index.html and index.js files
//index.js
import Vue from './vue.js'
const vm=new Vue({
el:'#app'.data: {msg:'hello word'.text:'
hahaha
'.html:' is bigger
'
},
methods: {fn(){
alert("111"); }}})console.log(vm);
//index.html
<body>
<div id="app">
{{msg}}
<input type="text" v-model='msg'>
<button type="button" v-on:click='fn'>button</button>
<div v-text='text'></div>
<div v-html='html'></div>
</div>
<script src="./index.js" type="module"></script>
</body>
Copy the code
- We know that Vue comes out of new, so we know that Vue is a class declared in class.
- The JS import of HTML says type=’module’, which tells the browser that we are using ESM syntax. Mainstream browsers support this declaration.
The second step is to write the body code of vue.js. There are a few things that need to be implemented
- Bind the passed options and related properties to the Vue instance
- Determines the el attribute passed in
- From the VM instance of VUE, you can see that Vue binds a copy of the attributes in data to the Vue instance
import Observe from "./observe.js";
import Complier from "./compiler.js";
export default class Vue{
constructor(options){
this.$options=options;
this.$data=options.data;
this.$methods=options.methods;
// Initialize el to judge the passed root element
this.initElement(options);
// Bind data elements to vue instances
this._proxyData (this.$data);
// Data hijacking
new Observe(this.$data);
// Compile the template
new Complier(this);
}
initElement(options){
if(typeof options.el==='string') {this.$el=document.querySelector(options.el);
}else if(options.el instanceof HTMLElement){
this.$el=options.el;
}
// The type of the root element passed in is incorrect
if(!this.$el){
throw new Error('Please pass in CSS Selector or HTMLElement'); }}_proxyData(data){
// Bind attributes in data to vue instances via Object.defineProperty
Object.keys(data).forEach(key= >{
Object.defineProperty(this, key, {
// can be enumerated, i.e. can be looped out
enumerable:true.// Indicates that corresponding configurations can be performed
configurable:true.get(){
return data[key];
},
set(newValue){
if(newValue===data[key]){
return} data[key]=newValue; }})})}}Copy the code
The _proxyData function is used to bind attributes to vUE instances, not to collect dependencies and distribute updates, so there is no recursive hijacking of each value.
The third step is to sort out the architecture
At this time, we do not rush to implement the rest of the class, but to write the architecture first, to better comb out our knowledge, of course, when writing the architecture notes to add, or when writing, forget what this is used for, the loss outweighed the gain.
- Dep class
// The dep class is used to hold the watch class, so we start with an array to hold the watch
export default class Dep{
constructor(){
/ / watch array
this.subs=[];
}
// To put the subs array, you need an add method
addSubs(watcher){}// Used to distribute updates
notify(){}Copy the code
- Watch the class
export default class Watcher{
constructor(vm, key, cb){}update(){}}Copy the code
Three parameters are required: the instance of the VM, the property name of data, and the callback function
- Observe the class
export default class Observe{
constructor(data){}transver(data){}}Copy the code
We need a data parameter, which is the data passed in. We use Object.defineProperty to hijack each property in the data, plus getters/setters. This is where we implement our dependency collection and distribution of updates
- Complier class
export default class Complier{
constructor(vm){}// Iterate over the elements and edit the template
compile(el){}}Copy the code
With the basic architecture of the four major categories completed, adding the corresponding code on top of this is one step closer to completion, doesn’t it seem easy?
The fourth step is the overall implementation of deP class
// The dep class is used to hold the watch class, so we start with an array to hold the watch
export default class Dep{
constructor(){
/ / watch array
this.subs=[];
}
// To save into the subs array, you need an add method
addSubs(watcher){
if(watcher&&watcher.update){
this.subs.push(watcher); }}// Used to distribute updates
notify(){
// Iterate through the subs array, calling each watcher's updatae method
console.log(this.subs);
this.subs.forEach(watcher= >{ watcher.update(); }}})Copy the code
It doesn’t seem easy but let’s think about two things.
- When is the DEP class instantiated? Where do I addSubs?
- When does the DEP class call notify?
Step 5: The whole implementation of the Watcher class
import Dep from "./dep.js";
export default class Watcher{
/ * * * *@param {*} Vm vm instance, get the desired value *@param {*} Property name of key data, get new value, old value *@param {*} Call the callback function */ when cb updates
constructor(vm, key, cb){
this.vm=vm;
this.key=key;
this.cb=cb;
// Why add a watcher instance to dep.target
Dep.target=this;
// Get the old value
// Also note that this is where the variable's get method is triggered
this.oldValue=vm[key];
Dep.target=null;
}
update(){
// Since the data has been updated, we get the latest value
let newValue=this.vm[this.key];
if(newValue===this.oldValue){
return;
}
this.cb(newValue); }}Copy the code
Don’t worry, the real complexity lies in the Compiler module, where there is also a problem.
- Why add a Watcher instance to dep.target? In order to maintain only one watcher at a time, because in computed and watch, more than one Watcher will be added, which will easily cause data disorder. Therefore, only one Watcher at a time can ensure that the watcher is added normally.
The sixth step is the overall implementation of Compiler
This step will be more complicated, mainly for the element node analysis, traversal. The code is long, so don’t panic.
import Watcher from "./watch.js";
export default class Complier{
constructor(vm){
this.vm=vm;
this.compile(vm.$el);
}
// Iterate over the elements to replace the template
compile(el){
// Get the element's child node, an array of classes
const childNodes=el.childNodes;
Array.from(childNodes).forEach(node= >{
// Determine the node type
if(this.isTextNode(node)){
// Text node
this.compileText(node);
}else if(this.isElementType(node)){
// Element node
this.compileElement(node);
}
if(node.childNodes&&node.childNodes.length>0) {this.compile(node); }})}// Determine the text node
isTextNode(node){
return node.nodeType===3;
}
// Determine the element node
isElementType(node){
return node.nodeType===1;
}
// Text node compiles
compileText(node){
let reg=/ \ {\ {(. +?) \} \} /;
let value=node.textContent;
if(reg.test(value)){
const key=RegExp.$1.trim();/ / get the MSG
node.textContent=value.replace(reg, this.vm[key]);// The replacement succeeded
// Data needs to change dynamically, so it depends on collection
new Watcher(this.vm, key, (newValue) = >{ node.textContent=value.replace(reg, newValue);; }}})// Element node compiles
compileElement(node){
if(node.attributes.length>0) {Array.from(node.attributes).forEach(attr= >{
/ / the property name
const attrName=attr.name;
// Check whether it starts with v-
if(this.isVStartsWith(attrName)){
// Check whether there are: signs, such as V-on :click V-model
const directiveName=attrName.indexOf(':') > -1? attrName.substr(5):attrName.substr(2);
// get the value, such as MSG where v-model=' MSG '
let key=attr.value;
this.update(node, key, directiveName); }}}})// Start with v-
isVStartsWith(attr){
return attr.startsWith('v-');
}
// concatenate the corresponding function
update(node, key, directiveName){
const updaterFn=this[directiveName+'Updater'];
updaterFn && updaterFn.call(this, node, this.vm[key], key, directiveName);
}
//v-model
modelUpdater(node, value, key){
// corresponds to input
node.value=value;
new Watcher(this.vm, key, (newValue) = >{
node.value=newValue;
})
node.addEventListener('input'.() = >{
// Triggers the setter
this.vm[key]=node.value; })}//v-text
textUpdater(node, value, key){
node.textContent=value;
new Watcher(this.vm, key, (newValue) = >{ node.textContent=newValue; })}//v-html
htmlUpdater(node, value, key){
node.innerHTML=value;
new Watcher(this.vm, key, (newValue) = >{ node.innerHTML=newValue; })}//v-on:click
clickUpdater(node, value, key, directiveName){
node.addEventListener(directiveName, this.vm.$methods[key]); }}Copy the code
Ah, done with this step, next is the last step, come on, almost done!!
Step 7 Implement the Observe class
import Dep from './dep.js'
export default class Observe{
constructor(data){
this.transver(data);
}
transver(obj){
if(! obj ||typeofobj! = ='object') {return
}
// Iterate over the listening data
Object.keys(obj).forEach(data= >{
this.defineReactive(obj, data, obj[data]); })}defineReactive(obj, data, value){
// Maybe an object
this.transver(value);
// Instantiate dep here
let dep=new Dep();
/ / save this
const that=this;
Object.defineProperty(obj, data, {
enumerable:true.configurable:true.get(){
// Add the dependency here and get the watcher bound to the Dep
Dep.target && dep.addSubs(Dep.target);
// Note that obj[data] is not sufficient for this step, which results in the loop's get value
return value;
},
set(newValue){
if(value===newValue){
return;
}
value=newValue;
// It can be an object
that.transver(newValue);
// Post updates heredep.notify(); }}}})Copy the code
At this point, caused by great success, can see the complete down really is perseverance commendable, might as well follow the video handwritten again, memory will be more profound.