This article brings you an introduction to Vue’s responsive principles and dependency collection (with code). It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
Vue monitors data changes by setting the setter/getter methods of object properties, and collects dependencies through getters. Each setter method is an observer, notifying subscribers to update the view when the data changes.
Turn data into observable
So how does Vue turn all properties under data into observable?
function obsever(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, configurable:true, get:()=>{ /*依赖收集*/ return val; }, set:newVal=>{ val=newVal; cb(); } }) } class Vue{ constructor(options){ this._data = options.data; obsever(this._data,options.render) } } let app = new Vue({ el:'#app', data:{ text:'text', text2:'text2' }, render(){ console.log('render') } })
In order to facilitate understanding, first consider the simplest case, without considering arrays and other situations. The code is as shown above. In initData, the observe function will be called to set the Vue data to observable. When the _data data changes, set will be triggered and a callback will be made to the subscriber (render in this case).
Then the problem comes, app._data.text needs to be operated to trigger set. In order to be lazy, we need a convenient way to trigger the set to redraw the view by directly setting it through app.text. Then you need to go to an agent.
Proxy
We can execute a proxy proxy for data in Vue's constructor constructor. In this way, we proxy the attributes on data to the vm instance.
_proxy.call(this,options.data);//构造函数 //代理 function _proxy(data){ const that = this; Object.keys(data).forEach(key=>{ configurable:true, enumerable:true, get:function proxyGetter(){ return that._data[key] }, set:function proxySetter(val){ that._data[key] = val; } }) }
We can use app.text instead of app._data.text.
Why do we need to rely on collection
First look at the following code
new Vue({ template:`<p> <span>text1:</span>{{text1}} <span>text2:</span>{{tetx2}} </p>`, data:{ text1:'text1', text2:'text2', text3:'text3' } })
If you bind according to the method in the responsive principle above, it will appear. One problem - text3 is not used in the actual template. However, when the data of text3 is modified, the setter of text3 will also be triggered, causing the rendering to be re-executed. This is obviously incorrect.
Let’s talk about Dep
When the value of the object on the data is modified, its setter will be triggered. Then when the value is obtained, the getter event will naturally be triggered. So as long as we perform a render at the beginning, all the data in the data that the rendering depends on will be collected by the getter into the subs of Dep. When modifying the data in data, the setter will only trigger the subs function of Dep.
Define a Dep that depends on the collection class.
class Dep{ constructor(){ this.subs = []; } addSub(sub:Watcher){ this.subs.push(sub) } removeSub(sub:Watcher){ remove(this.subs,sub) } notify(){ const subs = this.subs.slice() for(let i = 0;l=subs.length;i<1;i++){ subs[i].update() } } } function remove(arr,item){ if(arr.length){ const index = arr.indexOf(item) if(index>-1){ return arr.splice(index,1) } } }
Watcher
Subscribers, when dependencies are collected, will addSub to sub. When the data in data is modified, the notify of the dep object will be triggered to notify all Watcher object to modify the corresponding view.
class Watcher { constructor (vm, expOrFn, cb, options) { this.cb = cb; this.vm = vm; /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/ Dep.target = this; /*Github:https://github.com/answershuto*/ /*触发渲染操作进行依赖收集*/ this.cb.call(this.vm); } update () { this.cb.call(this.vm); } }
Start dependency collection
class Vue { constructor(options) { this._data = options.data; observer(this._data, options.render); let watcher = new Watcher(this, ); } } function defineReactive (obj, key, val, cb) { /*在闭包内存储一个Dep对象*/ const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{ if (Dep.target) { /*Watcher对象存在全局的Dep.target中*/ dep.addSub(Dep.target); } }, set:newVal=> { /*只有之前addSub中的函数才会触发*/ dep.notify(); } }) } Dep.target = null;
Assign the observer Watcher instance to the global Dep.target, and then trigger the render operation. Only dependencies marked by Dep.target will be collected. The object with Dep.target will push the Watcher instance to subs. When the object is modified and triggers the setter operation, dep will call the update method of the Watcher instance in subs for rendering.
This article has ended here. For more exciting content, you can pay attention to the JavaScript Video Tutorial column on the PHP Chinese website!The above is the detailed content of Introduction to vue responsiveness principle and dependency collection (with code). For more information, please follow other related articles on the PHP Chinese website!