响应式的本质是当数据变化后会自动执行某个函数。
映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染。
响应式的两个核心流程:
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
带来的性能负担
创建 reactive 对象主要做了 6 件事:
/**
* 响应式函数 - reactive
*/
function reactive(target) {
// 如果尝试把一个 readonly 变成 reactive,直接返回这个 readonly
if (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 函数
}
/**
* 创建 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_isReactive
if (key === "__v_isReactive") {
return !isReadonly
}
// 代理 observed.__v_isReadonly
else if (key === "__v_isReadonly") {
return isReadonly
}
// 代理 observed.__v_raw
else 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++) {
// 依赖收集 - get
track(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 对应一个 depsMap
let 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)
}
}
/**
* 创建 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 对应的依赖集合 depsMap
const 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 } = effect
if (deps.length) {
for (leti = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
/**
* 只读响应式对象
*/
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 的实现
*/
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 为固定的 value
track(r, "get"/* GET */, 'value')
return value
},
set value(newVal) { // setter,只处理 value 属性的修改
// 判断有变化后
if (hasChanged(toRaw(newVal), rawValue)) {
// 1、更新值
rawValue = newVal
value = convert(newVal)
// 2、派发通知
trigger(r, "set"/* SET */, 'value', void 0)
}
}
}
return r
}