Vue3 源码解读系列(五)——响应式

响应式

响应式的本质是当数据变化后会自动执行某个函数

映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染。

响应式的两个核心流程:

  1. 依赖收集
  2. 派发通知

Vue2

Vue3 源码解读系列(五)——响应式_第1张图片

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 的缺点:

  1. 不能监听对象属性的新增删除
  2. 初始化阶段递归执行 Object.defineProperty 带来的性能负担

Vue3

Vue3 源码解读系列(五)——响应式_第2张图片

reactive

创建 reactive 对象

创建 reactive 对象主要做了 6 件事:

  1. 判断 target 不是对象或数组类型,直接返回
  2. 判断 target 已经是响应式对象,直接返回
  3. 判断 target 已经有 readonly 或者 reactive,返回该响应式对象
  4. 判断 target 不能被监听,直接返回
  5. 通过 Proxy 劫持 target 对象,把它变成响应式
  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 函数
}
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_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
    }
  }
})

依赖收集

Vue3 源码解读系列(五)——响应式_第3张图片

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)
  }
}
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 对应的依赖集合 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
  }
}

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 为固定的 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
}

你可能感兴趣的:(Vue,vue.js,javascript,前端,前端框架)