详解 vue2、vue3 响应式原理及其对比

515 阅读10分钟

详解 vue2、vue3响应式原理及其对比

前言

本文是对vue2、vue3、react的响应式原理进行一个理解和对比,希望能帮助自己和大家更深入的了解框架背后的实现。
知识点:响应式、构造函数、原型、原型链、继承、ES5:Object.defineProperty、call、ES6:ProxyReflectClass

概念解析

  • 响应式:

    什么是响应式,在 vue 的官方文档中是这么介绍的:响应式原理的核心就是观测数据的变化,数据发生变化以后能通知到对应的观察者来执行相关的逻辑。

  • 构造函数

    在<JavaScript高级程序设计第四版>中是这么定义的:任何函数只要使用new操作符调用就是构造函数,而不使用new操作符调用的函数就是普通函数。

  • 原型:

    什么是原型,在<JavaScript高级程序设计第四版>中是这么介绍的:每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)。所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。Person.prototype.constructor指向Person。__proto__属性可以访问对象的原型

      console.log(Person.prototype.constructor === Person); // true
    
      console.log(Person.prototype.__proto__ === Object.prototype); // true
      console.log(Person.prototype.__proto__.constructor === Object); // true
    
  • 原型链:

    <JavaScript高级程序设计第四版>:每个构造函数都有一个原型对象,原型有一个属性指向构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例,就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。

  • 继承:

    实现继承是ECMAScript唯一支持的继承方式,而这主要是通过原型链实现的。

  • Object.defineProperty

    MDN定义:静态方法Object.defineProperty()直接在对象上定义一个新的属性,或者修改一个对象上已有的属性,并返回该对象。

/**
 *  @param obj: 在其上定义属性的对象  
 *  @param prop: Symbol要定义或修改的属性的名称
 *  @param descriptor: 正在定义或修改的属性的描述符
 *  return: 传递给函数的对象
 */
// descriptor: 中着重注意get 和 set函数
Object.defineProperty(obj, prop, descriptor)
  • call

    w3c:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象。 说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

  • Proxy

    w3c定义:一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

  • reflect

    ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。 Reflect对象的方法与 Proxy对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法

  • Class

    MDN定义:在类声明创建与使用基于原型的继承给定名称的新类。

vue2.x 中响应式的实现

从定义出发可以拆分为俩步:

  1. 观测数据的变化

    vue中实例都是通过 new Vue()构造函数生成的

// src/core/instance/index.js
function Vue(options) {
    if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

通过原型上挂载_init函数进行方法绑定

// src/core/instance/init.js
export function initMixin(Vue: Class<component>) {
    Vue.protototype._init = function (options?: Object){
        const vm: Component = this
        // ...
        initState(vm)
    }
}

通过initState函数进行数据劫持与代理

// src/core/instance/state.js
export function initState(vm: Component) {
    // vm._watchers = []
    // const opts = vm.$options
    // if (opts.props) initProps(vm, opts.props)
    // if (opts.methods) initMethods(vm, opts.methods)
    //...
    if (opts.data) {
        initData(vm)
    } else {
        observe(vm._data = {}, true /* asRootData */)
    }
    // ...
    // if (opts.computed) initComputed(vm, opts.computed)
    // if (opts.watch && opts.watch !== nativeWatch){
    //     initWatch(vm, opts.watch);
    // }
}

function initData(vm: Component) {
    let data = vm.$options.data;
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
    // ...
    // proxy data on instance
    const keys = Object.keys(data)
    let i = keys.length
    while(i--){
        const key = keys[i]
        // 看值的第一个字符串是否为_或$
        if (!isReserved(key)){
            proxy(vm, `_data`, key)
        }
    }
    // observe data
    observe(data, true, /* asRootData */)
}

const sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
}

// 代理
export function proxy (target: Object, sourceKey: string, key: string){
    sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter (val) {
        return this[souceKey][key] = val
    }
}

判断是否是数组进行递归定义defineProperty

// src/core/observe/index.js
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试为值创建观察者实例,
 * 如果观察成功返回新的观察者,
 * 或者现有的观察器(如果值已经有一个观察器)。
 */

export function observe (value: any, asRootData: ?boolean): Observer | void {
    // 如果不是对象或者值为虚拟节点则直接返回
    if (!isObject(value) || value instanceof VNode){
        return
    }
    let ob: Observer | void
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__
    } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
    ) {
        ob = new Observer(value)
    }
    return ob
}
/* 
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 * 每个观察到的观察者类
 * 对象。连接后,观察者将转换目标
 * 对象的属性键插入getter/setters
 * 收集依赖项并发送更新。
 */
export class Observer {
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that have this object as root $data
    //将此对象作为根$data的虚拟数
    
    constructor(value: any){
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        if (Array.isArray(value)){
            if (hasProto){
                protoAugument(value, arrayMethods)
            } else {
                copyAugment(value, arrayMethods, arrayKeys)
            }
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    /**
     * Walk through all properties and convert them into 
     * getter/setters.This method should only be called when
     * value type is Object.
     * 浏览所有属性并将其转换为
     * getter/setters。只有在
     * 值类型为Object。
     */
    walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++){
            defineReactive(obj, keys[i])
        }
    }

    /**
     * Observe a list of Array items.
     * 观察数组项列表。
     */
    observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++){
            observe(items[i])
        }
    }
}

/**
 * Define a reactive property on an Object.
 * 在对象上定义被动属性。
 */
 export function defineReactive (
     obj: Object,
     key: string,
     val: any,
     customSetter?: ?Function,
     shallow?: boolean 
 ) {
    const dep = new Dep()

    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
        return
    }

    // cater for pre-defined getter/setters
    满足预定义的getter/setter
    const getter = property && property.get
    const setter = property && property.set
    if((!getter | setter) && arguments.length === 2){
        val = obj[key]
    } 

    let childOb = !shallow && observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
                if (childObj) {
                    childObj.dep.depend()
                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val
            /* eslint-disable no-self-compare */
            /* eslint禁用无自比较 */
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            /* eslint-enable no-self-compare */
            /* eslint启用无自比较 */
            if (process.env.NODE_ENV !== 'production' && customSetter) {
                customSetter()
            }
            // #7981: for accessor properties without setter
            // 对于没有setter的访问器属性
            if (getter && setter) return
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            childOb = !shallow && observe(newVal)
            dep.notify()
        }
    })
 }
  1. 数据发生变化以后能通知到对应的观察者来执行相关的逻辑

当对值进行修改时,会触发setter方法,从而调用dep.notify()

定义一个Dep进行依赖收集

Dep 的核心是 notify 通过自定义数组subs进行控制 主要实现 addSub removeSub 循环遍历subs 去通知watch 更新

// src/core/observe/dep.js
let uid;
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 * dep是一个可观察的对象,可以有多个指令订阅它。
 */
export function class Dep {
    static target: ?Watcher;
    id: number;
    subs: Array<Watcher>;

    constructor () {
        this.id = uid++
        this.subs = []
    }

    addSub (sub: Watcher) {
        this.subs.push(sub)
    }

    removeSub (sub: Watcher){
        remove(this.subs, sub)
    }

    depend () {
        if (Dep.target) {
            Dep.target.addDep(this)
        }
    }

    notify () {
        // stabilize the subscriber list first
        // 局部浅拷贝这个数组
        const subs = this.subs.slice()
        if (process.env.NODE_ENV !== 'production' && !config.async) {
            // subs aren't sorted in scheduler if not running async
            // we need to sort them now to make sure they fire in correct
            // order
            // 如果未运行异步,则不会在调度程序中对sub进行排序
            // 我们现在需要对它们进行分类,以确保它们正确按顺序运行
            subs.sort((a, b) => a.id - b.id)
        }
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
        }
    }
}

看看watch的定义

// src/core/observe/watcher.js
let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 * 观察者解析表达式,收集依赖项,
 * 并在表达式值更改时激发回调。
 * 这用于$watch()api和指令。
 */
export default class Watcher {
    vm: Component;
    expression: string;
    cb: Function;
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean;
    sync: boolean;
    dirty: boolean;
    active: boolean;
    deps: Array<Dep>;
    newDeps: Array<Dep>;
    depIds: SimpleSet;
    newDepIds: SimpleSet;
    before: ?Function;
    getter: Function;
    value: any;

    constructor(
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
    ) {
        this.vm = vm
        if (isRenderWatcher) {
            vm._watcher = this
        }
        vm._watcher.push(this)
        // options
        if (options) {
            this.deep = !!options.deep
            this.user = !!options.user
            this.lazy = !!options.lazy
            this.sync = !!options.sync
            this.before = options.before
        } else {
            this.deep = this.user = this.lazy = this.sync = false
        }
        this.cb = cb
        this.id = ++uid // uid for batching
        this.active = true
        this.dirty = this.lazy // for lazy watchers
        this.deps = []
        // ...
        this.value = this.lazy
            ? undefined
            : this.get()

    }

    update () {
        if (this.lazy) {
            this.dirty = true
        } else if (this.sync) {
            this.run()
        } else {
            queueWatcher(this)
        }
    }

    /**
     * Scheduler job interface.
     * Will be called by the scheduler.
     * 调度任务接口。
     * 将会被调度程序调用
     */
    run () {
        if (this.active) {
            const value = this.get()
            if(
            value !== this.value ||
            // Deep watchers and watchers on Object/Arrays 
            //深度观察者和对象/数组上的观察者
            isObject(value) ||
            this.deep
            ) {
                // set new value
                //设置新值
                const oldValue = this.value
                this.value = value
                if (this.user) {
                    try {
                        // 调用回调函数,传新值和旧值
                        this.cb.call(this.vm, value, oldValue)
                    } catch(e) {
                        handleError(e, this.vm, `callback for watcher "${this.expression}"`)
                    }
                } else {
                     // 调用回调函数,传新值和旧值
                    this.cb.call(this.vm, value, oldValue)
                }
            }
        } 
    }

    /**
     * Evaluate the getter, and re-collect dependencies.
     * 评估getter,并重新收集依赖项。
     */
    get() {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
            value = this.getter.call(vm, vm)
        } catch (e) {
            if (this.user) {
                handleError(e, vm, `getter for watcher "${this.expression}"`)
            } else {
                throw e
        } finally {
            // "touch" every property so they are all tracked as
            // dependencies for deep watching
            // “触摸”每一处财产,以便它们都能被跟踪
            // 深度观察的依赖性
            // 对dep数组进行删除操作
            if (this.deep) {
                traverse(value)
            }
            popTarget()
            this.cleanupDeps()
        }
        return value
    }
}

vue3响应式实现

vue3响应式 reactive中的实现是由proxy和effect组合 主要是应用了ES6的Proxy代替了ES5的Object.defineProperty来实现

// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
    // if trying to observe a readonly proxy, return the readonly version.
    // 如果目标对象是一个只读的响应数据,则直接返回目标对象
    if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
        return target
    }
    // 否则调用 createReactiveObject 创建 observe
    return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap
    )
}

// Target 目标对象
// isReaonly 是否只读
// baseHandlers 基本类型的handlers
// collectionHandlers 主要针对(set、map、weakSet、weakMap)的handlers
// proxyMap弱引用map
function createReactiveObject(
    target: Target,
    isReadonly: boolean,
    baseHandlers: ProxyHandler<any>,
    collectionHandlers: ProxyHandler<any>,
    proxyMap: WeakMap<Target, any>
) {
    // 如果不是对象
    if (!isObject(target)) {
        // 在开发模式抛出警告,生产环境直接返回目标对象
        if (__DEV__) {
            console.warn(`value cannot be made reactive: ${String(target)}`)
        }
        return target
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object
    // 如果目标对象已经是个proxy直接返回
    // 异常:对反应对象调用readonly()
    if (
        target[ReactiveFlags.RAN] &&
        !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
    ) {
        return target
    }
    // target already has corresponding Proxy
    // 目标已具有相应的代理
 
    const existingProxy = proxyMap.get(target)
    if (existingProxy) {
        return existingProxy
    }
    // only a whitelist of value types can be observed
     // 检查目标对象是否能被观察,不能直接返回
    const target = getTargetType(target)
    if (targetType === TargetType.INVALID) {
        return target
    } 
    // 使用Proxy 创建observe
    const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
    )
    // 存储当前目标对象的proxy 可以防止反复创建
    proxyMap.set(target, proxy)
    return proxy
}

BaseHandlers

// packages/reactivity/src/baseHandlers.ts
// 可变处理
export const mutableHandlers: ProxyHandler<object> = {
  // 用于拦截对象的读取属性操作
  get,
  // 用于拦截对象的设置属性操作
  set,
   // 用于拦截对象的删除属性操作
  deleteProperty,
  // 检查一个对象是否拥有某个属性
  has,
  // 针对 getOwnPropertyNames, getOwnPropertySymbols, keys 的代理方法
  ownKeys
}

const get = /*#__PURE__*/ createGetter()

const set = /*#__PURE__*/ createSetter()

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}

function ownKeys(target: object): (string | symbol)[] {
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}

/**
  * @description:
  * @param {target} 目标对象
  * @param {key} 需要获取的值的键值
  * @param {receiver} 如果遇到 setter,receiver则为setter调用时的this值
  */
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    //  ReactiveFlags 是在reactive中声明的枚举值,如果key是枚举值则直接返回对应的布尔值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      // 如果key是raw 则直接返回目标对象
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
            ? shallowReactiveMap
            : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    // 如果目标对象是数组并且 key 属于三个方法之一 ['includes', 'indexOf', 'lastIndexOf'],即触发了这三个操作之一
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    // 如果 key 是 symbol 内置方法,或者访问的是原型对象,直接返回结果,不收集依赖
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 如果是浅观察并且不为只读则调用 track Get, 并返回结果
    if (shallow) {
      return res
    }

    // 如果get的结果是ref
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      // 目标对象为数组并且不为只读调用 track Get, 并返回结果
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 由于 proxy 只能代理一层,所以 target[key] 的值如果是对象,就继续对其进行代理
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

ref 接收参数并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性.value 由于reacive内部采用Proxy实现,proxy只接受对象作为入参。这才有了ref来解决值类型的数据响应,如果传入ref是一个对象,内部也会调用reactive方法进行深层响应转换

// packages/reactivity/src/reactive.ts
export function reactive(T extends object)(taget: T): UnwrapNestedRefs<T>
// packages/reactivity/src/ref.ts
export function ref(value?: unknown) {
    return createRef(value)
}

function createRef(rawValue: unknown, shallow = false) {
    if (isRef(rawValue)) {
        return rawValue
    }
    return RefImpl(rawValue, shallow)
}

class RefImpl<T> {
    private _value: T

    public readonly __v_isRef = true

    constructor(private _rawValue: T, public readonly _shallow = false) {
        // 如果是浅观察直接观察,不是则将rawValue转换成reactive
        this._value = _shallow ? _rawValue : convert(_rawValue)
    }

    get value() {
        // 依赖收集
        track(toRaw(this), TrackOpTypes.GET, 'value')
        return this._value
    }

    set value(newVal) {
        if(hasChanged(toRaw(newVal), this._rawValue)) {
            this._rawValue = newValue
            this._value = this.shallow ? newVal : convert(newVal)
            // 触发依赖
            trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
        }
    }
}

// 如果是对象 则调用reactive进行observe 否则返回当前val
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

effect 作为 reactive 的核心,主要负责收集依赖,更新依赖 effect 接收俩个参数

  • fn 回调函数
  • options 参数
// packages/reactivity/src/effect.ts
export interface ReactiveEffectOptions {
  // 是否延迟触发 effect
  lazy?: boolean
  // 调度函数
  scheduler?: (job: ReactiveEffect) => void
  // 追踪时触发
  onTrack?: (event: DebuggerEvent) => void
  // 触发回调时触发
  onTrigger?: (event: DebuggerEvent) => void
  // 停止监听时触发
  onStop?: () => void
  /**
   * Indicates whether the job is allowed to recursively trigger itself when
   * managed by the scheduler.
   *
   * By default, a job cannot trigger itself because some built-in method calls,
   * e.g. Array.prototype.push actually performs reads as well (#1740) which
   * can lead to confusing infinite loops.
   * The allowed cases are component update functions and watch callbacks.
   * Component update functions may update child component props, which in turn
   * trigger flush: "pre" watch callbacks that mutates state that the parent
   * relies on (#1801). Watch callbacks doesn't track its dependencies so if it
   * triggers itself again, it's likely intentional and it is the user's
   * responsibility to perform recursive state mutation that eventually
   * stabilizes (#1727).
   */
  allowRecurse?: boolean
}

export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // 如果已经是`effect` 先重置为原始对象
  if (isEffect(fn)) {
    fn = fn.raw
  }

  // 创建effect
  const effect = createReactiveEffect(fn, options)

  // 如果没有传入 lazy 则直接执行一次effect
  if (!options.lazy) {
    effect()
  }
  return effect
}

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    // 没有激活,说明调用了effect stop函数
    if (!effect.active) {
      return fn()
    }
    // 判断effectStack中有没有effect,如果在则不处理
    if (!effectStack.includes(effect)) {
      // 清除effect依赖
      cleanup(effect)
      try {
        // 开始重新收集依赖
        enableTracking()
        // 压入Stack
        effectStack.push(effect)
        // 将activeEffect当前effect
        activeEffect = effect
        return fn()
      } finally {
        // 完成后将effect弹出
        effectStack.pop()
        // 重置依赖
        resetTracking()
        // 重置activeEffect
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  // 自增id,effect唯一标识
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  // 是否是effect
  effect._isEffect = true
  // 是否激活
  effect.active = true
  // 挂载原始对象
  effect.raw = fn
  // 当前effect的dep数组
  effect.deps = []
  // 传入的options,在effect有解释的那个字段
  effect.options = options
  return effect
}

// 每次effect运行都会重新收集依赖,deps是effect的依赖数组,需要全部清空
function cleanup(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

/**
 * @description:
 * @param {target} 目标对象
 * @param {type} 收集的类型
 * @param {key} 触发 track 的 object 的 key
 */
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // activeEffect为空代表没有依赖,直接return
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  // targetMap 依赖管理中心,用于收集依赖和触发依赖
  // 检查targetMap中有没有当前target
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    // 没有则新建一个
    depsMap.set(key, (dep = new Set()))
  }
  // deps来收集依赖函数,当监听的key值发生变化时,触发dep中的依赖函数
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
    // 开发环境会触发onTrack,仅用于调试
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

// get、has、iterate 三种类型的读取对象会触发track
export const enum TrackOpTypes {
    GET = 'get',
    HAS = 'has',
    ITERATE = 'iterate'
}

//触发依赖(触发更新后执行监听函数之前触发)
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  // 依赖管理中没有,代表没有收集过依赖,直接返回
  if (!depsMap) {
    // never been tracked
    return
  }

  // 对依赖进行分类
  // effects 代表依赖
  // Set结构,避免重复收集
  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        // 避免重复收集
        if (effect !== activeEffect || effect.allowRecurse) {
          // 属性依赖
          effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      add(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    // 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  // 触发依赖函数
  effects.forEach(run)
}

总结

vue2.x和vue3的响应式对比主要是

  1. vue3采用了proxy代替了vue2.x: Object.defineProperty的写法,因为proxy是针对对象的代理,所以增加了ref对值的代理
  2. 在依赖收集上vue3采用了effect,vue2.x使用了dep写法
  3. 在通知上面vue2.x使用this.cb.call(this.vm, value, oldValue)直接调用传递函数,vue3循环调用effect执行回调函数