前语

在ES6中,"reactive"一般指的是创立具有呼应式特性的方针,使得这些方针的特点能够主动地呼应数据改变而更新视图。Vue.js的呼应式体系就是根据这个概念构建的。在ES6中,咱们能够经过运用Proxy方针来手动创立具有相似呼应式特性的方针。

下面咱们将逐渐创立一个简略的reactive函数,来手动完成一个相似Vue.js中呼应式方针的功用(只完成了set及get功用):

1、 reactive.js:

import { mutableHandlers } from "./baseHandlers.js";
export const reactiveMap = new WeakMap(); // 创立一个WeakMap用来存储现已署理过的方针,WeakMap对内存的回收愈加友爱
export function reactive(target) {
  // 将target变成呼应式方针
  return createReactiveObject(target, reactiveMap, mutableHandlers); // 创立呼应式方针
}
export function createReactiveObject(target, proxyMap, proxyHandlers) {
  // 创立呼应式的函数,参数别离是方针方针,存储署理方针的map,署理方针的处理函数
  // 判别target是不是引证类型
  if (typeof target !== "object" || target === null) {
    // 不是引证类型直接回来
    return target;
  }
  // 该方针是否现已被署理过(现已是呼应式方针)
  const existingProxy = proxyMap.get(target); // 从reactiveMap中获取target
  if (existingProxy) {
    return existingProxy; // 假如现已署理过了,直接回来
  }
  // 履行署理操作(将target变成呼应式方针)
  const proxy = new Proxy(target, proxyHandlers); //第二个参数:当target被读取值,设置值,判别值等等操作时,会触发的函数
  // 往 reactiveMap中增加 proxy,把现已署理过的方针存储起来
  proxyMap.set(target, proxy);
  return proxy; // 回来署理方针
}

在上述代码中,主要是完成reactive函数的主要功用之一,将引证类型转换为呼应式方针,下面让我逐渐解说这段代码的功用:

  1. 创立 reactiveMap:这行代码创立了一个 WeakMap 方针,用于存储现已署理过的方针。
    WeakMap 中的键是弱引证的,这意味着假如键方针没有其他引证,因而对内存的回收愈加友爱,当被署理的方针被废物回收时,相应的条目也会被主动移除。
  2. reactive 函数:这个函数承受一个一般方针作为参数,并将其转换为具有呼应式特性的方针。它调用了 createReactiveObject 函数来创立呼应式方针,并传入了方针方针、reactiveMapmutableHandlers 作为参数,参数别离是方针方针,存储署理方针的map,署理方针的处理函数。
  3. createReactiveObject 函数:这个函数用于创立呼应式方针。

    • 首要它判别传入的方针方针是否是引证类型(即方针或数组),假如不是,则直接回来方针方针自身。
    • 然后它检查方针方针是否现已被署理过,假如是,则直接回来之前创立的署理方针。假如方针方针尚未被署理过,则运用 Proxy 方针创立一个新的署理方针,并将其存储到 reactiveMap 中。
    • 最终回来创立的署理方针。

2、baseHandlers.js

import { track, trigger } from "./effect.js";
const get = createGetter(); // 创立一个get函数
const set = createSetter(); // 创立一个set函数
function createGetter() {
  return function get(target, key, receiver) {
    // console.log('target被读取值');
    const res = Reflect.get(target, key, receiver); // 获取源方针中的键值
    // 这个特点终究还有哪些地方用到了,(副作用函数的搜集,computed,watch...)
    track(target, key); // 依靠搜集
    return res;
  }
}
function createSetter() {
  return function set(target, key, value, receiver) {
    // console.log('target被设置值', key, value);
    const res = Reflect.set(target, key, value, receiver); // 设置源方针中的键值 === target[key] = value
    trigger(target, key); // 触发副作用函数
    // 需求记录下来此刻是哪一个key的值变更了,再去告诉其他依靠该值的函数收效,更新浏览器的视图(呼应式)
    // 触发被修正的特点身上的副函数 依靠搜集(被修正的key在哪些地方被运用了)发布订阅
    return res;
  }
}
export const mutableHandlers = {
  // get: function (target, key, receiver) { // target 被署理的源方针,key是源方针中的键,receiver是署理后方针
  //   console.log('target被读取值');
  //   const res = Reflect.get(target, key, receiver); // 获取源方针中的键值
  //   return res;
  // },
  get,
  set,
  // set: function (target, key, value, receiver) {
  //   console.log('target被设置值', key, value);
  //   const res = Reflect.set(target, key, value, receiver); // 设置源方针中的键值 === target[key] = value
  //   return res;
  //   // 更新浏览器的视图(呼应式)
  // },
}

以上代码完成了对署理方针的操作处理,包含了对特点的读取和设置。被署理方针中的恣意特点的值产生修正,都应该将用到了这个特点的各个函数从头履行一遍,那么在履行之前就需求先为每一个特点都做好副作用函数的搜集,也称为依靠搜集.这也是完成 Vue.js 呼应式体系的关键之一,它保证了在特点改变时能够正确地触发浏览器视图更新。下面让我来逐渐解说这段代码的功用:

  1. createGetter 函数

    • 这个函数回来一个用于处理特点读取的函数。在这个函数内部,首要调用了 Reflect.get() 办法获取方针方针中指定键的值,并将成果存储在变量 res 中。
    • 接着调用了外部导入的 track 函数,用于搜集当时特点的依靠联系。这意味着该特点被拜访时,需求盯梢它的依靠,以便在特点改变时触发相应的副作用函数。
    • 最终回来特点的值 res
  2. createSetter 函数

    • 这个函数回来一个用于处理特点设置的函数。在这个函数内部,首要调用了 Reflect.set() 办法将指定键的值设置为指定的值,并将成果存储在变量 res 中。
    • 接着调用了外部导入的 trigger 函数,用于触发与当时特点相关联的副作用函数。这意味着当特点被修正时,需求告诉一切依靠该特点的函数履行相应的副作用操作。
    • 最终回来设置操作的成果 res
  3. mutableHandlers 方针

    • 这个方针包含了对可变方针的操作处理函数,其间包含了 getset 操作的具体完成。在这里,getset 的处理函数别离由 createGettercreateSetter 函数回来。
    • mutableHandlers 方针被导出,能够被其他模块引证和运用。

3、effect.js

const targetMap = new WeakMap();
let activeEffect = null; //得是一个副作用函数
export function effect(fn,options={}) { //watch,computed的核心逻辑
  const effectFn = () => {
    try {
      activeEffect = effectFn
      return fn()
    } finally {
      activeEffect = null
    }
  }
  if(!options.lazy){
    effectFn()
  }
  return effectFn
}
// 为某个特点增加effect
export function track(target,key) {
  // targetMap = { //存成这样的结构
  //   target: {
  //     key: [effect1,effect2,...]
  //   }
  // }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) { //改特点未增加过effect
    dep = new Set()
  }
  if (!dep.has(activeEffect) && activeEffect) {
    // 存入一个effect函数
    dep.add(activeEffect)
  }
  depsMap.set(key, dep)
}
// 触发特点effect
export function trigger(target,key) {
  const depsMap = targetMap.get(target)
  if(!depsMap){ //当时方针中一切的key都没有副作用函数,从来都没有被运用过
    return
  }
  const deps = depsMap.get(key)
  if (!deps) { //这个特点没有依靠
    return
  }
  deps.forEach(effectFn => {
    effectFn() //将该特点上的一切副作用函数悉数触发
  });
}

以上代码完成了一个状况管理的功用,用于完成对方针特点的依靠盯梢和副作用函数的履行,并管理方针特点的依靠联系和履行副作用函数。这样,在特点值产生改变时,就能主动履行相应的副作用函数,然后完成数据的呼应式处理。下面我将详细的介绍一下以上代码的功用:

  1. effect 函数

    • 这个函数用于创立副作用函数。副作用函数是一个函数,它会在呼应式数据产生改变时被履行。
    • effect 函数承受两个参数:fn 是一个函数,代表需求履行的副作用函数;options 是一个包含选项的方针,默以为空方针。其间,lazy 是一个布尔值选项,表示是否延迟履行副作用函数,默以为 false
    • effect 函数内部,首要创立了一个名为 effectFn 的函数,它是一个封装了传入的副作用函数 fn 的函数。在 effectFn 函数内部,经过 try...finally 结构保证了 activeEffect 变量的正确设置和清除。然后依据 options.lazy 的值决定是否立即履行 effectFn 函数,并回来 effectFn 函数。
  2. track 函数

    • 这个函数用于为某个特点增加副作用函数。当某个特点被拜访时,需求调用 track 函数来搜集对应的依靠联系,以便在特点值产生改变时履行相应的副作用函数。
    • track 函数承受两个参数:target 是方针方针,key 是方针方针的特点名。
    • track 函数内部,首要依据 targettargetMap 中获取对应的依靠映射联系 depsMap,假如不存在,则创立一个新的空映射联系并存储到 targetMap 中。然后依据 keydepsMap 中获取对应的依靠调集 dep,假如不存在,则创立一个新的空调集。接着判别当时副作用函数 activeEffect 是否现已存在于 dep 中,假如不存在且 activeEffect 存在,则将其增加到 dep 中。最终将更新后的 dep 存储回 depsMap 中。
  3. trigger 函数

    • 这个函数用于触发特点的副作用函数。当某个特点的值产生改变时,需求调用 trigger 函数来履行一切依靠于该特点的副作用函数。
    • trigger 函数承受两个参数:target 是方针方针,key 是方针方针的特点名。
    • trigger 函数内部,首要依据 targettargetMap 中获取对应的依靠映射联系 depsMap,假如不存在,则说明方针方针中一切的特点都没有副作用函数,直接回来。然后依据 keydepsMap 中获取对应的依靠调集 deps,假如不存在,则说明该特点没有任何依靠,直接回来。接着遍历 deps 调集,顺次履行其间的副作用函数。

值得注意的是,对于effect函数,实际上大部分完成监听作用的函数都会运用到这种相似的函数,就如watchcomputed函数。因为它用于创立副作用函数,而 watchcomputed 又都是根据副作用函数完成的,它们都依靠于副作用函数来完成对数据的调查和计算。

结语

就这样,咱们别离经过创立reactive.jsbaseHandlers.jseffect.js三个js文件完成了一个简略的reactive函数。

总结

在ES6中,咱们能够经过运用Proxy方针和一些基本的JavaScript技巧手动创立具有呼应式特性的数据处理体系。本文介绍了怎么运用Proxy方针来完成一个简略的reactive函数,以及怎么合作运用WeakMapReflect和副作用函数来构建一个简易的呼应式体系。经过对依靠联系的追寻和副作用函数的履行,咱们能够完成数据的主动更新和视图的同步更新,这也是现代JavaScript结构中常见的核心功用之一。

补充:什么是副作用函数

副作用函数指的是在函数履行过程中,除了回来一个值之外,还对函数外部的状况产生了影响,或许履行了与函数自身的意图无关的操作。
在呼应式体系中,副作用函数一般用于创立调查者模式或完成数据的主动更新。例如,当一个数据产生改变时,会触发与之相关联的副作用函数,然后履行一些预订的操作,比如更新UI界面或触发其他相关的事情。Vue.js中的watchcomputed就是根据副作用函数完成的,它们能够监视数据改变并履行相应的操作。

最终,假如您也和我一样,在准备春招。欢迎加微信shunwuyu,这里有几十位一心去大厂的友友能够相互鼓舞,共享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!