# 响应式

Vue 是采用数据劫持结合观察者(发布者-订阅者)模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter, 在数据变动时发布消息给订阅者(watcher),触发相应的监听回调来更新DOM

# 数据代理

# 数组响应式处理

数组某些特殊的查找情况,会出现查找不到的结果

  • 查找响应式数据本身

    const obj = {}
    const proxy = reactive([obj])
    console.log(proxy.includs(proxy[0])) //false
    
  • 查找原始数据

    const obj = {}
    const proxy = reactive([obj])
    console.log(proxy.includs(obj)) // false
    

getter

  createGetter() {
    // ...
    // 处理数组类型的 getter
    const targetIsArray = shared.isArray(target);
    if (!isReadonly && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {
      // 重写/增强数组的方法: 
      //  - 查找方法:includes、indexOf、lastIndexOf
      //  - 修改原数组长度的方法:push、pop、unshift、shift、splice
      return Reflect.get(arrayInstrumentations, key, receiver);
    }
    // ...
  }
  
  // 数组捕获器,包含 3个查找和5个影响长度的方法
  const arrayInstrumentations = createArrayInstrumentations();
  function createArrayInstrumentations() {
    const instrumentations = {};
    // 1.修改数组查找的方法,处理查找失败的问题
    ['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
      instrumentations[key] = function (...args) {
        // 外部调用查找方法,此时的上下文 this 是代理后的 数组
        // 使用查找的方法时,需要到原始数据中去寻找,所以使用 toRaw 还原响应式数据
        const arr = toRaw(this);
        for (let i = 0, l = this.length; i < l; i++) {
          track(arr, "get", i + '');
        }
        // 执行数组方法的时候,先尝试使用原始的参数(参数可能也是响应式数组)
        const res = arr[key](...args);
        if (res === -1 || res === false) {
          // 如果方法执行无结果,则使用响应式的参数
          return arr[key](...args.map(toRaw));
        }
        else {
          return res;
        }
      };
    });
    // 2.改写5个会修改数组长度的方法
    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
        instrumentations[key] = function (...args) {
          // 这些方法会隐式的改变数组的长度,导致 length 副作用被提前收集
          // 使用 pauseTracking 先暂停依赖收集,待方法执行完毕再恢复收集
          pauseTracking();
          const res = toRaw(this)[key].apply(this, args);
          resetTracking();
          return res;
        };
    });
    return instrumentations;
  }

setter

  function createSetter(shallow = false) {
    return function set(
      target: object,
      key: string | symbol,
      value: unknown,
      receiver: object
    ): boolean {
      // ...
      // 通过 "当前操作的下标是否 < 数组长度" 判断是 "修改 or 新增"类型 
      const hadKey =
        isArray(target) && isIntegerKey(key)
          ? Number(key) < target.length
          : hasOwn(target, key)
    
      // 设置对应值
      const result = Reflect.set(target, key, value, receiver)
    
      if (target === toRaw(receiver)) {
        if (!hadKey) {
          // 目标对象不存在对应的 key,则为新增操作,会改变 length,需要触发 length 副作用
          trigger(target, TriggerOpTypes.ADD, key, value)
        } else if (hasChanged(value, oldValue)) {
          // 目标对象存在对应的值,则为修改操作,不需要触发length 副作用
          trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
      }
    
      // 返回修改结果
      return result
    }
  }

# 依赖收集:track

# 副作用函数

# 数据结构

const depsMap = new WeakMap()
const dep = new Map()
const fn = new Set()
{
  // depsMap
  target1: {
    // dep
    key1: [
      // effect
      fn1,
      fn2
    ],
    key2: [fn3, fn4]
  },
  target2: {
    key: [fn5, fn6]
  }
}

# 派发更新:trigger