100 lines of code understanding and analysis vue2.0 responsive architecture, 100 lines of vue2.0
Long-winded before sharing
I have previously introduced how to implement observer and watcher in vue1.0. I wanted to continue writing, but vue2.0 was born... so let's look at vue2.0 directly. This article was finally written after being shared in the company. We use the most streamlined code to restore the vue2.0 responsive architecture.
How to Implement observer and watcher in the previous vue source code analysis can be used as a reference for this sharing.
But it doesn't matter if you don't know it, but it's best to know about Object. defineProperty.
What does this article share?
Understand the response architecture of vue2.0, as shown in the following figure.
This article introduces one of the reasons why he is faster than react.
Implementation of this score
Const demo = new Vue ({data: {text: "before ",}, // The corresponding template is <div> <span >{{ text }}</span> </div> render (h) {return h ('div ',{}, [h ('span ', {}, [this. _ toString _ (this. text)])} setTimeout (function () {demo. text = "after"}, 3000)
The corresponding virtual dom will
<Div> <span> before </span> </div>: <div> <span> after </span> </div>
Okay. Let's get started !!!
Step 1,All attributes under data are changed to observable.
Come and see the code first
Class Vue {constructor (options) {this. $ options = options this. _ data = options. data observer (options. data, this. _ update) this. _ update ()} _ update () {this. $ options. render ()} function observer (value, cb) {Object. keys (value ). forEach (key) => defineReactive (value, key, value [key], cb)} function defineReactive (obj, key, val, cb) {Object. defineProperty (obj, key, {enumerable: true, retriable: true, get: () =>{}, set: newVal =>{ cb ()}})} var demo = new Vue ({el: '# demo', data: {text: 123,}, render () {console. log ("I want to render")}) setTimeout (function () {demo. _ data. text = 444}, 3000)
For a good demonstration, we only consider the simplest situation. If we look at the vue source code analysis and how to implement observer and watcher, we may be able to understand it well, but it doesn't matter. Let's say it in a few words, the function of this Code is
Var demo = new Vue ({el: '# demo', data: {text: 123,}, render () {console. log ("I want to render ")}})
All the attributes in data are placed in the observer, and then the attributes in data, such as text to be changed, cause _ update () function call and re-rendering. How is this done, we know that the value is actually changed when the value is assigned, right? When I assign a value to the text under data, the set function will be triggered. At this time, it is okay to call _ update,
setTimeout(function(){ demo._data.text = 444 }, 3000)
Demo. _ data. text is good without demo. text. It doesn't matter. We add a proxy.
_proxy(key) { const self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) }
Add the following sentence to the constructor of Vue.
Object. keys (options. data). forEach (key => this. _ proxy (key ))
First, we will find a problem. Any change in the value of any attribute in data will cause
_ Triggering of update and re-rendering. This attribute is obviously not accurate enough.
Step 2,Explain in detail why the first step is not accurate enough
For example, consider the following code:
new Vue({ template: ` <div> <section> <span>name:</span> {{name}} </section> <section> <span>age:</span> {{age}} </section> <div>`, data: { name: 'js', age: 24, height: 180 } }) setTimeout(function(){ demo.height = 181 }, 3000)
The template only uses the two attributes name and age on data. But when I change the height, will the code in the First Step trigger re-rendering? Yes !, However, you do not need to trigger re-rendering. This is the problem !!
Step 3,How to solve the above problems
Brief Introduction to virtual DOM
First, the template is finally compiled into the render function (for details, do not expand it, I will talk about it later). Then, after the render function is executed, a virtual DOM is obtained, for better understanding, we write the simplest virtual DOM.
function VNode(tag, data, children, text) { return { tag: tag, data: data, children: children, text: text } } class Vue { constructor(options) { this.$options = options const vdom = this._update() console.log(vdom) } _update() { return this._render.call(this) } _render() { const vnode = this.$options.render.call(this) return vnode } __h__(tag, attr, children) { return VNode(tag, attr, children.map((child)=>{ if(typeof child === 'string'){ return VNode(undefined, undefined, undefined, child) }else{ return child } })) } __toString__(val) { return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val); } } var demo = new Vue({ el: '#demo', data: { text: "before", }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } })
Run the command to output
{Tag: 'div ', data :{}, children: [{tag: 'span', data :{}, children: [{children: undefined, data: undefined, tag: undefined, text: ''// The string before is normal, because we do not write the proxy code for demonstration, so it is blank here}]}
This is the simplest virtual DOM. The tag is the html tag name, and the data contains attributes on tags such as class and style. childen is the sub-node. We will not discuss the virtual DOM.
Back to the start question, that is, I have to know which variables in the vue instance are depended on in the render function (only consider render, because the template will also help you compile it into render ). It's a bit difficult to describe. Check the code.
var demo = new Vue({ el: '#demo', data: { text: "before", name: "123", age: 23 }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } })
Like this code, the render function only depends on text and does not depend on name and age. Therefore, when text is changed, we will automatically trigger the render function to make it generate a virtual DOM and it will be OK (the rest is the comparison between the virtual DOM and the previous virtual DOM, and then operate the real DOM. We can only talk about it later ), so we will officially consider how to do it.
Step 3,'Touch' gets dependency
Return to the top figure. After we know that the attribute setting on data is defineReactive, modifying the value on data triggers the set.
Then, get will be triggered when we take the data value.
Yes, we can do something above. Let's first execute render. Let's see which attributes on data trigger get, so we don't know which variables on data will render depend on.
Then I do some operations on these variables. Every time these variables change, we trigger render.
The above steps are simply described as computing dependencies.
(In fact, it is not only render, but the change of any variable is caused by other variables. The above method can be used, that is, the principles of computed and watch, which is also the core of mobx)
Step 1:
We write a class that depends on the collection. Every object on data may be depended by the render function. Therefore, every attribute initializes it when defineReactive, which is simply like this.
Class Dep {constructor () {this. subs = []} add (cb) {this. subs. push (cb)} y () {console. log (this. subs); this. subs. forEach (cb) => cb ()} function defineReactive (obj, key, val, cb) {const dep = new Dep () Object. defineProperty (obj, key, {// omitted })}
Then, when the render function is executed to 'touch' dependencies, the dependent variable get will be executed, and then we can add this render function to subs.
When we set, we will execute notify to execute all the functions in the subs array, which contains the execution of render.
This completes the entire graph, so we can display all the code.
Function VNode (tag, data, children, text) {return {tag: tag, data: data, children: children, text: text} class Vue {constructor (options) {this. $ options = options this. _ data = options. data Object. keys (options. data ). forEach (key => this. _ proxy (key) observer (options. data) const vdom = watch (this, this. _ render. bind (this), this. _ update. bind (this) console. log (vdom) }_proxy (key) {const self = th Is Object. defineProperty (self, key, {retriable: true, enumerable: true, get: function proxyGetter () {return self. _ data [key]}, set: function proxySetter (val) {self. _ data. text = val }})} _ update () {console. log ("I need to update"); const vdom = this. _ render. call (this) console. log (vdom);} _ render () {return this. $ options. render. call (this)} _ h _ (tag, attr, children) {return VNode (tag, attr, childr En. map (child) =>{ if (typeof child === 'string') {return VNode (undefined, child )} else {return child})} _ toString _ (val) {return val = null? '': Typeof val = 'object '? JSON. stringify (val, null, 2): String (val) ;}} function observer (value, cb) {Object. keys (value ). forEach (key) => defineReactive (value, key, value [key], cb)} function defineReactive (obj, key, val, cb) {const dep = new Dep () Object. defineProperty (obj, key, {enumerable: true, retriable: true, get: () =>{ if(Dep.tar get) {dep.add(Dep.tar get)} return val}, set: newVal => {if (newVal = val) return val = newVal dep. notify () }}function watch (vm, exp, cb) {Dep.tar get = cb return exp ()} class Dep {constructor () {this. subs = []} add (cb) {this. subs. push (cb)} y () {this. subs. forEach (cb) => cb ()} Dep.tar get = null var demo = new Vue ({el: '# demo', data: {text: "before" ,}, render () {return this. _ h _ ('div ', {}, [this. _ h _ ('span ', {}, [this. _ toString _ (this. text)])} setTimeout (function () {demo. text = "after"}, 3000)
Let's take a look at the running results.
Well, Let's explain Dep.tar get, because we have to distinguish between normal get and get when looking for dependencies. When we look for dependencies, we will
function watch(vm, exp, cb){ Dep.target = cb return exp() }
Dep.tar get value assignment, equivalent to flag, and then get
get: () => { if (Dep.target) { dep.add(Dep.target) } return val },
Just judge it. So far, let's look at the figure again. Is that much clearer?
Summary
I like it very much. For better presentation, vue2.0 and later codes are presented in the simplest way.
However, the entire code execution process, or even the naming method, is the same as vue2.0.
Compared with react, vue2.0 automatically helps you monitor dependencies and automatically re-render your services. To maximize performance, react has to do a lot of work, for example:
How to maximize the performance of react (Prepass), and why does react need to use immutable. js?
When react implements pure render, bind (this) risks.
Vue2.0 naturally helps you achieve the optimal performance, and vue2.0 does not compare the static class attributes such as labels after re-rendering, vue2.0 is faster than the react that maximizes performance.
Then the source code is here. If you like it, remember to give it a star.
In the future, I will briefly talk about the diff of vue2.0.
This article has been compiled into "Vue. js front-end component learning tutorial". You are welcome to learn and read it.
For a tutorial on vue. js components, click the tutorial on vue. js components.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.