Ask yourself more why every day. You always have unimaginable gains. A rookie Xiaobai's growth path (copyer)
Decide whether to continue reading this note
This is a purely personal learning step, the process of practicing code from shallow to incremental, and the idea is improved step by step
If you don't know anything about the response, you can try to look at the notes here, which is applicable to Xiaobai (because I am Xiaobai)
If you know more about the response, you don't have to look at it. There's little gain here
What is responsive?
A simple understanding is that a function depends on a variable. When the variable is changed, the function will be automatically re executed and updated.
const info = { count: 0} function doubleCount() { //This function uses info count console.log(info.count * 2) } getCount() //When info When the count is changed, you want to automatically execute doubleCount(), which faces info Count has dependency info.count++
When multiple functions depend on a variable, multiple functions need to be executed multiple times. Therefore, a single function is encapsulated to uniformly execute these functions.
//Suppose there is another function, which also depends on info Count function powCount() { console.log(info.count * info.count) } //When the count is changed, doubleCount and powCount need to be executed automatically
Simply encapsulate the implementation of Dep class (manual collection version)
class Dep { constructor() { this.subscribers = new Set() //Ensure that the collected dependencies are not duplicated } //Collect side effects //Side effects: when the variable changes, other functions also need to be executed. These are the side effects produced by the variable addEffect(effect) { this.subscribers.add(effect) } //Variable change, notify execution notify() { this.subscribers.forEach(item => { item() }) } }
Use in instances:
const dep = new Dep() //Instantiation dependency const info = { count: 0} function doubleCount() { //This function uses info count console.log(info.count * 2) } function powCount() { console.log(info.count * info.count) } //Collection dependency dep.addEffect(doubleCount) dep.addEffect(powCount) info.count++ //Variable change, re execute side effects dep.notify()
Refactoring the implementation of encapsulated Dep class (automatic collection version)
Here, the depend ent function is used instead of the addEffect function
addEffect: we need to add dependencies manually
Dependent: it is used to judge whether there are side effects. If there are side effects, they will be added, so there is no need to collect them manually
class Dep { constructor() { this.subscribers = new Set() } depend() { //Automatic collection of dependent functions if(activeEffect) { this.subscribers.add(activeEffect) } } notify() { this.subscribers.forEach(item => { item() }) } } //Encapsulate a watchEffect function to automatically collect let activeEffect = null //Determine whether there are side effects function watchEffect(effect) { activeEffect = effect //Assign side effects and add dep.depend(activeEffect) //Collection add effect() //First execution activeEffect =null //Reset empty }
use:
const info = { count: 100} const dep = new Dep() watchEffect(function() { //The functions defined here use watchEffect, which will automatically collect dependencies console.log(info.count * 2); }) watchEffect(function () { console.log(info.count * info.count); }) info.count++ dep.notify()
Responsive implementation of reactive (data hijacking: get())
The process is difficult. If you understand the basic ideas above, let's look at the logic below!
Let's start with a code:
const dep = new Dep() const info = { count: 100, name: 'james'} const bar = { age: 19 } watchEffect(function() { //watch1 console.log(info.count, info.name) }) watchEffect(function() { //watch2 console.log(info.name) }) watchEffect(function() { //watch3 console.log(bar.age) })
watchEffect collection dependency
watch1 depends on info count info. name
watch2 depends on info name`
watch3 depends on bar age
We know that the depand function collects different side effects and adds them to subscribers. As long as the dependency changes, all side effects will be executed. This is definitely a waste of performance, not what we expect. So how should we solve it?
We need to use another data structure: Map and WeakMap
Objective: to create multiple dep instance objects and use subscribers to save the side effects of different data
1. First, the structure is analyzed
For the above, if there are two objects info (with two attributes) and bar (with one attribute), you should instantiate three dep objects and use the subscribers in instantiation to save their corresponding side effects respectively.
//For the info object, you need to create two dep objects dep1 and dep2 info.count: dep1.subscribers = [watch1] info.name: dep2.subsrcibers = [watch1, wathc2] //For the bar object, you need to create a dep object dep3 bar.age: dep3.subsrcibers = [watch3]
When the properties of info and bar change, execute the side effects collected by their respective subscribers, and then execute the notify function
Tips:
Understanding: the difference between WeakMap and Map
Similarities:
They are all key: value pairs
difference:
The key value of Map is string, and value is any type
The key value of WeakMap is: the object value is of any type
After understanding the difference between the above WeakMap and Map, design the Map structure for the code
Pseudo code data structure:
//There are two objects above, so use weakMap to save them const targetMap = new WeakMap() //Info object, key is info object, and value is a map const infoMap = new Map(info) //Create a map targetMap[info] = infoMap //The value value is a map, so it exists in the form of a key value pair. The key is the attribute name of the info object infoMap['count'] = dep1.subscribers infoMap['name'] = dep2.subscribers //bar object targetMap[bar] = new Map(bar) //It's the same as the above explanation
Therefore, according to the above analysis, we can know exactly which data changes and execute the corresponding dep subscribers
The code implements the above data structure
//Create a WeakMap to save info and bar objects (the info object is mainly used as an example below) const targetWeakMap = new WeakMap() //getDep(info, 'name') gets dep whose attribute value of info is name function getDep(target, key) { //Get the value where the key in the WeakMap is the info object let depsMap = targetWeakMap.get(target) //During initialization, there is no, so new Map() is used as value and re assigned if(!depsMap) { depsMap = new Map() targetWeakMap.set(target, depsMap) } //According to the info object, get the value, which is Map. According to the key, you can get the corresponding dep let dep = depsMap.get(key) //Handle initialization. If not, create a dep instance object if(!dep) { dep = new Dep() depsMap.set(key, dep) } //Return the dep obtained return dep }
After analyzing the above code, you can get dep, and then you can write reactive responsive logic
2. reactive implementation of vue2
const info = reactive({count: 100, name: 'james'})
Objective: call reactive, pass an object and return a responsive object
//Raw: raw data function reactive(raw) { //Traverse to get all key values Object.keys(raw).forEach(item => { const dep = getDep(raw, item) let value = raw[item] Object.defineProperty(raw, item, { //When you get the attribute value in the info object, you call the get method. Then add dependencies here (the so-called data hijacking) get() { dep.depend() return value }, set(newValue) { //When the value is set, notify is executed and all side effects are executed if(value !== newValue) { value = newValue dep.notify() } } }) }) return raw }
3. Explain the process implementation of the complete code
//Step 1: class Dep { constructor() { this.subscribers = new Set() } depend() { if(activeEffect) { this.subscribers.add(activeEffect) } } notify() { this.subscribers.forEach(item => { item() }) } } //Step 2: let activeEffect = null function watchEffect(effect) { activeEffect = effect effect() activeEffect =null } //Step 3: const targetWeakMap = new WeakMap() function getDep(target, key) { //Get the Map of dep according to the target object let depsMap = targetWeakMap.get(target) if(!depsMap) { depsMap = new Map() targetWeakMap.set(target, depsMap) } //According to the obtained depsMap, get their dep through the key value let dep = depsMap.get(key) if(!dep) { dep = new Dep() depsMap.set(key, dep) } return dep } //Raw: raw data function reactive(raw) { Object.keys(raw).forEach(item => { const dep = getDep(raw, item) let value = raw[item] Object.defineProperty(raw, item, { get() { dep.depend() return value }, set(newValue) { if(value !== newValue) { value = newValue dep.notify() } } }) }) return raw } //Test code const info = reactive({ count: 100, name: 'james'}) const bar = reactive({age: 12}) watchEffect(function() { //watch1 console.log('watch1: ', info.count, info.name) }) watchEffect(function() { //watch2 console.log('watch2: ', info.name) }) watchEffect(function() { //watch3 console.log('watch3: ', bar.age) }) info.count++
Code interpretation:
-
Step 1: define the Dep class, which implements two methods: depend (collect side effect function) and notify (re execute side effect function after data change)
-
Step 2: define the watchEffect function, execute it for the first time, and collect dependencies
-
Step 3: define a reactive function to change the data into responsive data
-
Step 4: test code verification
watchEffect(function() { //watch1 console.log('watch1: ', info.count, info.name) }) //Info. Is used directly here Count, object will be called directly The get method of defineproperty, then the dependency will be collected info.count++ //If you change the data, you will directly call object The set method of defineproperty will execute the notify function
-
Step 5: verify the results
//Run in node environment //First execution watch1: 100 james watch2: james watch3: 12 //info. Execute after count change watch1: 101 james
4. reactive implementation of vue3
proxy object used by vue3.
Why vue3 choose Proxy?
-
Object. When defineproperty hijacks the properties of an object, if an element is added:
- Then vue2 you need to execute defineProperty again, such as Vue$ Set is re execution
- The Proxy hijacks the whole object without special processing
-
Modify different objects
-
When using defineProperty, we can trigger interception by modifying the original obj object
-
Proxy, you must modify the proxy object, that is, the instance of proxy, before it can be triggered
//vue2 function reactive(raw) { return raw } //vue3 function reactive(raw) { return new Proxy(raw, {}) }
-
-
Proxy can observe more types than defineProperty
- Catcher for has: in operator
- deleteProperty: the catcher for the delete operator
- Other operations
-
Disadvantages: Proxy is not compatible with IE
code implementation
function reactive(raw) { return new Proxy(raw, { get(target, key) { const dep = getDep(target, key) dep.depend() return target[key] }, set(target, key, newValue) { const dep = getDep(target, key) target[key] = newValue dep.notify() } }) }
Summary:
Know a lot and gain a lot. Continue to refuel. If you are wrong, please spray.