preface
Hello everyone, I am console without log, a sophomore junior college student. This article will share with you the principle of vUE’s data-driven view, as well as the knowledge of template compilation. I will write this article very simply, trust me, you will understand.
In this article, I mainly explain the logic of the principle from requirements, flow charts and codes. Now I will start to write the article without more words.
Step into the project’s essentials
closure
Scopes are divided into global scopes and local scopes
Variables in the local scope are collected by the garbage collection mechanism after the execution of the scoped method
function run() {
let a = 11
return a
}
let num = run() // When this code is done, variable A in the local scope of the run method is disposed of by the garbage collection mechanism
let num2 = run() // When this code is executed, a local scope is recreated and a variable a is generated, which is disposed of again by the garbage collection mechanism when the function completes
Copy the code
function run () {
let a = 11
let b = 12
function asd() {
a = a+1
return a
}
return asd
}
let me = run() / / at this point, in accordance with the original plan, run the local scope of variables can be carried in the run method, garbage collection will be recycled, but found that a variable is used by another local scope, then can produce an effect, is run the local scope of other variables will be to get rid of garbage collection mechanism, The A variable will be stored in memory.
let num = me() // The value of variable A is read in memory and returned by incrementing. The answer is no, a is still in memory and the value of num here is 12
console.log(num); // -- 12
let num2 = me() // It is obvious that the me method returned twice with the same memory address as the a variable
console.log(num2); // -- 13
Copy the code
The same is true for function arguments, so a function declares a variable, so it declares a variable in its local scope without using the var let const flag. Look at the code
function run(a) {
function asd() {
a += 1
return a
}
return asd
}
let me = run(1)
let num = me()
console.log(num); // -- 2
let num2 = me()
console.log(num2); // -- 3
// It is possible to express the parameters of a function.
Copy the code
Object.defineProperty
Can look at the link Object directly. DefineProperty () – JavaScript | MDN (mozilla.org), if you don’t understand again to see my explanation.
This method directly defines a new property on an object, or modifies an existing property of an object, and returns the object.
let obj = {}
Object.defineProperty(obj, 'a', {
value: '111',})console.log(obj); // -- { a : '111' }
Object.keys(obj).forEach(key= > console.log(key)); // -- 'a'
// This method defines a new attribute directly on an object and can set the value
Copy the code
let obj = {}
Object.defineProperty(obj, 'a', {
value: '111'.enumerable: false // We can set whether this key is enumerable or not, if it is false we can use either for... in... Keys and so on will not read it
})
console.log(obj); // -- { a : '111' }
Object.keys(obj).forEach(key= > console.log(key)); // -- no print
for (let key in obj) {
console.log(key); // No print
}
Copy the code
let obj = {}
Object.defineProperty(obj, 'a', {
value: '111'.writable: false // Sets whether the value can be changed, or not if it is false
})
console.log(obj); // -- { a : '111' }
obj.a = '222'
console.log(obj) // -- { a : '111' }
Copy the code
Get and set down here
Get: represents the function that fires when the property is used, and the return value of the function is the value obtained when the property is used
Set: the function that fires when the property is set to a new value. The function takes one argument, which is the new value you set
// The following image is the print of the code block after it runs. It is found that the maximum stack memory is exceeded
let obj = {
a: 11
}
Object.defineProperty(obj, 'a', {
get() {
console.log('OBj. A is used');
return 'I'm a'
},
set(val) {
// console.log(val) // -- 12
obj.a = val; // This is because if you assign obj. A to obj}})let a = obj.a // -- 'I am a'
obj.a = 12
Copy the code
How to solve this problem, see the following code
Solution 1
// We can use a variable to accept the new value and return the variable we set the new value to when we get
let obj = {
a: 11
}
let temp;
Object.defineProperty(obj, 'a', {
get() {
console.log('OBj. A is used');
return temp
},
set(val) {
temp = val;
}
})
obj.a = 12
let as = obj.a
console.log('As has the value:'.as);
// Here is the result
Copy the code
Solution 2
This is where closure is used, remember we said that the argument to a function is actually declaring a new variable, and the argument variable here is equivalent to the temp in our code block up there and it’s just a mediator
let obj = {
a: 11
}
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newValue) {
value = newValue;
}
})
}
defineReactive(obj)
let as = obj.a
console.log(as);
obj.a = 22
console.log(obj);
Copy the code
Design pattern -- Subscribe publish pattern
I think this article is quite well written, you can go to see the observer mode vs publish and subscribe mode – Zhihu.com after reading each article, come back to see my explanation
Publish and subscribe is one to many. Let’s take a real life example
“There is a sales office, the sales office has a boss (DEP) sales office has three salespeople (watcher) now Zhang SAN, Li Si, Wang Wu to buy a house, they were respectively received by a sales office, but now there is no house search some sales let her corresponding customers go back and so on notice, suddenly one day, the house has, Dep informs every Watcher that a house is available, Watcher informs all of his customers that a house is available, and all of their customers come and sell their houses when they hear about it. This is an example of a publish and subscribe model.”
Building the base Directory
- First we create a new folder 📁
my-vue
(My VUE)- So let’s do
npm init -y
Initialize our project- use
yarn add vite
Install the Vite environment- Create files in the root directory
index.js
andindex.html
And write the text we want to see in the HTML- Run from the command line
vite
Now that our project is running, click on the link to see the page we wrote in the HTML
The overall function
Since we’re only looking at data-driven views, we’ll just implement data
The theoretical knowledge
Vue uses a combination of data hijacking and the publisheer-subscriber pattern to realize the responsivity of data. It hijabs the setter and getter of data through Object.defineProperty to publish messages to subscribers when data changes. The subscriber receives the message and processes it accordingly. .
Theory of implementation
The get method is first registered in the Observer process, which is used for “dependency collection.” It will have a Dep object in its closure that will hold an instance of the Watcher object. The process of “dependency collection” is to store the Watcher instance into the corresponding Dep object. The get method stores the current Watcher object (dep.target) in its subs (addSub). When data changes, set calls notify of the Dep object to notify all of its internal Watcher objects of view updates.
In my own words, I used the Object.defineProperty API to add get (use it) and set (change it) methods to all attributes in data. In Compiler class, variables in the data we use on the page are bound with a Watcher listener, and this listener is the general manager of Dep. When the page data changes, the set method is triggered to notify the corresponding manager. Let him tell each Watcher listener to trigger their own page update method.
Data driven prologue
-
Write the basic configuration of the vue class in index.js
- Save the DOM element to mount. By default we bind the Dome element with the ID app
- Execute the data function passed in to retrieve the data object it returns
class Vue { constructor(option) { this.$el = option.el ? document.querySelector(option.el) : document.querySelector('#app') this.$data = option.data() } } Copy the code
-
We want to be able to access data directly from data using this
- In the Vue class
_proxyData
Method that takes a parameter (data) - through
Object.keys
The getKey () method extracts all the keys of an object into an array - ForEach iterates through an array of data key values
- through
Object.defineProperty
Method implementation can implement this access data requirements - Execute _proxyData in the constructor
class Vue { constructor(option) { this.$el = option.el ? document.querySelector(option.el) : document.querySelector('#app') this.$data = option.data() this._proxyData(this.$data) } _proxyData(data) { Object.keys(data).forEach(key= > { Object.defineProperty(this, key, { get() { return data[key] }, set(newValue) { if(newValue === data[key]) return data[key] = newValue } }) }) } } Copy the code
So before we get down to this, let’s go ahead and hit the code and see if it works
let vue = new Vue({ data() { return { name: 'I'm console without log'}}})console.log(vue.$data.name); // I have no log for console console.log(vue.name); // I am console without log Copy the code
No problem at all. That’s it. Heh heh.
- In the Vue class
-
Implement the Observer class Observer
- Create observer.js in the root directory, and create the Observer class
- We used
Object.defineProperty
Just to realize that this can access data, this time we re using data in dataObject.defineProperty
, this time is to listen to data changes in data
export class Observer { constructor(data) { this.walk(data) } walk(data) { if(! data ||typeofdata ! = ='object') return Object.keys(data).forEach(key= > { this.defineReactive(data, key, data[key]) }) } defineReactive(obj, key, value) { const self = this self.walk(value) Object.defineProperty(obj, key, { get() { return value }, set(newValue) { if(newValue === value) return value = newValue self.walk(value) } }) } } Copy the code
Just a moment, LET me draw a flow chart and explain it to you……
Ok! finished
There are no changes to the view yet, don’t worry because there are still three classes to write, we will now use the Observer class for Vue
import {Observer} from './Observer' class Vue { constructor(option) { this.$el = option.el ? document.querySelector(option.el) : document.querySelector('#app') this.$data = option.data() this._proxyData(this.$data) new Observer(this.$data) } _proxyData(data) { Object.keys(data).forEach(key= > { Object.defineProperty(this, key, { get() { return data[key] }, set(newValue) { if(newValue === data[key]) return data[key] = newValue } }) }) } } Copy the code
Now that we’ve been writing the logic for a while but can’t see it in action, let’s implement a compiler that can see the page directly.
-
Compiler class
- To achieve the DOM text node compilation
- Create it in the root directory
Compiler.js
file
export class Compiler { constructor(vm) { this.vm = vm this.el = vm.$el this.compile(this.el) } compile(el) { // Get all the child nodes of the element let childNodes = [...el.childNodes] childNodes.forEach(node= > { // If it is a text node if(this.isTextNode(node)) { this.compileText(node) // If it is an element node } else if(this.isElementNode) { } // Determine if there are any children below the element, and recurse if there are if(node.childNodes && node.childNodes.length) { this.compile(node) } }) } compileText(node) { let val = node.textContent let reg = / \ {\ {(. +?) \} \} / if(reg.test(val)) { // Get the value inside the double braces let key = RegExp.$1.trim() node.textContent = val.replace(reg, this.vm[key]) } } compileElement(node) { // Don't write anything here } isTextNode(node) { return node.nodeType === 3 } isElementNode(node) { return node.nodeType === 1}}Copy the code
I still have to write a flow chart. Hold on!
Ok, there we go again
Hey hey this time we can see the effect immediately !!!!!!!!!
We do this in index.html!
<div id="app"> {{name}} </div> <script type="module" src="./index.js"></script> Copy the code
Use the Compiler class in the Vue class and write it that way
import {Observer} from './Observer' import {Compiler} from './Compiler' class Vue { constructor(option) { this.$el = option.el ? document.querySelector(option.el) : document.querySelector('#app') this.$data = option.data() this._proxyData(this.$data) new Observer(this.$data) new Compiler(this) // Use it } _proxyData(data) { Object.keys(data).forEach(key= > { Object.defineProperty(this, key, { get() { return data[key] }, set(newValue) { if(newValue === data[key]) return data[key] = newValue } }) }) } } // Write it here new Vue({ data() { return { name: 'I'm console without log'}}})Copy the code
Next look at our page !!!!
It’s such a good thing. It’s awesome
The following two classes are a bit tricky to explain, so my solution is to write them all in one go and draw a whole flow chart! Get it in seconds.
-
Dep class
- Create it in the root directory
Dep.js
- with
addSub
Methods can be used in the presentDep
ObjectWatcher
Subscribe operation; - with
notify
Method notice presentDep
The object’ssubs
All of theWatcher
Object triggers the update operation
export class Dep { constructor() { this.subs = [] } addSub(sub) { if(sub && sub.update) { this.subs.push(sub) } } notify() { this.subs.forEach(sub= > { sub.update() }) } } Copy the code
- Create it in the root directory
-
Watcher class
- Create it in the root directory
Watcher.js
- It adds a listener to each variable used on the page and receives notification whenever data changes in data to update the page. If this variable is not used on the page, then it would be a waste of time to have it bind the view update method. —-
Please pay attention to this sentence
import {Dep} from './Dep' export class Watcher { constructor(vm, key, cd) { this.vm = vm this.key = key this.cd = cd Dep.nb = this this.oldValue = vm[key] Dep.nb = null } update() { let newValue = this.vm[this.key] if(newValue === this.oldValue) return this.cd(newValue) } } Copy the code
Now let’s use these two classes
Watcher is used in Compiler
import { Watcher } from "./Watcher" export class Compiler { constructor(vm) { this.vm = vm this.el = vm.$el this.compile(this.el) } compile(el) { let childNodes = [...el.childNodes] childNodes.forEach(node= > { if(this.isTextNode(node)) { this.compileText(node) } else if(this.isElementNode) { } if(node.childNodes && node.childNodes.length) { this.compile(node) } }) } compileText(node) { let val = node.textContent let reg = / \ {\ {(. +?) \} \} / if(reg.test(val)) { let key = RegExp.$1.trim() node.textContent = val.replace(reg, this.vm[key]) new Watcher(this.vm, key, (newValue) = > { // In this case, the variable used by the page will be set to update the page method node.textContent = newValue }) } } compileElement(node) { // Don't write anything here } isTextNode(node) { return node.nodeType === 3 } isElementNode(node) { return node.nodeType === 1}}Copy the code
Dep is used in the Observer
import { Dep } from "./Dep" export class Observer { constructor(data) { this.walk(data) } walk(data) { if(! data ||typeofdata ! = ='object') return Object.keys(data).forEach(key= > { this.defineReactive(data, key, data[key]) }) } defineReactive(obj, key, value) { const self = this self.walk(value) let dep = new Dep() / / here Object.defineProperty(obj, key, { get() { Dep.nb && dep.addSub(Dep.nb) / / here return value }, set(newValue) { if(newValue === value) return value = newValue self.walk(value) dep.notify() / / here}}}})Copy the code
Look at the theory again, and then look at the whole flow chart
The get method is first registered in the Observer process, which is used for “dependency collection.” It will have a Dep object in its closure that will hold an instance of the Watcher object. The process of “dependency collection” is to store the Watcher instance into the corresponding Dep object. The get method stores the current Watcher object (dep.target) in its subs (addSub). When data changes, set calls notify of the Dep object to notify all of its internal Watcher objects of view updates.
As a reminder, if the data in the data is changed, the set method bound to the Observer will be run, which can be combined with my flow chart
play
This is the end of the data impact view principle, so we need to bind events to change the data in the data, which I can’t bear. Let’s play with it
Remember when we decided there was nothing to do if it was an element node, what did we do next
Add $methods to the Vue class
import {Observer} from './Observer' import {Compiler} from './Compiler' class Vue { constructor(option) { this.$el = option.el ? document.querySelector(option.el) : document.querySelector('#app') this.$data = option.data() this.$methods = option.methods ? option.methods : {} this._proxyData(this.$data) this._initMethods(this.$methods) this._proxyMethods(this.$methods) new Observer(this.$data) new Compiler(this)}_proxyData(data) { Object.keys(data).forEach(key= > { Object.defineProperty(this, key, { get() { return data[key] }, set(newValue) { if(newValue === data[key]) return data[key] = newValue } }) }) } _proxyMethods(methods) { Object.keys(methods).forEach(key= > { Object.defineProperty(this, key, { get() { return methods[key] }, set(newValue) { if(newValue === methods[key]) return; methods[key] = newValue } }) }) } _initMethods(methods) { Object.keys(methods).forEach(key= > { methods[key] = methods[key].bind(this)}}}Copy the code
Add the compileElement method in Compiler
compileElement(node) { let self = this, props = node.getAttributeNames(), reg = /^c-(\w+)/, reg2 = / / / ((. +) \), reg3 = / \ '\' (. +), reg4 = /^\d+$/, reg5 = /(\w+)\(/, value, methodName props.forEach((key) = > { if (reg.test(key)) { methodName = RegExp.$1.trim() } let qian = node.getAttribute(key) if (reg2.test(qian)) { value = RegExp.$1.trim() if (reg4.test(value)) { value = parseInt(value) } if (reg3.test(value)) { value = RegExp.$1.trim } } if (reg5.test(qian)) { qian = RegExp.$1.trim } console.log(qian) console.log(methodName) console.log(value) node.addEventListener(methodName, function (e) { self.vm[qian](value ? (self.vm[value] ? self.vm[value] : value) : e) }) }) } Copy the code
Now we can bind methods to the instantiated Vue object in the index.js file
let vue = new Vue({ data() { return { name: 'I'm console without log'}},methods: { run() { this.name = 'This is really great.'}}})Copy the code
Add to the HTML file
<div id="app"> <div> {{name}} </div> <button c-click="run">change</button> </div> <script type="module" src="./index.js"></script> Copy the code
- Create it in the root directory
conclusion
This is my first article in nuggets, in fact, THE first article I tangled for a long time, do not know what to post, want to post this look that the blogger has written and write very well, ah a little inferiority. But I want to think everyone’s way of learning is not the same, may not see my can understand hey hey
I would like to give special thanks to Pongo for his guidance. He really helped me a lot and he is very kind. This is punge nuggets address, I hope you also pay more attention to Punge, really very powerful
Gitee’s warehouse address
If you like this style of my article please like and follow, there are comments let me know!!
Bye ~ see you next time