background
For a long time, I have little knowledge about watch and computed in VUE. (For example, the essence of watch and computed is new Watcher. Computed has cache, and it will only be executed when called, and only when the dependent data changes, will it be triggered again…) And then there was no more.
Also saw a lot of big guy to write the article, a large section of a large section of the source code listed, really let me see this dish chicken head big, natural also don’t want to see. Recently, I started to learn the vue source code and really understand how they are implemented.
data() {
return {
msg: 'hello guys'.info: {age:'18'},
name: 'FinGet'}}Copy the code
watcher
What is a watcher? The listener? It’s just a class!
class Watcher{
constructor(vm,exprOrFn,callback,options,isRenderWatcher){}}Copy the code
vm
Vue instanceexprOrFn
It could be a string or a callback (look behind you, it’s not important right now)options
Various configuration items (configure what, look later)isRenderWatcher
Whether it isApply colours to a drawingWathcer
initState
An initState method is executed during Vue initialization, including the most familiar initData, object.defineProperty data hijacking.
export function initState(vm) {
const opts = vm.$options;
// Vue's data source property method data calculation property watch
if(opts.props) {
initProps(vm);
}
if(opts.methods) {
initMethod(vm);
}
if(opts.data) {
initData(vm);
}
if(opts.computed){
initComputed(vm);
}
if(opts.watch) { initWatch(vm, opts.watch); }}Copy the code
During the data hijacking, Watcher’s best gay friend Dep shows up, and Dep is there to save Watcher.
function defineReactive(data, key, val) {
let dep = new Dep();
Object.defineProperty(data, key, {
get(){
if(Dep.target) {
dep.depend(); // Collect dependencies
}
return val;
},
set(newVal) {
if(newVal === val) return;
val = newVal;
dep.notify(); // Notification execution}})}Copy the code
When initData is used, dep. target is nothing, so the collection is lonely. Target is bound to the class Dep (static property), not to the instance.
But after $mount, things are different. $mount: compile, generate, render, patch, diff
There’s only one thing you need to know: the following code will be executed
new Watcher(vm, updateComponent, () = > {}, {}, true); // True indicates that it is a render watcher
Copy the code
UpdateComponent is an update, and regardless of how it’s executed, it’s now a callback that updates the page, and it’s stored in the getter of Watcher. It corresponds to the exprOrFn argument that we started with.
Hey, hey, hey, this time is different:
- Rendering a page is a call to the data you defined (don’t bar, defined not called), and will go
get
. new Watcher
I’m going to call a method that puts this instance inDep.target
On.
pushTarget(watcher) {
Dep.target = watcher;
}
Copy the code
These two things come together, and dep.Depend () does the work.
So one thing you can understand from here is that all data defined in the data, whenever called, it will collect a render Watcher, that is, data changes, execute the dep.notify in the set and the render Watcher will be executed
Here is the definitionmsg
,info
,name
Three numbers, they all have oneApply colours to a drawingWatcher
:
Sharp-eyed friends should have noticed that there are two watcher in MSG, one is a user-defined watch and the other is also a user-defined watch. Ah, of course not. Vue is de-weighted, there’s no repeat watcher, as you might expect, and computed Watcher;
User to watch
Here’s how we use watch:
watch: {
msg(newVal, oldVal){
console.log('my watch',newVal, oldVal)
}
// or
msg: {
handler(newVal, oldVal) {
console.log('my watch',newVal, oldVal)
},
immediate: true}}Copy the code
InitWatch is executed, and after a short operation, exprOrFn(which is a string at this point), handler, and options are extracted, which somehow corresponds to Watcher, and the vm.$watch method is called.
Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
options.user = true; // mark it as user watcher
// Core is to create a watcher
const watcher = new Watcher(this, exprOrFn, cb, options);
if(options.immediate){
cb.call(vm,watcher.value)
}
}
Copy the code
Come on, you can’t help but look at this code (it was a long one, but to be honest, I cut out the ones that don’t have much to do with it) :
class Watcher{
constructor(vm,exprOrFn,callback,options,isRenderWatcher){
this.vm = vm;
this.callback = callback;
this.options = options;
if(options) {
this.user = !! options.user; }this.id = id ++;
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn; // Place the internally passed callback function on the getter property
} else {
this.getter = parsePath(exprOrFn);
if (!this.getter) {
this.getter = (() = >{}); }}this.value = this.get();
}
get(){
pushTarget(this); // Save the current watcher to deP
let result = this.getter.call(this.vm, this.vm); // The execution of the render watcher goes to the observeGet method and saves the watcher
popTarget(); By the time we get to this point, all the dependency collections are done, all with the same watcher
returnresult; }}Copy the code
// 这个就是拿来把msg的值取到,取到的就是oldVal
function parsePath(path) {
if(! path) {return
}
var segments = path.split('. ');
return function(obj) {
for (var i = 0; i < segments.length; i++) {
if(! obj) {return }
obj = obj[segments[i]];
}
return obj
}
}
Copy the code
As you can see, new Watcher is going to do a get method, when it’s rendering Watcher is going to render the page, do updateComponent once, when it’s the user Watcher is going to do the return method in parsePath, And then you get a value this.value which is oldVal.
Dep. Depend () = dep. Depend ();
When MSG changes, there are some other operations involved, which don’t matter. Finally, a run method is executed, which calls the callback and passes in newValue and oldValue:
run(){
let oldValue = this.value;
// Execute again to get the current value, will be rehash, Watcher does not repeat
let newValue = this.get();
this.value = newValue;
if(this.user && oldValue ! = newValue) {// The user watcher calls callback
this.callback(newValue, oldValue)
}
}
Copy the code
computed
computed: {
c_msg() {
return this.msg + 'computed'
}
// or
c_msg: {
get() {
return this.msg + 'computed'
},
set(){}}},Copy the code
What are the characteristics of computed Tomography:
- Is executed when called
- Have a cache
- Recalculation occurs when dependencies change
When called, how do I know it’s called? DefineProperty, Object. DefineProperty.
Dependencies are recalculated when their data changes, which is where the dependencies need to be collected. Again, this. MSG -> get -> dep.depend() is called.
function initComputed(vm) {
let computed = vm.$options.computed;
const watchers = vm._computedWatchers = {};
for(let key in computed) {
const userDef = computed[key];
// Get the get method
const getter = typeof userDef === 'function' ? userDef : userDef.get;
Watcher lazy is not called the first time
watchers[key] = new Watcher(vm, userDef, () = > {}, { lazy: true });
defineComputed(vm, key, userDef)
}
}
Copy the code
const sharedPropertyDefinition = {
enumerable: true.configurable: true.get: () = > {},
set: () = >{}}function defineComputed(target, key, userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
} else {
sharedPropertyDefinition.get = createComputedGetter(userDef.get);
sharedPropertyDefinition.set = userDef.set;
}
// Use the defineProperty definition so that it can be used to calculate
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code
DefineProperty = object.defineProperty = object.defineProperty
class Watcher{
constructor(vm,exprOrFn,callback,options,isRenderWatcher){...this.dirty = this.lazy;
// Lazy is not executed the first time
this.value = this.lazy ? undefined : this.get(); . }update(){
if (this.lazy) {
// The calculated properties need to be updated
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this); // This is just a foil for now}}evaluate() {
this.value = this.get();
this.dirty = false; }}Copy the code
The cache is right there, so if you do get, you get a return value this.value is the cache value. In user Watcher, it’s oldValue. 🐂 🍺 plus!
function createComputedGetter(key) {
return function computedGetter() {
// This points to the vue instance
const watcher = this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // If dirty is true
watcher.evaluate();// Evaluates the new value and updates dirty to false
}
// If the dependent value does not change, the result of the last calculation is returned
return watcher.value
}
}
}
Copy the code
When is the update from Watcher called? When dep.notify() is called, dirty needs to be true. However, the calculation attributes still need to be calculated at the time of the call, so update only changes the status of dirty. And then it’s recalculated the next time it’s called.
class Dep {
constructor() {
this.id = id ++;
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher);
}
depend() {
Dep.target.addDep(this);
}
notify() {
this.subs.forEach(watcher= > watcher.update())
}
}
Copy the code
conclusion
watch
和computed
Nature isWatcher
, are storedDep
When data changes, it is executeddep.notify
Put the currentDep
In the instanceWatcher
allrun
So let’s do thatApply colours to a drawingWatcher
The page refreshes;- Each statistic has its own
Dep
If it is called in the template, it must have oneRender the Watcher
; initData
When, noWatcher
2. Capable of being collected;- Did you find any,Apply colours to a drawing
Watcher
和Computed
,exprOrFn
It’s all functions,The userWatcher
Are all strings.
The code in the article is a brief version, there are a lot of details did not say, is not important for this article is not important, you can go to read the source code for a deeper understanding.