Side Effect Function
A side effect function refers to a function that produces side effects, as shown in the following code:
function effect(){ document.body.innerText = 'hello vue3' }
When the effect function is executed, it will set the text content of the body, but Any function except the effect function can read or set the text content of the body. In other words, the execution of the effect function will directly or indirectly affect the execution of other functions. At this time, we say that the effect function has side effects. Side effects are easy to occur. For example, if a function modifies a global variable, this is actually a side effect.
// 全局变量 let val = 1 function effect() { val = 2 // 修改全局变量,产生副作用 }
Global variables of side-effect functions
In the side-effect module, several global variables are defined. Knowing these variables in advance will help us understand the generation and calling process of the side-effect function.
// packages/reactivity/src/effect.ts export type Dep = Set<ReactiveEffect> & TrackedMarkers type KeyToDepMap = Map<any, Dep> // WeakMap 集合存储副作用函数 const targetMap = new WeakMap<any, KeyToDepMap>() // 用一个全局变量存储当前激活的 effect 函数 export let activeEffect: ReactiveEffect | undefined // 标识是否开启了依赖收集 export let shouldTrack = true const trackStack: boolean[] = []
targetMap
targetMap is a collection of WeakMap types, used to store side effect functions. From the type definition, we can see the data structure of targetMap:
WeakMap consists of
target --> Map
Map consists of
key --> Set
const map = new Map(); const weakMap = new WeakMap(); (function() { const foo = {foo: 1}; const bar = {bar: 2}; map.set(foo, 1); // foo 对象是 map 的key weakMap.set(bar, 2); // bar 对象是 weakMap 的 key })In the above code, The map and weakMap constants are defined, corresponding to instances of Map and WeakMap respectively. Two objects are defined inside the immediately executed function expression: foo and bar, which serve as the keys of map and weakMap respectively. After the function expression is executed, for the object foo, it is still referenced as the key of the map, so
the garbage collector will not remove it from the memory. We can still print out object foo via map.keys.
For object bar, since the key of WeakMap is a weak reference, it does not affect the work of the garbage collector, so once the expression is executed, the garbage collector will remove the object bar from the memory. And we cannot obtain the key value of weakMap, so we cannot obtain the object bar through weakMap. Simply put,WeakMap is a weak reference to key and does not affect the work of the garbage collector**. According to this feature, once the key is recycled by the garbage collector, the corresponding key and value will not be accessible. Therefore, WeakMap is often used to store information that is valuable only when the object referenced by key exists (has not been recycled)**.
For example, in the above scenario, if the target object does not have any references, it means that the user side no longer needs it, and the garbage collector will complete the recycling task. But if you use Map instead of WeakMap, even if the user-side code does not have any reference to the target, the target will not be recycled, which may eventually lead to memory overflow. activeEffectactiveEffect variable is used to maintain the side effects currently being executed shouldTrackshouldTrack variable is used to identify whether dependency collection is enabled, only the value of shouldTrack When it is true, dependency collection is performed, that is, the side-effect function is added to the dependency collection. Implementation of side effectseffect functioneffect API is used to create a side effect function, accepting two parameters, which are user-defined fn function and options options. The source code is as follows:// packages/reactivity/src/effect.ts export function effect<T = any>( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { // 当传入的 fn 中存在 effect 副作用时,将这个副作用的原始函数赋值给 fn if ((fn as ReactiveEffectRunner).effect) { fn = (fn as ReactiveEffectRunner).effect.fn } // 创建一个副作用 const _effect = new ReactiveEffect(fn) if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) } // 如果不是延迟执行的,则立即执行一次副作用函数 if (!options || !options.lazy) { _effect.run() } // 通过 bind 函数返回一个新的副作用函数 const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 将副作用添加到新的副作用函数上 runner.effect = _effect // 返回这个新的副作用函数 return runner }From the above code, we can know that when there is an effect side effect in the passed parameter fn, the original function of this side effect is assigned to fn. Then call the ReactiveEffect class to create an encapsulated side effect function. In some scenarios, we do not want the effect to be executed immediately, but we want it to be executed when needed. We can achieve this by adding the lazy attribute to options. In the effect function source code, determine the value of the options.lazy option. When the value is true, the side effect function will not be executed immediately, thereby achieving a lazy execution effect. Then return a new side effect function runner through the bind function. This of this new function is designated as _effect, and _effect is added to the effect attribute of the new side effect function, and finally the new side effect function is returned. . Since the effect API returns the encapsulated side effect function, the original side effect function is stored in the effect attribute of the encapsulated side effect function, so if you want to get the side effect function passed in by the user, you need to pass
fn.effect.fn to obtain.
The ReactiveEffect class is called in the effect function to create side effects. Next, let’s look at the implementation of the ReactiveEffect class. ReactiveEffect class// packages/reactivity/src/effect.ts
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
/**
* @internal
*/
allowRecurse?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
// 如果 effect 已停用,返回原始副作用函数执行后的结果
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 创建一个新的副作用前将当前正在执行的副作用存储到新建的副作用的 parent 属性上,解决嵌套effect 的情况
this.parent = activeEffect
// 将创建的副作用设置为当前正则正在执行的副作用
activeEffect = this
// 将 shouldTrack 设置为 true,表示开启依赖收集
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
// 初始化依赖
initDepMarkers(this)
} else {
// 清除依赖
cleanupEffect(this)
}
// 返回原始副作用函数执行后的结果
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
// 重置当前正在执行的副作用
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
// 停止(清除) effect
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
In the ReactiveEffect class, a run method is defined. This run method is the actual running method when creating side effects. Each time an update is dispatched, this run method will be executed, thereby updating the value.
全局变量 activeEffect 用来维护当前正在执行的副作用,当存在嵌套渲染组件的时候,依赖收集后,副作用函数会被覆盖,即 activeEffect 存储的副作用函数在嵌套 effect 的时候会被内层的副作用函数覆盖。为了解决这个问题,在 run 方法中,将当前正在执行的副作用activeEffect保存到新建的副作用的 parent 属性上,然后再将新建的副作用设置为当前正在执行的副作用。在新建的副作用执行完毕后,再将存储到 parent 属性的副作用重新设置为当前正在执行的副作用。
在 ReactiveEffect 类中,还定义了一个 stop 方法,该方法用来停止并清除当前正在执行的副作用。
track 收集依赖
当使用代理对象访问对象的属性时,就会触发代理对象的 get 拦截函数执行,如下面的代码所示:
const obj = { foo: 1 } const p = new Proxy(obj, { get(target, key, receiver) { track(target, key) return Reflect.get(target, key, receiver) } }) p.foo
在上面的代码中,通过代理对象p 访问 foo 属性,便会触发 get 拦截函数的执行,此时就在 get 拦截函数中调用 track 函数进行依赖收集。源码中 get 拦截函数的解析可阅读《Vue3 源码解读之非原始值的响应式原理》一文中的「访问属性的拦截」小节。
下面,我们来看看 track 函数的实现。
track 函数
// packages/reactivity/src/effect.ts // 收集依赖 export function track(target: object, type: TrackOpTypes, key: unknown) { // 如果开启了依赖收集并且有正在执行的副作用,则收集依赖 if (shouldTrack && activeEffect) { // 在 targetMap 中获取对应的 target 的依赖集合 let depsMap = targetMap.get(target) if (!depsMap) { // 如果 target 不在 targetMap 中,则加入,并初始化 value 为 new Map() targetMap.set(target, (depsMap = new Map())) } // 从依赖集合中获取对应的 key 的依赖 let dep = depsMap.get(key) if (!dep) { // 如果 key 不存在,将这个 key 作为依赖收集起来,并初始化 value 为 new Set() depsMap.set(key, (dep = createDep())) } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }
在 track 函数中,通过一个 if 语句判断是否进行依赖收集,只有当 shouldTrack 为 true 并且存在 activeEffect,即开启了依赖收集并且存在正在执行的副作用时,才进行依赖收集。
然后通过 target 对象从 targetMap 中尝试获取对应 target 的依赖集合depsMap,如果 targetMap 中不存在当前target的依赖集合,则将当前 target 添加进 targetMap 中,并将 targetMap 的 value 初始化为 new Map()。
// 在 targetMap 中获取对应的 target 的依赖集合 let depsMap = targetMap.get(target) if (!depsMap) { // 如果 target 不在 targetMap 中,则加入,并初始化 value 为 new Map() targetMap.set(target, (depsMap = new Map())) }
接着根据target中被读取的 key,从依赖集合depsMap中获取对应 key 的依赖,如果依赖不存在,则将这个 key 的依赖收集到依赖集合depsMap中,并将依赖初始化为 new Set()。
// 从依赖集合中获取对应的 key 的依赖 let dep = depsMap.get(key) if (!dep) { // 如果 key 不存在,将这个 key 作为依赖收集起来,并初始化 value 为 new Set() depsMap.set(key, (dep = createDep())) }
最后调用 trackEffects 函数,将副作用函数收集到依赖集合depsMap中。
const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo)
trackEffects 函数
// 收集副作用函数 export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. // 如果依赖中并不存当前的 effect 副作用函数 shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 将当前的副作用函数收集进依赖中 dep.add(activeEffect!) // 并在当前副作用函数的 deps 属性中记录该依赖 activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack( Object.assign( { effect: activeEffect! }, debuggerEventExtraInfo ) ) } } }
在 trackEffects 函数中,检查当前正在执行的副作用函数 activeEffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
trigger 派发更新
当对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:
const obj = { foo: 1 } const p = new Proxy(obj, { // 拦截设置操作 set(target, key, newVal, receiver){ // 如果属性不存在,则说明是在添加新属性,否则设置已有属性 const type = Object.prototype.hasOwnProperty.call(target,key) ? 'SET' : 'ADD' // 设置属性值 const res = Reflect.set(target, key, newVal, receiver) // 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数 trigger(target,key,type) return res } // 省略其他拦截函数 }) p.foo = 2
在上面的代码中,通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行,此时就在 set 拦截函数中调用 trigger 函数中派发更新。源码中 set 拦截函数的解析可阅读《Vue3 源码解读之非原始值的响应式原理》一文中的「设置属性操作的拦截」小节。
下面,我们来看看 track 函数的实现。
trigger 函数
trigger 函数的源码如下:
export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = targetMap.get(target) // 该 target 从未被追踪,则不继续执行 if (!depsMap) { // never been tracked return } // 存放所有需要派发更新的副作用函数 let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target // 当需要清除依赖时,将当前 target 的依赖全部传入 deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { // 处理数组的特殊情况 depsMap.forEach((dep, key) => { // 如果对应的长度, 有依赖收集需要更新 if (key === 'length' || key >= (newValue as number)) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE // 在 SET | ADD | DELETE 的情况,添加当前 key 的依赖 if (key !== void 0) { deps.push(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 ADD 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 DELETE 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } } const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] // 将需要执行的副作用函数收集到 effects 数组中 for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
在 trigger 函数中,首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。
const depsMap = targetMap.get(target) // 该 target 从未被追踪,则不继续执行 if (!depsMap) { // never been tracked return }
接着创建一个 Set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。
// 存放所有需要派发更新的副作用函数 let deps: (Dep | undefined)[] = []
接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。
如果操作类型是 TriggerOpTypes.CLEAR ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。
if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target // 当需要清除依赖时,将当前 target 的依赖全部传入 deps = [...depsMap.values()] }
如果操作目标是数组,并且修改了数组的 length 属性,需要把与 length 属性相关联的副作用函数以及索引值大于或等于新的 length 值元素的相关联的副作用函数从 depsMap 中取出并添加到 deps 集合中。
else if (key === 'length' && isArray(target)) { // 如果操作目标是数组,并且修改了数组的 length 属性 depsMap.forEach((dep, key) => { // 对于索引大于或等于新的 length 值的元素, // 需要把所有相关联的副作用函数取出并添加到 deps 中执行 if (key === 'length' || key >= (newValue as number)) { deps.push(dep) } }) }
如果当前的 key 不为 undefined,则将与当前key相关联的副作用函数添加到 deps 集合中。注意这里的判断条件 void 0,是通过 void 运算符的形式表示 undefined 。
if (key !== void 0) { deps.push(depsMap.get(key)) }
接下来通过 Switch 语句来收集操作类型为 ADD、DELETE、SET 时与 ITERATE_KEY 和 MAP_KEY_ITERATE_KEY 相关联的副作用函数。
// also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 ADD 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 DELETE 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break }
最后调用 triggerEffects 函数,传入收集的副作用函数,执行派发更新。
const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] // 将需要执行的副作用函数收集到 effects 数组中 for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } }
triggerEffects 函数
export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization // 遍历需要执行的副作用函数集合 for (const effect of isArray(dep) ? dep : [...dep]) { // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行 if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { // 如果一个副作用函数存在调度器,则调用该调度器 effect.scheduler() } else { // 否则直接执行副作用函数 effect.run() } } } }
在 triggerEffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
The above is the detailed content of Vue3 side effect function and dependency collection example analysis. For more information, please follow other related articles on the PHP Chinese website!

The main disadvantages of Vue.js include: 1. The ecosystem is relatively new, and third-party libraries and tools are not as rich as other frameworks; 2. The learning curve becomes steep in complex functions; 3. Community support and resources are not as extensive as React and Angular; 4. Performance problems may be encountered in large applications; 5. Version upgrades and compatibility challenges are greater.

Netflix uses React as its front-end framework. 1.React's component development and virtual DOM mechanism improve performance and development efficiency. 2. Use Webpack and Babel to optimize code construction and deployment. 3. Use code segmentation, server-side rendering and caching strategies for performance optimization.

Reasons for Vue.js' popularity include simplicity and easy learning, flexibility and high performance. 1) Its progressive framework design is suitable for beginners to learn step by step. 2) Component-based development improves code maintainability and team collaboration efficiency. 3) Responsive systems and virtual DOM improve rendering performance.

Vue.js is easier to use and has a smooth learning curve, which is suitable for beginners; React has a steeper learning curve, but has strong flexibility, which is suitable for experienced developers. 1.Vue.js is easy to get started with through simple data binding and progressive design. 2.React requires understanding of virtual DOM and JSX, but provides higher flexibility and performance advantages.

Vue.js is suitable for fast development and small projects, while React is more suitable for large and complex projects. 1.Vue.js is simple and easy to learn, suitable for rapid development and small projects. 2.React is powerful and suitable for large and complex projects. 3. The progressive features of Vue.js are suitable for gradually introducing functions. 4. React's componentized and virtual DOM performs well when dealing with complex UI and data-intensive applications.

Vue.js and React each have their own advantages and disadvantages. When choosing, you need to comprehensively consider team skills, project size and performance requirements. 1) Vue.js is suitable for fast development and small projects, with a low learning curve, but deep nested objects can cause performance problems. 2) React is suitable for large and complex applications, with a rich ecosystem, but frequent updates may lead to performance bottlenecks.

Vue.js is suitable for small to medium-sized projects, while React is suitable for large projects and complex application scenarios. 1) Vue.js is easy to use and is suitable for rapid prototyping and small applications. 2) React has more advantages in handling complex state management and performance optimization, and is suitable for large projects.

Vue.js and React each have their own advantages: Vue.js is suitable for small applications and rapid development, while React is suitable for large applications and complex state management. 1.Vue.js realizes automatic update through a responsive system, suitable for small applications. 2.React uses virtual DOM and diff algorithms, which are suitable for large and complex applications. When selecting a framework, you need to consider project requirements and team technology stack.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

Dreamweaver CS6
Visual web development tools
