Vue3 源码解读系列(五)——响应式
响应式
响应式的本质是当数据变化后会自动执行某个函数。
映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染。
响应式的两个核心流程:
- 依赖收集
- 派发通知
Vue2
Vue2 中只有 data 中定义的数据才是响应式的,因为 data 中的数据会通过
Object.defineProperty
劫持后再挂载到 this 上,这是一个相对黑盒的行为。
/*** 观察某个对象的所有属性*/
function observe(obj) {for(const key in obj) {let internalValue = obj[key];let funcs = new Set(); // 依赖该属性的函数(注意:要用 Set 而非 Array,因为一个函数可能在多个地方都有用到该属性)Object.defineProperty(obj, key, {get: function() {// 依赖收集,记录:是哪个函数在用我if(window.__func) {funcs.add(window.__func)}return internalValue;},set: function(val) {internalValue = val;// 派发更新,运行:执行用我的函数for(let i = 0; i < funcs.length; i++) {funcs[i]();}}})}
}/*** 自动运行* @param {Function} fn - 自动运行的函数*/
function autorun(fn) {window.__func = fn;fn();window.__func = null;
}
Object.defineProperty
的缺点:
- 不能监听对象属性的新增和删除
- 初始化阶段递归执行
Object.defineProperty
带来的性能负担
Vue3
reactive
创建 reactive 对象
创建 reactive 对象主要做了 6 件事:
- 判断 target 不是对象或数组类型,直接返回
- 判断 target 已经是响应式对象,直接返回
- 判断 target 已经有 readonly 或者 reactive,返回该响应式对象
- 判断 target 不能被监听,直接返回
- 通过 Proxy 劫持 target 对象,把它变成响应式
- 给原始数据打个标记,说明它已经存在响应式对象了
/*** 响应式函数 - reactive*/
function reactive(target) {// 如果尝试把一个 readonly 变成 reactive,直接返回这个 readonlyif (target && target.__v_isReadonly) {return target}// 否则创建 reactive 对象return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}/*** 创建 reactive 对象* createReactiveObject 主要做了 6 件事:* 1、判断 target 不是对象或数组类型,直接返回* 2、判断 target 已经是响应式对象,直接返回* 3、判断 target 已经有 readonly 或者 reactive,返回该响应式对象* 4、判断 target 不能被监听,直接返回* 5、通过 Proxy 劫持 target 对象,把它变成响应式* 6、给原始数据打个标记,说明它已经存在响应式对象了*/
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {// 1、判断 target 不是对象或数组类型,直接返回 - 限制目标对象必须为对象或数组if (!isObject(target)) {if (process.env.NODE_ENV !== 'production') {console.warn(/* ... */)}return target}// 2、判断 target 已经是响应式对象,直接返回// 有个例外,如果是 readonly 作用于一个响应式对象,则继续if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {return target}// 3、判断 target 已经有 readonly 或者 reactive,返回该响应式对象 - 同一原始数据生成的响应式对象只有一个if (hasOwn(target, isReadonly ? "__v_readonly"/* readonly */ : "__v_reactive"/* reactive */)) {return isReadonly ? target.__v_readonly : target.__v_reactive}// 4、判断 target 不能被监听,直接返回if (!canObserve(target)) {return target}// 5、通过 Proxy 劫持 target 对象,把它变成响应式const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)// 6、给原始数据打个标记,说明它已经存在响应式对象了def(target, isReadonly ? "__v_readonly"/* readonly */ : "__v_reactive"/* reactive */, observed)return observed
}/*** 能否被监听* 同时满足以下三点:* 1、不存在 __v_skip 属性* 2、是可以被监听的数据类型* 3、没有被冻结*/
function canObserv(value) {// 判断是否是可以被监听的数据类型const isObservableType = makeMap('Object,Array,Map,Set,WeakMap,WeakSet')return (!value.__v_skip && isObservableType(toRawType(value)) && !Object.isFrozen(value))
}// 处理器对象
const mutableHandlers = {get, // 访问对象属性会触发 get 函数set, // 设置对象属性会触发 set 函数deleteProperty, // 删除对象属性会触发 deleteProperty 函数has, // in 操作符会触发 has 函数ownKeys // Object.getOwnPropertyNames 触发 ownKeys 函数
}
get - 依赖收集
/*** 创建 getter - 依赖收集* get 主要做了 4 件事:* 1、对特殊的 key 做代理* 2、通过 Reflect.get 求值* 3、执行 track 函数进行依赖收集* 4、判断 res 为对象或者数组,递归执行 reactive 函数把 res 变成响应式(在对象属性被访问时递归 - 懒响应式)*/
function createGetter(isReadonly = false) {return function get(target, key, receiver) {// 1、对特殊的 key 做代理// 代理 observed.__v_isReactiveif (key === "__v_isReactive") {return !isReadonly}// 代理 observed.__v_isReadonlyelse if (key === "__v_isReadonly") {return isReadonly}// 代理 observed.__v_rawelse if (key === "__v_raw") {return target}// 2、通过 Reflect.get 求值const targetIsArray = isArray(target) // 判断目标对象是否是数组if (targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)}const res = Reflect.get(target, key, receiver)// 内置 Symbol key 不需要依赖收集if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {return res}// 3、执行 track 函数进行依赖收集!isReadonly && track(target, "get", key)// 4、判断 res 为对象或者数组,递归执行 reactive 函数把 res 变成响应式(在对象属性被访问时递归 - 懒响应式)return isObject(res) ? isReadonly ? readonly(res) : reactive(res) : res}
}// arrayInstrumentations - 包含对数组一些方法修改的函数
const arrayInstrumentations = {}['includes', 'indexOf', 'lastIndexOf'].forEach(key => {arrayInstrumentations[key] = function (...args) {// toRaw - 把响应式对象转化为原始数据const arr = toRaw(this)for (let i = 0, l = this.length; i < l; i++) {// 依赖收集 - gettrack(arr, "get", i + '')}// 先尝试用参数本身,可能是响应式数据const res = arr[key](...args)if (res === -1 || res === false) {// 如果失败,再尝试把参数转成原始数据return arr[key](...args.map(toRaw))} else {return res}}
})
依赖收集
let shouldTrack = true // 是否应该收集依赖
let activeEffect // 当前激活的 effect
const targetMap = new WeakMap() // 原始数据对象 map/*** 收集依赖函数*/
function track(target, type, key) {if (!shouldTrack || activeEffect === undefined) return// 每个 target 对应一个 depsMaplet depsMap = targetMap.get(target)if (!depsMap) {targetMap.set(target, (depsMap = new Map()))}// 每个 key 对应一个 dep 集合let dep = depsMap.get(key)if (!dep) {depsMap.set(key, (dep = new Set()))}if (!dep.has(activeEffect)) {// 收集当前激活的 effect 作为依赖dep.add(activeEffect)// 当前激活的 effect 收集 dep 集合作为依赖activeEffect.deps.push(dep)}
}
set - 派发通知
/*** 创建 setter - 事件派发* set 主要做了 2 件事:* 1、通过 Reflect.set 求值* 2、通过 trigger 函数派发通知*/
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key]value = toRaw(value)// 1、通过 Reflect.set 求值const result = Reflect.set(target, key, value, receiver)// 2、通过 trigger 函数派发通知// 如果目标的原型链也是一个 proxy,通过 Reflect.set 修改原型链上的属性会再次触发 setter,这种情况下就没必要触发两次 trigger 了if (target === toRaw(receiver)) {// 根据 key 是否存在于目标对象上判断是 新增属性 还是 修改属性,然后通过 trigger 函数派发通知const hadKey = hasOwn(target, key)if (!hadKey) {trigger(target, "add"/* ADD */, key, value)} else if (hasChanged(value, oldValue)) {trigger(target, "set"/* SET */, key, value, oldValue)}}return result}
}
派发通知
const targetMap = new WeakMap() // 原始数据对象 map
/*** 派发通知 - 根据 target 和 key 从 targetMap 中找到相关的所有副作用函数遍历执行一次* trigger 主要做了 4 件事:* 1、通过 targetMap 拿到 target 对应的依赖集合 depsMap* 2、创建运行的 effects 集合* 3、根据 key 从 depsMap 中找到对应的 effects(SET|ADD|DELETE),添加到 effects 集合* 4、遍历 effects,执行相关的副作用函数*/
function trigger(target, type, key, newValue) {// 1、通过 targetMap 拿到 target 对应的依赖集合 depsMapconst depsMap = targetMap.get(target)// 没有依赖,直接返回if (!depsMap) return// 2、创建 effects 集合const effects = new Set()// 添加 effects 的函数const add = (effectsToAdd) => {if (effectsToAdd) {effectsToAdd.forEach(effect => {effects.add(effect)})}}// 3、根据 key 从 depsMap 中找到对应的 effects(SET|ADD|DELETE),添加到 effects 集合if (key !== void 0) {add(depsMap.get(key))}// 执行副作用函数的函数const run = (effect) => {// 调度执行if (effect.options.scheduler) {effect.options.scheduler(effect)}// 直接运行else {effect()}}// 4、遍历 effects,执行相关的副作用函数effects.forEach(run)
}const effectStack = [] // 全局 effect 栈
let activeEffect // 当前激活 effect
/*** 副作用函数*/
function effect(fn, options = EMPTY_OBJ) {// 如果 fn 已经是一个 effect 函数了,则指向原始函数if (isEffect(fn)) {fn = fn.raw}// 通过 createReactiveEffect 创建一个新的 effect,它是一个响应式的副作用的函数const effect = createReactiveEffect(fn, options)// lazy 配置,计算属性会用到,非 lazy 则直接执行一次if (!options.lazy) {effect()}return effect
}/*** 创建响应式副作用函数,并且添加一些额外的属性*/
function createReactiveEffect(fn, options) {/*** 响应式副作用函数* reactiveEffect 主要做了 2 件事:* 1、把全局的 activeEffect 指向它* 2、执行被包装的原始函数 fn 即可*/const effect = function reactiveEffect(...args) {// 非激活状态,则判断如果非调度执行,则直接执行原始函数if (!effect.active) {return options.scheduler ? undefined : fn(...args)}if (!effectStack.includes(effect)) {// 入栈前执行 cleanup 清空 reactiveEffect 对应的依赖(防止添加 render 函数导致组件重新渲染,例如 v-show、v-if 的情况)cleanup(effect)try {// 开启全局 shouldTrack,允许依赖收集enableTracking()// 压栈effectStack.push(effect)// 1、把全局的 activeEffect 指向它activeEffect = effect// 2、执行被包装的原始函数 fn 即可return fn(...args)} finally {// 出栈effectStack.pop()// 恢复 shouldTrack 开启之前的状态resetTracking()// 指向栈最后一个 effect(目的是解决 effect 嵌套的问题)activeEffect = effectStack[effectStack.length - 1]}}}effect.id = uid++ // 唯一标识effect.isEffect = true // 标识是一个 effect 函数effect.active = true // effect 自身的状态effect.raw = fn // 包装的原始函数effect.deps = [] // effect 对应的依赖,双向指针,依赖包含对 effect 的引用,effect 也包含对依赖的引用effect.options = options // effect 的相关配置return effect
}/*** 清空 effect 引用的依赖*/
function cleanup(effect) {const { deps } = effectif (deps.length) {for (leti = 0; i < deps.length; i++) {deps[i].delete(effect)}deps.length = 0}
}
readonly
/*** 只读响应式对象*/
function readonly(target) {// 和 reactive 一样都是通过 createReactiveObject 来创建响应式对象,区别在于第二个参数 isReadonly 不同return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers)
}/*** 只读响应式对象处理对象* readonlyHandlers 和 mutableHandlers 的区别主要在于 get、set、deleteProperty 三个函数上*/
const readonlyHandlers = {get: readonlyGet, // createGetter 的返回值has,ownKeys,set(target, key) {// 只读对象不允许修改,所以直接警告并返回if ((process.env.NODE_ENV !== 'production')) {console.warn(/* ... */)}return true},deleteProperty(target, key) {// 只读对象不允许删除,所以直接警告并返回if ((process.env.NODE_ENV !== 'production')) {console.warn(/* ... */)}return true}
}/*** readonlyGet 的实现*/
function createGetter(isReadonly = false) {return function get(target, key, receiver) {// ...// 是 readonly 响应式对象,则不需要依赖收集,节省性能!isReadonly && track(target, "get"/* GET */, key)// 如果 res 是个对象或者数组类型,则递归执行 readonly 函数把 res 只读化return isObject(res) ? isReadonly ? readonly(res) : reactive(res) : res}
}
ref
/*** ref 的实现*/
function ref(value) {return createRef(value)
}const convert = (val) => isObject(val) ? reactive(val) : val/*** 创建 ref 对象*/
function createRef(rawValue) {// 如果传入的就是一个 ref,那么返回自身即可,处理嵌套 ref 的情况if (isRef(rawValue)) {return rawValue}// 如果是对象或者数组类型,则转换一个 reactive 对象let value = convert(rawValue)const r = {_v_isRef: true,get value() { // getter// 依赖收集,key 为固定的 valuetrack(r, "get"/* GET */, 'value')return value},set value(newVal) { // setter,只处理 value 属性的修改// 判断有变化后if (hasChanged(toRaw(newVal), rawValue)) {// 1、更新值rawValue = newValvalue = convert(newVal)// 2、派发通知trigger(r, "set"/* SET */, 'value', void 0)}}}return r
}