vue3响应式原理

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

一、首先了解Object.defineProperty、Proxy、Reflect

1. Object.defineProperty()

2. proxy 

3. 解决了defineproperty的那些问题?

4. Reflect 


一、首先了解Object.defineProperty、Proxy、Reflect

1. Object.defineProperty()

这个函数有三个参数分别为 obj、prop、descriptor。

        obj为我们要监听的对象 。 prop一个字符串或 Symbol,指定了要定义或修改的属性键。

        descriptor要定义或修改的属性的描述符。

(1)写一个简单示例

 

(2) 监听多个属性:

vue3响应式原理_第1张图片

 (3)深度监听对象属性

 (4)监听数组

let hobby = ['抽烟','喝酒','烫头']
    let person = {
        name:'Barry',
        age:22
    }
 
 
// 把 hobby 作为 person 属性监听
Object.defineProperty(person,'hobby',{
    get(){
        console.log('tigger get');
        return hobby
    },
    set(newVal){
        console.log('tigger set',newVal);
        hobby = newVal
    }
})
 
console.log(person.hobby);
person.hobby = ['看书','游泳','听歌']

// 不能被监听
person.hobby.push('游泳')

数组的push、unshift、splice、sort、reverse等方法,set方法是监听不到的。

vue2.x通过 劫持这些方法实现响应式

具体实现:

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { TriggerOpTypes } from '../../v3'
import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    if (__DEV__) {
      ob.dep.notify({
        type: TriggerOpTypes.ARRAY_MUTATION,
        target: this,
        key: method
      })
    } else {
      ob.dep.notify()
    }
    return result
  })
})

首先调用def方法,内部实现了方法的劫持。

export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

处理方法的逻辑都在 mutator()  函数中。其中如果是push,unshift、splice 就会触发observeArray方法。

observeArray(value: any[]) {
    for (let i = 0, l = value.length; i < l; i++) {
      observe(value[i], false, this.mock)
    }
  }

最后都会执行 notify,通知变更。notify 函数中通过onTrigger派发依赖,update方法渲染dom;就不具体看了,大体流程就这样。

 notify(info?: DebuggerEventExtraInfo) {
    // stabilize the subscriber list first
    const subs = this.subs.filter(s => s) as DepTarget[]
    if (__DEV__ && !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
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      const sub = subs[i]
      if (__DEV__ && info) {
        sub.onTrigger &&
          sub.onTrigger({
            effect: subs[i],
            ...info
          })
      }
      sub.update()
    }
  }

2. proxy 

概念:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

new Proxy() 包含两个参数,分别为target和handler。代表要使用Proxy包装的对象,定义一些行为。

简单实例

vue3响应式原理_第2张图片

3. 解决了defineproperty的那些问题?

  •  Object.defineProperty一次只能监听属性,需要遍历对所有的属性监听。
  • 在遇到一个对象属性时,需要递归监听。
  • 对于对象的新增属性,需要手动监听。
  • 对于数组通过push、unshift方法增加属性,无法监听。 

4. Reflect 

Reflect是ES6的高级Api,是一个内置对象,他提供了拦截js的方法。Reflect不是函数对象,所以不能被构造。

Reflect的所有方法都是静态的

vue3响应式原理_第3张图片

 为什么要使用Reflect?
(1)触发代理对象的劫持时,保证正确的this上下文指向。其中receiver的作用就是修改this指向


(2)代码的健壮性

(3)操作对象出错时返回false,避免大量try cache

二、reactive 

 vue的版本是"version": "3.2.45", 知道大概的实现思路即可,深入理解还得慢慢来,别给自己太大压力。

reactive的实现函数是createReactiveObject 。


// 获取数据类型
function targetTypeMap(rawType) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return 1 /* TargetType.COMMON */;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return 2 /* TargetType.COLLECTION */;
        default:
            return 0 /* TargetType.INVALID */;
    }
}


// 根据target 生成proxy实例
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler,
  collectionHandlers: ProxyHandler,
  proxyMap: WeakMap
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // 目标值已经是proxy对象,就直接返回
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 目标值已经有对应的proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  //只能观察到特定的值类型
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === 2 ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

 createReactiveObject简要分析:(1)判断reactive传值是否为引用类型,如果不是引用类型则直接返回值,不具备响应式。(2)判断传值是狗已经是proxy对象,是就直接返回。(3)判断该proxy对象是否存在,存在直接返回。(4)判断是否是可跳过或者非扩展对象,是就直接返回(5)最后生成proxy对象,并对目标值进行存储,避免重复代理。

注意: 

 const proxy = new Proxy(

    target,

    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers

  )

劫持对象的主要方法分为两种:第一种代理的值为Map、Set、WeakMap、WeakSet对象,代理以上对象内部只监听get方法,第二种代理的值为Array、Object,内部监听get,set,

deleteProperty、has、ownKeys。其中get、has、ownKeys都是执行track函数进行依赖收集,set、deleteProperty执行trigger函数进行派发依赖

依赖收集和依赖派发是实现在effect.ts中,大家可以自己查看。

这里就不说collectionHandlers、baseHandlers,就是通过这个函数定义了监听对象的一些方法,proxy的内部原理。当修改或者读取时触发相应的方法。

然后就是get、has、ownKeys会进行依赖收集。set、deleteProperty方法会进行派发依赖。

在分析依赖收集和派发时,我们先了解一下effect.ts中的ReactiveEffect类,effect()


// 这个类的作用是创建一个响应式副作用函数,这个函数会在依赖数据发生变化时执行
export class ReactiveEffect {
  // 是否处于活动状态
  active = true
 // 响应式依赖项集合
  deps: Dep[] = []
   // 父级作用域
  parent: ReactiveEffect | undefined = undefined
  computed?: ComputedRefImpl
  allowRecurse?: boolean
  private deferStop?: 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() {
     // 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
    
  }

  stop() {
   
  }
}

知道里边有两个重要的方法:stop和run。

run:就是执行副作用函数

function run() {
    // 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
    if (!this.active) {
        return this.fn();
    }
    
    // 寻找当前 ReactiveEffect 对象的最顶层的父级作用域
    let parent = activeEffect;
    let lastShouldTrack = shouldTrack;
    while (parent) {
        if (parent === this) {
            return;
        }
        parent = parent.parent;
    }
    
    try {
        // 记录父级作用域为当前活动的 ReactiveEffect 对象
        this.parent = activeEffect;
        
        // 将当前活动的 ReactiveEffect 对象设置为 “自己”
        activeEffect = this;
        
        // 将 shouldTrack 设置为 true (表示是否需要收集依赖)
        shouldTrack = true;
        
        // effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1
        trackOpBit = 1 << ++effectTrackDepth;
        
        // 这里是用于控制 "effect调用栈的深度" 在一个阈值之内
        if (effectTrackDepth <= maxMarkerBits) {
            // 初始依赖追踪标记
            initDepMarkers(this);
        }
        else {
            // 清除所有的依赖追踪标记
            cleanupEffect(this);
        }
        
        // 执行副作用函数,并返回执行结果
        return this.fn();
    }
    finally {
        // 如果 effect调用栈的深度 没有超过阈值
        if (effectTrackDepth <= maxMarkerBits) {
            // 确定最终的依赖追踪标记
            finalizeDepMarkers(this);
        }
        
        // 执行完毕会将 effectTrackDepth 减 1
        trackOpBit = 1 << --effectTrackDepth;
        
        // 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域”
        activeEffect = this.parent;
        
        // 将 shouldTrack 设置为上一个值
        shouldTrack = lastShouldTrack;
        
        // 将父级作用域设置为 undefined
        this.parent = undefined;
        
        // 延时停止,这个标志是在 stop 方法中设置的
        if (this.deferStop) {
            this.stop();
        }
    }
}

stop

function stop() {
    // 如果当前 活动的 ReactiveEffect 对象是 “自己”
    // 延迟停止,需要执行完当前的副作用函数之后再停止
    if (activeEffect === this) {
        // 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法
        this.deferStop = true;
    }
    
    // 如果当前 ReactiveEffect 对象处于活动状态
    else if (this.active) {
        // 清除所有的依赖追踪标记
        cleanupEffect(this);
        
        // 如果有 onStop 回调函数,就执行
        if (this.onStop) {
            this.onStop();
        }
        
        // 将 active 设置为 false
        this.active = false;
    }
}

effect()

effect()函数会真正的执行run方法(执行副作用函数)。

export function effect(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
    // 这里得到ReactiveEffect类 的实例 
  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

这里再看依赖收集和派发就会大概清楚执行顺序。

/ 收集依赖
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
 // targetMap 为全局WeakMap对象,在依赖派发的时候使用
    let depsMap = targetMap.get(target)
    // 不存在target
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    trackEffects(dep, eventInfo)
  }
}

export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  

  if (shouldTrack) {
    dep.add(activeEffect!)
    
  }
}

以上是依赖收集的过程,定义了一个targetMap对象(WeakMap)键值target,值是一个Map,这个Map的键key(即对象的属性值),值是Set集合。使用Set避免重复的副作用函数。

收集完依赖以后如下结构:

vue3响应式原理_第4张图片

 

/**
 * 触发依赖
 * @param target 指向的对象
 * @param type 操作类型
 * @param key 指向对象的 key
 * @param newValue 新值
 * @param oldValue 旧值
 * @param oldTarget 旧的 target
 */

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map | Set
) {
  // 获取targetMap中的depsMap
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  // 创建一个数组,用来存放需要执行的ReactiveEffect对象  
  let deps: (Dep | undefined)[] = []
    // 如果 type 为 clear,就会将 depsMap 中的所有 ReactiveEffect 对象都添加到 deps 中

  if (type === ‘clear’) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
 // 如果 key 为 length ,并且 target 是一个数组
  } else if (key === 'length' && isArray(target)) {
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= newLength) {
        deps.push(dep)
      }
    })
  } else {
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

     // 执行 add、delete、set 操作时,就会触发的依赖变更

    switch (type) {
      case 'add':
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            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 'delete':
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case '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[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // 如果dep不是数组,就会将dep转成数组,因为dep可能时Set对象
  const effects = isArray(dep) ? dep : [...dep]
   // 遍历所有的依赖,
  for (const effect of effects) {
    // 执行computed依赖
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
// 执行其他依赖
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    // 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行

    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    // 如果 effect 是一个调度器,就会执行 scheduler

    if (effect.scheduler) {
      effect.scheduler()
    } else {
      // 否则直接执行 effect.run()
      effect.run()
    }
  }
}

tigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。

总结 

reactive(obj) vue3内部先执行(在reactive.ts中)createReactiveObject() 参数 target(要代理的对象)、isReadonly(是否只读)、baseHanlers(拦截Object和Array类型数据的函数集合)、collectionHandlers(拦截Map,Set,WeakMap,WeakSet的函数集合)、proxyMap(WeakMap集合用来记录代理的对象,避免重复代理)。此函数主要用来创建响应式对象

然后我们需要关注proxy的第二个参数,也就是baseHandlerscollectionHandlers 分别在对应的文件里。在文件中我们可以看到mutableHandlermutableCollectionHandlers 作为Proxy的第二个参数。用来监听被代理的对象。再次对象的方法中get、has 、ownKeys方法执行会收集依赖,set和deleteproperty方法会触发依赖。重点关注set函数(createSetter()返回值)和get函数(createGetter())

然后关注track()trigger(),分别实现收集和触发依赖。都在effect,ts文件中。

track---->trackEffects.。         trigger--->triggerEffects--->triggerEffect(执行副作用)

然后看 effect() 也在effect.ts文件中,这里会const _effect = new ReactiveEffect(fn);_effect.run()

然后在这个文件中找到ReactiveEffect构造函数。这就是大概的流程。

收集的依赖就是ReactiveEffect实例,解释初始化组件时,会执行ReactiveEffect构造函数,执行run方法,会将this赋值activeEffect,这里的this指向调用run的对象,这就是ReactiveEffect的实例。  收集依赖时:dep.add(activeEffect!)。

你可能感兴趣的:(机器学习)