What is responsive?
catalogue
let m = 3 console.log(m); console.log(m +'1aaaaaa'); console.log('hello');
When the value of m changes, I hope the following three lines of code will be executed again automatically.
// obj const obj = { name:'Sherry', age:18 } console.log('hello'); console.log('i love u '); console.log(obj.name);
Requirement: when the value of name changes, the next three lines of code will be executed again. We first want to encapsulate a function
Function encapsulation
const obj = { name:'Sherry', age:18 } // let reactiveFn = [] function watchFn(fn){ reactiveFn.push(fn) } watchFn(function(){ console.log('hello') console.log('i love u ') console.log(obj.name) } ) obj.name = 'Lily' reactiveFn.forEach(fn =>{ fn() })
The array stores the functions we want to execute. When we want to execute any function when the value changes, we put the function into watchFn. watchFn will push the function in and finally call forEach. But now this array collects all the functions that need to be executed when the name changes. If it is age, it won't work. It's inconvenient to change an object, so we don't use the array
Class encapsulation
class Depend{ constructor(){ this.reactiveFn = [] } addDepend(reactiveFn){ this.reactiveFn.push(reactiveFn) } } // obj const obj = { name:'Sherry', age:18 } const depend = new Depend() function watchFn(fn){ depend.addDepend(fn) } watchFn(function(){ console.log('hello') console.log('i love u ') console.log(obj.name) } ) obj.name = 'Lily' reactiveFn.forEach(fn =>{ fn() })
At this time, after encapsulating the array and the method added to the array into the class, age and name will be separated. Each attribute will have a corresponding array. Now one attribute can correspond to one dep object
Now, we can't change a property. We'll execute it after forEach every time. Let's put another method in the class
notify(){ this.reactiveFn.forEach(fn => { fn() }) }
In this way, notify is called when responding
Listening object change
It hasn't been done yet. We should let the object execute itself after we change the attribute value of the object, instead of manually calling the function to execute it. Now we should listen to the changes of object attributes
Two methods: Proxy(vue3), object Define property (vue2). Let's talk about Proxy
const obj = { name:'Sherry', age:18 } // Monitor obj changes const objProxy = new Proxy(obj,{ get:function(target,key,receiver){ return Reflect.get(target,key,receiver) }, set:function(target,key,newValue,receiver){ Reflect.set(target,key,newValue,receiver) // We want automatic monitoring depend.notify() } }) function watchFn(fn){ depend.addDepend(fn) } const depend = new Depend() watchFn(function(){ console.log('hello') console.log('i love u ') console.log(objProxy.name) } ) objProxy.name = 'Lily' objProxy.name = 'Lily' objProxy.name = 'Lily'
Now, whenever we change the name, the Proxy will automatically listen for us and execute notify
But if age changes
const depend = new Depend() watchFn(function(){ console.log('hello') console.log('i love u ') console.log(objProxy.name) } ) watchFn(function(){ console.log(objProxy.age,'age is change111'); }) watchFn(function(){ console.log(objProxy.age,'age is change22'); }) objProxy.age = 100
When the age changes, all the functions in watchFn will execute by themselves
In the current design, since the depend in the set function collects the dependencies of all attributes, all functions will be re executed no matter which attribute changes. This is not possible. We need to make a distinction
// Encapsulate a function to get depend ent const targetMap = new WeakMap() function getDepend(target,key){ // Get object according to target let map = targetMap.get(target) // New map if(!map){ map = new Map() targetMap.set(target, map) } // Get depend ent according to key let depend = map.get(key) if(!depend){ depend = new Depend() map.set(key,depend) } return depend } // obj response const obj = { name: 'Sherry', age: 18 } // Monitor obj changes const objProxy = new Proxy(obj, { get: function (target, key, receiver) { return Reflect.get(target, key, receiver) }, set: function (target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) // We want automatic monitoring // depend.notify() // Get the most authentic depend ent, name/age const depend = getDepend(target,key) depend.notify() } })
Let's take a brief look at the relationship diagram. This diagram can well understand the encapsulated function getappend. As for why it is map, the difference between map and object is that the key name of map can be any value, and the object can also be, but the object can't be. The key name of the object can't be an object. If it is an object, it will be automatically converted into a string. If you are interested, you can test it
But now let's hit dependency discovery
function getDepend(target,key){ // Get object according to target let map = targetMap.get(target) // New map if(!map){ map = new Map() targetMap.set(target, map) } // Get depend ent according to key let depend = map.get(key) if(!depend){ depend = new Depend() console.log(depend.reactiveFn); map.set(key,depend) } return depend } objProxy.name = 'Lily' objProxy.age = 100
It's empty
When should we collect dependencies?
When you get
const objProxy = new Proxy(obj, { get: function (target, key, receiver) { // I want to collect dependencies here const depend = getDepend(target, key) depend.addDepend() return Reflect.get(target, key, receiver) }, watchFn(function () { console.log('hello') console.log('i love u ') console.log(objProxy.name) }
Can't get watchFn in adddepot
Using global variables
let activeReactiveFn = null function watchFn(fn) { activeReactiveFn = fn depend.addDepend(fn) } get: function (target, key, receiver) { // I want to collect dependencies here const depend = getDepend(target, key) depend.addDepend(activeReactiveFn) return Reflect.get(target, key, receiver) },
Optimize: don't want get to care about whether there is a value in the global variable activeReactiveFn. Pull it out
let activeReactiveFn = null class Depend { constructor() { this.reactiveFn = [] } addDepend(reactiveFn) { this.reactiveFn.push(reactiveFn) } notify() { this.reactiveFn.forEach(fn => { fn() }) } // optimization depend(){ if(activeReactiveFn){ this.reactiveFn.push(activeReactiveFn) } } }
In this way, when get listens, you only need to call the depend ent method
For the same WatchFn, we don't care how many times the attribute is accessed. In general, we only need to execute this function once
class Depend { constructor() { this.reactiveFn = new Set() } addDepend(reactiveFn) { this.reactiveFn.add(reactiveFn) } notify() { this.reactiveFn.forEach(fn => { fn() }) } // optimization depend(){ if(activeReactiveFn){ this.reactiveFn.add(activeReactiveFn) } } }