current position:Home>Uncover the responsive optimization of vue.js 3.2!
Uncover the responsive optimization of vue.js 3.2!
2021-08-23 07:46:29 【Tong ouba】
background
Vue 3 It has been almost a year since it was officially released , I believe that many small partners have been used in the production environment Vue 3 了 . Now ,Vue.js 3.2 It has been officially released , This time, minor
The upgrade of the version is mainly reflected in the optimization of the source code level , For the user's use level, it doesn't change much . One of the things that appeals to me is the improved performance of responsiveness :
- More efficient ref implementation (~260% faster read / ~50% faster write)
- ~40% faster dependency tracking
- ~17% less memory usage
Which translates as ref
API The reading efficiency is improved by about 260%
, The write efficiency is improved by about 50%
, The efficiency improvement of relying on collection is about 40%
, At the same time, it also reduces about 17%
Memory usage .
This is just an optimization , Because you know, responsive systems are Vue.js One of the core implementations of , Optimizing it means using all Vue.js Developed App Performance optimization .
And this optimization is not Vue Official personnel achieved , But a big man in the community @basvanmeurs Proposed , The relevant optimization code is in 2020 year 10 month 9 No. has been submitted , However, due to the large changes to the internal implementation , The authorities waited until Vue.js 3.2 Release , Just put the code into .
This time, basvanmeurs The proposed responsive performance optimization really makes you happy , Not just greatly improved Vue 3 Run time performance of , And because such core code can come from the contribution of the community , That means Vue 3 Attracted more and more attention ; Some competent developers participate in the contribution of core code , It can make Vue 3 It's better to go farther .
We know , Compared with Vue 2,Vue 3 We've done a lot of optimization , One part is the implementation of data response Object.defineProperty
API Changed to Proxy
API.
The original Vue 3 When it comes to publicity , Officials claim to have optimized the performance of responsive implementation , So what are the aspects of optimization ? Some small partners think it is Proxy
API Better than Object.defineProperty
Of , It's not , actually Proxy
In terms of performance, it is better than Object.defineProperty
Poor , For details, please refer to Thoughts on ES6 Proxies Performance This article , And I tested it , The conclusion is the same as above , You can refer to this repo.
since Proxy
slow , Why? Vue 3 Or did you choose it to implement data response ? because Proxy
It's essentially a hijacking of an object , In this way, it can not only listen for changes in the value of a property of the object , You can also listen for the addition and deletion of object properties ; and Object.defineProperty
Is to add a corresponding to an existing attribute of an object getter
and setter
, So it can only listen for changes in the value of this attribute , Instead of listening to the addition and deletion of object properties .
The performance optimization of responsiveness is actually reflected in the scene of changing deeply nested objects into responsiveness . stay Vue 2 In the implementation of , When the data is turned into a response in the component initialization phase , When the child attribute is still an object , Will execute recursively Object.defineProperty
Define the response of the child object ; And in the Vue 3 In the implementation of , Only when the object attribute is accessed will the type of sub attribute be determined to decide whether to execute recursively reactive
, This is actually an implementation of delay definition sub object response , There will be some improvement in performance .
therefore , Compared with Vue 2,Vue 3 Indeed, some optimization has been done in the responsive implementation part , But in fact, the effect is limited . and Vue.js 3.2 This optimization in terms of responsive performance , It really made a qualitative leap , Next, we'll serve some hard dishes , What optimization has been done from the source level , And the technical thinking behind these optimizations .
Principle of responsive implementation
It's called responsive , When we modify the data , Can automatically do something ; The rendering corresponding to the component , After modifying the data , It can automatically trigger the re rendering of components .
Vue 3 Implement responsive , It's essentially through Proxy
API Hijacked the reading and writing of data objects , When we access data , Will trigger getter
Perform dependency collection ; When modifying data , Will trigger setter
Distribution notice .
Next , Let's briefly analyze the implementation of dependency collection and dispatch notifications (Vue.js 3.2 Previous version ).
Rely on collection
First, let's look at the process of dependency collection , The core is when accessing responsive data , Trigger getter
function , And then perform track
Function collection dependency :
let shouldTrack = true // Currently active effect let activeEffect // Original data object map const targetMap = new WeakMap() function track(target, type, key) { if (!shouldTrack || activeEffect === undefined) { return } let depsMap = targetMap.get(target) if (!depsMap) { // Every target Corresponding to one depsMap targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { // Every key Corresponding to one dep aggregate depsMap.set(key, (dep = new Set())) } if (!dep.has(activeEffect)) { // Collect currently active effect As a dependency dep.add(activeEffect) // Currently active effect collect dep Set as dependency activeEffect.deps.push(dep) } }
Before analyzing the implementation of this function , Let's first think about what dependencies to collect , Our goal is to achieve responsive , It can automatically do something when the data changes , For example, execute some functions , So the dependency we collect is the side effect function executed after the data changes .
track
The function has three arguments , among target
Represents raw data ;type
Indicates the type of dependency collection ;key
Represents the accessed property .
track
A global... Is created outside the function targetMap
As the original data object Map
, Its key is target
, The value is depsMap
, As a dependent Map
; This depsMap
The key is target
Of key
, The value is dep
aggregate ,dep
Stored in the collection are dependent side-effect functions . For ease of understanding , The relationship between them can be represented by the following figure :
So every time track
function , Is to put the currently activated side effect function activeEffect
As a dependency , And collect it target
dependent depsMap
Corresponding key
Dependency set under dep
in .
Distribution notice
The dispatch notification occurs at the stage of data update , The core is when modifying responsive data , Trigger setter
function , And then perform trigger
Function dispatch notification :
const targetMap = new WeakMap() function trigger(target, type, key) { // adopt targetMap Get target Corresponding dependency set const depsMap = targetMap.get(target) if (!depsMap) { // No dependence , Go straight back to return } // Create a running effects aggregate const effects = new Set() // add to effects Function of const add = (effectsToAdd) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { effects.add(effect) }) } } // SET | ADD | DELETE One of the operations , Add corresponding effects if (key !== void 0) { add(depsMap.get(key)) } const run = (effect) => { // Scheduling execution if (effect.options.scheduler) { effect.options.scheduler(effect) } else { // Direct operation effect() } } // Traverse the execution effects effects.forEach(run) }
trigger
The function has three arguments , among target
Represents the target original object ;type
Indicates the type of update ;key
Indicates the attribute to be modified .
trigger
function I did four things :
- from
targetMap
Get in thetarget
Corresponding dependency setdepsMap
; - Create a running
effects
aggregate ; - according to
key
fromdepsMap
Find the correspondingeffect
Add toeffects
aggregate ; - Traverse
effects
Execute the relevant side effect function .
So every time trigger
function , It is based on target
and key
, from targetMap
Find all the related side-effect functions in, and traverse and execute them again .
In describing the process of relying on the collection and distribution of notifications , We all mentioned a word : Side effect function , In the process of dependency collection, we put activeEffect
( Currently active side effect function ) Collect as dependencies , What is it ? Next, let's look at the true face of the side effect function .
Side effect function
that , What is a side effect function , Before introducing it , Let's first review the original requirements of responsiveness , That is, we can automatically do something by modifying the data , A simple example :
import { reactive } from 'vue' const counter = reactive({ num: 0 }) function logCount() { console.log(counter.num) } function count() { counter.num++ } logCount() count()
We defined responsive objects counter
, And then in logCount
I visited counter.num
, We hope to implement count
Function modification counter.num
When it's worth it , Can automatically execute logCount
function .
According to our previous analysis of the dependency collection process , If logCount
yes activeEffect
Words , Then the requirements can be realized , But it's obviously impossible , Because the code is executing to console.log(counter.num)
In this business , It's on itself logCount
The operation in a function is unknown .
So what to do ? In fact, as long as we run logCount
Function before , hold logCount
Assign a value to activeEffect
Just fine :
activeEffect = logCount logCount()
Follow this line , We can use the idea of higher-order functions , Yes logCount
Make a layer of packaging :
function wrapper(fn) { const wrapped = function(...args) { activeEffect = fn fn(...args) } return wrapped } const wrappedLog = wrapper(logCount) wrappedLog()
wrapper
It's also a function , It accepts fn
As a parameter , Returns a new function wrapped
, Then maintain a global variable activeEffect
, When wrapped
When it comes to execution , hold activeEffect
Set to fn
, And then execute fn
that will do .
So when we execute wrappedLog
after , Go ahead and revise counter.num
, Automatically logCount
Function .
actually Vue 3 Is to adopt a similar approach , Inside it there is a effect
Side effect function , Let's take a look at its implementation :
// overall situation effect Stack const effectStack = [] // Currently active effect let activeEffect function effect(fn, options = EMPTY_OBJ) { if (isEffect(fn)) { // If fn Is already a effect Function , Then it points to the original function fn = fn.raw } // Create a wrapper, It is a function of reactive side effects const effect = createReactiveEffect(fn, options) if (!options.lazy) { // lazy To configure , Calculating properties will use , Not lazy Directly execute once effect() } return effect } function createReactiveEffect(fn, options) { const effect = function reactiveEffect() { if (!effect.active) { // Inactive state , It is judged that if it is not scheduled to execute , Then execute the original function directly . return options.scheduler ? undefined : fn() } if (!effectStack.includes(effect)) { // Empty effect Referenced dependencies cleanup(effect) try { // Turn on the whole shouldTrack, Allow dependency collection enableTracking() // Pressing stack effectStack.push(effect) activeEffect = effect // Execute the original function return fn() } finally { // Out of the stack effectStack.pop() // recovery shouldTrack Status before opening resetTracking() // Point to the last... On the stack effect activeEffect = effectStack[effectStack.length - 1] } } } effect.id = uid++ // The logo is a effect function effect._isEffect = true // effect The state of oneself effect.active = true // Wrapped primitive function effect.raw = fn // effect Corresponding dependence , bi-directional pointer , Dependency contains a pair of effect References to ,effect It also contains references to dependencies effect.deps = [] // effect Related configuration of effect.options = options return effect }
Combined with the above code ,effect
Internally through the implementation of createReactiveEffect
Function to create a new effect
function , In order to communicate with the outside effect
Functions distinguish , We call it reactiveEffect
function , And added some additional properties to it ( I have marked in my notes ). in addition ,effect
The function also supports passing in a configuration parameter to support more feature
, It's not going to unfold here .
reactiveEffect
A function is a side effect function of a response , When executed trigger
When the process sends out the notice , Executive effect
Is it .
According to our previous analysis ,reactiveEffect
Functions only need to do two things : Let the overall activeEffect
Pointing to it , Then execute the wrapped original function fn
.
But in fact, its implementation is more complex , First it will judge effect
Is the state of active,
This is actually a means of control , Allow in non active
Status and non scheduling execution , Then execute the original function directly fn
And back to .
Then judge effectStack
Include in effect
, If not, put effect
Press into the stack . We mentioned before , Just set activeEffect = effect
that will do , So why design a stack structure here ?
In fact, it takes into account the following nesting effect
Scene :
import { reactive} from 'vue' import { effect } from '@vue/reactivity' const counter = reactive({ num: 0, num2: 0 }) function logCount() { effect(logCount2) console.log('num:', counter.num) } function count() { counter.num++ } function logCount2() { console.log('num2:', counter.num2) } effect(logCount) count()
Every time we execute effect
Function time , If you just put reactiveEffect
The function is assigned to activeEffect
, So for this nested scenario , After execution effect(logCount2)
after ,activeEffect
still effect(logCount2)
Back to reactiveEffect
function , So follow-up visits counter.num
When , Dependency collection corresponds to activeEffect
That's not right. , At this point, we execute count
Function modification counter.num
After the execution is not logCount
function , It is logCount2
function , The final output is as follows :
num2: 0 num: 0 num2: 0
The results we expect should be as follows :
num2: 0 num: 0 num2: 0 num: 1
So for nested effect
Scene , We cannot simply assign values activeEffect
, It should be considered that the execution of a function itself is an on and off stack operation , So we can also design a effectStack
, So every time I enter reactiveEffect
The function puts it on the stack first , then activeEffect
Point to this reactiveEffect
function , And then fn
When the execution is finished, it will be put out of the stack , And then activeEffect
Point to effectStack
The last element , That is, the outer layer effect
Function corresponding reactiveEffect
.
Here we also notice a detail , Before the stack, it will execute cleanup
Function empty reactiveEffect
The dependency corresponding to the function . In execution track
Function , In addition to collecting currently active effect
As a dependency , And through activeEffect.deps.push(dep)
hold dep
As activeEffect
Dependence , In this way cleanup
We can find effect
Corresponding dep
了 , And then put effect
From these dep
Delete in .cleanup
The code for the function is as follows :
function cleanup(effect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } deps.length = 0 } }
Why cleanup
Well ? If you encounter this kind of scene :
<template> <div v-if="state.showMsg"> {{ state.msg }} </div> <div v-else> {{ Math.random()}} </div> <button @click="toggle">Toggle Msg</button> <button @click="switchView">Switch View</button> </template> <script> import { reactive } from 'vue' export default { setup() { const state = reactive({ msg: 'Hello World', showMsg: true }) function toggle() { state.msg = state.msg === 'Hello World' ? 'Hello Vue' : 'Hello World' } function switchView() { state.showMsg = !state.showMsg } return { toggle, switchView, state } } } </script>
Combined with the code, you can know , The view of this component will be based on showMsg
Variable control display msg
Or a random number , When we click Switch View
When the button of , Will change the value of this variable .
Suppose there is no cleanup
, When rendering the template for the first time ,activeEffect
Is the side effect rendering function of the component , Because the template render
It's time to visit state.msg
, Therefore, dependency collection will be performed , Take the side effect rendering function as state.msg
Dependence , We call it render effect
. And then we click Switch View
Button , The view switches to show random numbers , Now let's click Toggle Msg
Button , Because of the modification state.msg
Will send a notice , eureka render effect
And implement , This triggers the re rendering of the component .
But this behavior does not actually meet expectations , Because when we click Switch View
Button , When the view is switched to display random numbers , It will also trigger the re rendering of components , But the view is not rendered at this time state.msg
, Therefore, changes to it should not affect the re rendering of components .
Therefore, in the of components render effect
Perform before , If you pass cleanup
Clean up dependencies , We can delete the previous state.msg
Collected render effect
rely on . So when we modify state.msg
when , Since there are no dependencies, the re rendering of components will not be triggered , In line with expectations .
Optimization of responsive implementation
The principle of responsive implementation is analyzed , Everything seems to be OK, So what other points can be optimized ?
Rely on the optimization of the collection
At present, every time the side effect function executes , You need to do it first cleanup
Clear dependency , The dependencies are then collected again during the execution of the side effect function , This process involves a large number of pairs of Set
Add and delete operations of collection . In many scenarios , Dependencies rarely change , Therefore, there is a certain optimization space .
In order to reduce the addition and deletion of collections , We need to identify the state of each dependency set , For example, is it a new collection , Or has been collected .
So here we need to set dep
Add two properties :
export const createDep = (effects) => { const dep = new Set(effects) dep.w = 0 dep.n = 0 return dep }
among w
Indicates whether it has been collected ,n
Indicates whether a new collection .
Then design several global variables ,effectTrackDepth
、trackOpBit
、maxMarkerBits
.
among effectTrackDepth
Represents recursive nested execution effect
The depth of the function ;trackOpBit
Used to identify the status of dependency collection ;maxMarkerBits
Indicates the number of digits of the maximum tag .
Next, let's look at their applications :
function effect(fn, options) { if (fn.effect) { fn = fn.effect.fn } // establish _effect example const _effect = new ReactiveEffect(fn) if (options) { // Copy options Attributes in to _effect in extend(_effect, options) if (options.scope) // effectScope Related processing logic recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // Execute now _effect.run() } // binding run function , As effect runner const runner = _effect.run.bind(_effect) // runner Retain the right to _effect References to runner.effect = _effect return runner } class ReactiveEffect { constructor(fn, scheduler = null, scope) { this.fn = fn this.scheduler = scheduler this.active = true // effect Storage related deps rely on this.deps = [] // effectScope Related processing logic recordEffectScope(this, scope) } run() { if (!this.active) { return this.fn() } if (!effectStack.includes(this)) { try { // Pressing stack effectStack.push((activeEffect = this)) enableTracking() // Record the number of bits according to the recursive depth trackOpBit = 1 << ++effectTrackDepth // exceed maxMarkerBits be trackOpBit The calculation of will exceed the number of bits of the maximum shaping , Downgrade to cleanupEffect if (effectTrackDepth <= maxMarkerBits) { // Mark dependencies initDepMarkers(this) } else { cleanupEffect(this) } return this.fn() } finally { if (effectTrackDepth <= maxMarkerBits) { // Complete dependency tag finalizeDepMarkers(this) } // Restore to the previous level trackOpBit = 1 << --effectTrackDepth resetTracking() // Out of the stack effectStack.pop() const n = effectStack.length // Point to the last... On the stack effect activeEffect = n > 0 ? effectStack[n - 1] : undefined } } } stop() { if (this.active) { cleanupEffect(this) if (this.onStop) { this.onStop() } this.active = false } } }
You can see ,effect
The implementation of the function has been modified and adjusted , For internal use ReactiveEffect
Class creates a _effect
example , And the function returns runner
Pointing to ReactiveEffect
Class run
Method .
That is, execute the side effect function effect
Function time , This is what is actually being done run
function .
When run
Function execution time , We noticed that cleanup
Functions no longer execute by default , In encapsulated functions fn
Before execution , First, execute trackOpBit = 1 << ++effectTrackDepth
Record trackOpBit
, Then compare whether the recursion depth exceeds maxMarkerBits
, If exceeded ( Usually not ) The old cleanup
Logic , If not, execute initDepMarkers
Mark dependencies , Look at its implementation :
const initDepMarkers = ({ deps }) => { if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].w |= trackOpBit // Tag dependencies have been collected } } }
initDepMarkers
Function implementation is simple , Traverse _effect
In the instance deps
attribute , For each dep
Of w
Property marked as trackOpBit
Value .
And then it will execute fn
function , It is the function encapsulated by the side effect function , For example, for component rendering ,fn
Is the component rendering function .
When fn
When the function is executed , Will access responsive data , Will trigger them getter
, And then perform track
The function performs dependency collection . Corresponding , The process of dependency collection has also been adjusted :
function track(target, type, key) { if (!isTracking()) { return } let depsMap = targetMap.get(target) if (!depsMap) { // Every target Corresponding to one depsMap targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { // Every key Corresponding to one dep aggregate depsMap.set(key, (dep = createDep())) } const eventInfo = (process.env.NODE_ENV !== 'production') ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } function trackEffects(dep, debuggerEventExtraInfo) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // Mark as new dependency dep.n |= trackOpBit // If dependencies have been collected , There is no need to collect shouldTrack = !wasTracked(dep) } } else { // cleanup Pattern shouldTrack = !dep.has(activeEffect) } if (shouldTrack) { // Collect currently active effect As a dependency dep.add(activeEffect) // Currently active effect collect dep Set as dependency activeEffect.deps.push(dep) if ((process.env.NODE_ENV !== 'production') && activeEffect.onTrack) { activeEffect.onTrack(Object.assign({ effect: activeEffect }, debuggerEventExtraInfo)) } } }
We found that , When creating a dep
When , It's through execution createDep
Method , Besides , stay dep
Put the previously activated effect
Before collecting as dependencies , Will judge this dep
Whether it has been collected , If it has been collected , There is no need to collect again . Besides , This will be judged here dep
Is it a new dependency , If not , Mark as new .
Next , Let's see fn
Logic after execution :
finally { if (effectTrackDepth <= maxMarkerBits) { // Complete dependency tag finalizeDepMarkers(this) } // Restore to the previous level trackOpBit = 1 << --effectTrackDepth resetTracking() // Out of the stack effectStack.pop() const n = effectStack.length // Point to the last... On the stack effect activeEffect = n > 0 ? effectStack[n - 1] : undefined }
Under the condition that the dependency tag is satisfied , You need to perform finalizeDepMarkers
Complete dependency tag , Look at its implementation :
const finalizeDepMarkers = (effect) => { const { deps } = effect if (deps.length) { let ptr = 0 for (let i = 0; i < deps.length; i++) { const dep = deps[i] // Once collected but not new dependencies , You need to remove if (wasTracked(dep) && !newTracked(dep)) { dep.delete(effect) } else { deps[ptr++] = dep } // Empty state dep.w &= ~trackOpBit dep.n &= ~trackOpBit } deps.length = ptr } }
finalizeDepMarkers
The main thing to do is to find those dependencies that have been collected but have not been collected in the new round of dependency collection , from deps
Remove . This is actually to solve the needs mentioned above cleanup
The problem of the scene : Reactive objects not accessed during the rendering of new components , Then its change should not trigger the re rendering of the component .
The above realizes the optimization of dependency collection , You can see that compared with each previous execution effect
All functions need to clear dependencies first , Then add the dependent process , The current implementation will be executed every time effect
The dependent state is marked before the wrapped function , Dependencies that have been collected during the process will not be collected repeatedly , After execution effect
The function also removes the dependencies that have been collected but are not collected in the new round of dependency collection .
After optimization, for dep
Operations that depend on collections are reduced , Naturally, the performance is optimized .
Response type API The optimization of the
Response type API The optimization of is mainly reflected in ref
、computed
etc. API The optimization of the .
With ref
API For example , Take a look at its implementation before optimization :
function ref(value) { return createRef(value) } const convert = (val) => isObject(val) ? reactive(val) : val function createRef(rawValue, shallow = false) { if (isRef(rawValue)) { // If the incoming is a ref, Then return to yourself , Handle nesting ref The situation of . return rawValue } return new RefImpl(rawValue, shallow) } class RefImpl { constructor(_rawValue, _shallow = false) { this._rawValue = _rawValue this._shallow = _shallow this.__v_isRef = true // Not shallow The situation of , If its value is an object or array , Then the recursive response this._value = _shallow ? _rawValue : convert(_rawValue) } get value() { // to value Attribute addition getter, And do dependency collection track(toRaw(this), 'get' /* GET */, 'value') return this._value } set value(newVal) { // to value Attribute addition setter if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) // Distribution notice trigger(toRaw(this), 'set' /* SET */, 'value', newVal) } } }
ref
Function returned createRef
The return value of the function execution , And in the createRef
Inside , First, we deal with nested ref
The situation of , If the incoming rawValue
It's also a ref
, Then go straight back rawValue
; Then go back to RefImpl
Instance of object .
and RefImpl
Internal implementation , Mainly an instance of hijacking it value
Attribute getter
and setter
.
When accessing a ref
Object's value
attribute , Will trigger getter
perform track
Function does dependency collection and returns its value ; When modifying a ref
Object's value
value , It triggers setter
Set the new value and execute trigger
Function dispatch notification , If the new value newVal
Is an object or array type , So turn it into a reactive
object .
Next , Let's see Vue.js 3.2 Changes related to the implementation of this part :
class RefImpl { constructor(value, _shallow = false) { this._shallow = _shallow this.dep = undefined this.__v_isRef = true this._rawValue = _shallow ? value : toRaw(value) this._value = _shallow ? value : convert(value) } get value() { trackRefValue(this) return this._value } set value(newVal) { newVal = this._shallow ? newVal : toRaw(newVal) if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) triggerRefValue(this, newVal) } } }
The main change is to ref
Object's value
Property to execute the logic that relies on collecting and dispatching notifications .
stay Vue.js 3.2 Version of ref
In the implementation of , About dependency collection , From the original track
The function is changed to trackRefValue
, Look at its implementation :
function trackRefValue(ref) { if (isTracking()) { ref = toRaw(ref) if (!ref.dep) { ref.dep = createDep() } if ((process.env.NODE_ENV !== 'production')) { trackEffects(ref.dep, { target: ref, type: "get" /* GET */, key: 'value' }) } else { trackEffects(ref.dep) } } }
As you can see here, just put ref
The related dependencies of are saved to dep
Properties of the , And in the track
Function implementation , Will keep dependencies globally targetMap
in :
let depsMap = targetMap.get(target) if (!depsMap) { // Every target Corresponding to one depsMap targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { // Every key Corresponding to one dep aggregate depsMap.set(key, (dep = createDep())) }
obviously ,track
You may need to make multiple judgments and set logic inside the function , And save dependencies to ref
Object's dep
Attribute omits this series of judgment and settings , To optimize performance .
Corresponding ,ref
The implementation of the distribution notice part , From the original trigger
The function is changed to triggerRefValue
, Look at its implementation :
function triggerRefValue(ref, newVal) { ref = toRaw(ref) if (ref.dep) { if ((process.env.NODE_ENV !== 'production')) { triggerEffects(ref.dep, { target: ref, type: "set" /* SET */, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } } } function triggerEffects(dep, debuggerEventExtraInfo) { for (const effect of isArray(dep) ? dep : [...dep]) { if (effect !== activeEffect || effect.allowRecurse) { if ((process.env.NODE_ENV !== 'production') && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { effect.scheduler() } else { effect.run() } } } }
Because directly from ref
Property gets all its dependencies and traverses the execution , No need to execute trigger
Function some additional lookup logic , Therefore, the performance has also been improved .
trackOpBit The design of the
Careful you may find , Tag dependent trackOpBit
, The left shift operator is used in each calculation trackOpBit = 1 << ++effectTrackDepth
; And when assigning values , The or operation is used :
deps[i].w |= trackOpBit dep.n |= trackOpBit
So why is it so designed ? because effect
The execution of may be recursive , In this way, you can record the dependency marking of each level .
Judging a dep
Whether it has been collected by dependency , Used wasTracked
function :
const wasTracked = (dep) => (dep.w & trackOpBit) > 0
Whether the result of the and operation is greater than 0
To judge , This requires that the nested levels of dependencies be matched when they are collected . for instance , Suppose this time dep.w
The value of is 2
, Explain that it is executed at the first layer effect
Function , But at this time, the nested in the second layer has been executed effect
function ,trackOpBit
Moving two bits to the left becomes 4
,2 & 4
The value of is 0
, that wasTracked
The return value of the function is false
, Explain that you need to collect this dependency . obviously , This demand is reasonable .
You can see , without trackOpBit
Design of bit operation , It's hard for you to deal with dependency tags at different nesting levels , This design also reflects basvanmeurs The boss has very solid basic computer skills .
summary
Generally in Vue.js The application of , The access and modification of responsive data are very frequent operations , Therefore, the performance optimization of this process , It will greatly improve the performance of the whole application .
Most people go to see it Vue.js Responsive implementation , The most likely goal is to understand the implementation principle , Little attention is paid to whether the implementation is optimal . and basvanmeurs The boss can propose the implementation of this series of optimization , And wrote a benchmark Tools to verify your optimization , It's worth learning .
I hope you will finish reading this article , In addition to praising the third company of forwarding , You can also go and see the original post , Look at their discussion , I believe you will gain more .
The performance optimization of the front end is always a direction worthy of deep exploration , I hope that in the future development , Whether it's writing a framework or a business , You can always think about the possible optimization points .
Reference material
[1] Vue.js 3.2 Upgrade Introduction : https://blog.vuejs.org/posts/vue-3.2.html
[2] basvanmeurs GitHub Address :https://github.com/basvanmeurs
[3] relevant PR Discussion address :https://github.com/vuejs/vue-next/pull/2345
[4] Thoughts on ES6 Proxies Performance: https://thecodebarbarian.com/thoughts-on-es6-proxies-performance
[5] Proxy-vs-DefineProperty repo: https://github.com/ustbhuangyi/Proxy-vs-DefineProperty
[6 ]benchmark Tools : https://github.com/basvanmeurs/vue-next-benchmarks
This article is from WeChat official account. - Front end canteen (webcanteen)
The source and reprint of the original text are detailed in the text , If there is any infringement , Please contact the [email protected] Delete .
Original publication time : 2021-08-18
Participation of this paper Tencent cloud media sharing plan , You are welcome to join us , share .
copyright notice
author[Tong ouba],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210823074626293F.html
The sidebar is recommended
- Crazy blessing! Tencent boss's "million JVM learning notes", real topic of Huawei Java interview 2020-2021
- JS JavaScript how to get the subscript of a value in the array
- How to implement injection in vuex source code?
- JQuery operation select (value, setting, selected)
- One line of code teaches you how to advertise on Tanabata Valentine's Day - Animation 3D photo album (music + text) HTML + CSS + JavaScript
- An article disassembles the pyramid architecture behind the gamefi outbreak
- BEM - a front-end CSS naming methodology
- [vue3] encapsulate custom global plug-ins
- Error using swiper plug-in in Vue
- Another ruthless character fell by 40000, which was "more beautiful" than Passat and maiteng, and didn't lose BMW
guess what you like
-
Huang Lei basks in Zhang Yixing's album, and the relationship between teachers and apprentices is no less than that in the past. Netizens envy Huang Lei
-
He was cheated by Wang Xiaofei and Li Chengxuan successively. Is an Yixuan a blessed daughter and not a blessed home?
-
Zhou Shen sang the theme song of the film "summer friends and sunny days" in mainland China. Netizen: endless aftertaste
-
Pink is Wangyuan online! Back to the peak! The new hairstyle is creamy and sassy
-
Front end interview daily 3 + 1 - day 858
-
Spring Webflux tutorial: how to build reactive web applications
-
[golang] walk into go language lesson 24 TCP high-level operation
-
August 23, 2021 Daily: less than three years after its establishment, Google dissolved the health department
-
The female doctor of Southeast University is no less beautiful than the female star. She has been married four times, and her personal experience has been controversial
-
There are many potential safety hazards in Chinese restaurant. The top of the program recording shed collapses, and the artist will fall down if he is careless
Random recommended
- Anti Mafia storm: He Yun's helpless son, Sun Xing, is destined to be caught by his dry son
- Introduction to flex flexible layout in CSS -- learning notes
- CSS learning notes - Flex layout (Ruan Yifeng tutorial summary)
- Today, let's talk about the arrow function of ES6
- Some thoughts on small program development
- Talk about mobile terminal adaptation
- Unwilling to cooperate with Wang Yibo again, Zhao Liying's fans went on a collective strike and made a public apology in less than a day
- JS function scope, closure, let, const
- Zheng Shuang's 30th birthday is deserted. Chen Jia has been sending blessings for ten years. Is it really just forgetting to make friends?
- Unveil the mystery of ascension
- Asynchronous solution async await
- Analysis and expansion of Vue infinite scroll source code
- Compression webpack plugin first screen loading optimization
- Specific usage of vue3 video play plug-in
- "The story of huiyeji" -- people are always greedy, and fairies should be spotless!
- Installing Vue devtool for chrome and Firefox
- Basic usage of JS object
- 1. JavaScript variable promotion mechanism
- Two easy-to-use animation JS that make the page move
- Front end Engineering - scaffold
- Java SQL Server intelligent fixed asset management, back end + front end + mobile end
- Mediator pattern of JavaScript Design Pattern
- Array de duplication problem solution - Nan recognition problem
- New choice for app development: building mobile applications using Vue native
- New gs8 Chengdu auto show announces interior Toyota technology blessing
- Vieira officially terminated his contract and left the team. The national security club sent blessings to him
- Less than 200000 to buy a Ford RV? 2.0T gasoline / diesel power, horizontal bed / longitudinal bed layout can be selected
- How does "heart 4" come to an end? Pinhole was boycotted by the brand, Ma Dong deleted the bad comments, and no one blessed him
- We are fearless in epidemic prevention and control -- pay tribute to the front-line workers of epidemic prevention!
- Front end, netty framework tutorial
- Xiaomi 11 | miui12.5 | android11 solves the problem that the httpcanary certificate cannot be installed
- The wireless charging of SAIC Roewe rx5 plus is so easy to use!
- Upload and preview pictures with JavaScript, and summarize the most complete mybatis core configuration file
- [25] typescript
- CSS transform Complete Guide (Second Edition) flight.archives 007
- Ajax foundation - HTTP foundation of interview essential knowledge
- Cloud lesson | explain in detail how Huawei cloud exclusive load balancing charges
- Decorator pattern of JavaScript Design Pattern
- [JS] 10. Closure application (loop processing)
- Left hand IRR, right hand NPV, master the password of getting rich