JS高级中的响应式|青训营笔记

127 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第4天,关于对象的响应式操作vue3实现以及自己的理解。

1.什么是响应式

比如变量m有一个初始化的值,有一段代码使用了这个值; 那么在m有一个新的值时,这段代码可以自动重新执行

2.对象的响应式操作vue3实现

建议配合下面的代码一起看哦

怎样才能自动重新执行呢?就是把这段代码放到一个函数里面,在该调用的时候(有一个新的值时)调用一下。 怎么才能知道该调用了呢?就需要一个响应式函数watchFn。把使用此变量的代码的函数传进去,然后调用一下。

  1. 封装watchFn函数

先在外面定义一个全局变量activeReactiveFn设为null,用来接收传入的函数(即变化时要执行的代码放到的那个函数),因为这样这个函数就是全局的了,在别的函数内部也能用到。注意在这个响应式函数里面再把全局变量为null

  1. 收集依赖

定义一个类dePend,类里面 的 constructor定义一个空数组,还有添加两个方法,一个是addDepend(用来把函数添加到数组里面),所以有传参,另一个方法是notify(将数组遍历,然后对数组的每一项(即函数)调用一下)

addDepend优化:append。不用传函数,这样get里面也不用关注这个函数。就是先判断全局变量里有没有函数,有了再添加。

  1. 依赖管理

因为可能有很多个对象发生变化,而且每个对象里面还有好多个属性发生变化。这就需要每个属性对应一个depend类。通过weakMap进行管理不同的对象(因为它的key值只能是对象,value就可以设置为对应的Map),然后通过Map进行管理不同的属性(key为对象里变化的属性,value为相应的dePend)

此时就需要封装一个获取dePend的函数getDepend:通过上述weakMap管理的原理来封装。首先创建一个WeakMap为targetMap,获取dePend需要知道目标对象以及key值(target、key),即给getDepend传入这两个参数。然后根据target对象获取map,map = targetMap.get(target),再根据key获取dePend,depend = map.get(key),中间还有过程的(因为第一次是空的,当为空的时候还要设置一下)

  1. Proxy监听对象

使用get和set,可以实现自动执行该调用的函数调用(即notify)。在某个属性的代码快里面,肯定有什么什么点的即用到这个属性,这样就会触发proxy中的get,这个get就有相应的target和key,所以在这里就可以调用getDepend函数得到相应的dePend,然后就可以执行dePend里的addDepend,即完成了把要调用的函数添加到了dePend里。当值发生改变的时候,就会触发set,set里面也有target和key,也能调用getDepend得到相应的dePend,然后就可以执行dePend里的notify进行相应函数调用,即完成了改变了值,用到值的代码进行调用,完成了响应式。

直接使用Proxy传入了只是一个对象,可以封装一个函数reactive传入一个对象,并new一个Proxy并返回。

响应式简略过程

先来到watchFn函数,然后把要调用的函数保存到全局变量里面,然后在执行这个要调用的函数的时候,用到就会进到Proxy里的get里面,拿到相应的dePend,再把要调用的函数加到dePend里,最后在值发生改变的时候就会进入到set里,拿到dePend,调用notify,完成响应式。

// 保存当前需要收集的响应式函数
let activeReactiveFn = null

/**
 * Depend优化:
 *  1> depend方法
 *  2> 使用Set来保存依赖函数, 而不是数组[]
 */

class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }

  // addDepend(reactiveFn) {
  //   this.reactiveFns.add(reactiveFn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

// 封装一个响应式的函数
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根据target对象获取map的过程:
  let map = targetMap.get(target)//这是直接使用,但是肯定要给targetMap设置相应的target和map
  if (!map) {
    map = new Map()
    targetMap.set(target, map)//设置了才有值,第一次执行设置就可
  }

  // 根据key获取depend对象
  let depend = map.get(key)//这是直接使用,但是肯定要给map设置相应的key和dePend
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

function reactive(obj) {
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // 根据target.key获取对应的depend
      const depend = getDepend(target, key)
      // 给depend对象中添加响应函数
      // depend.addDepend(activeReactiveFn)
      depend.depend()
  
      return Reflect.get(target, key, receiver)
    },
    set: function(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      // depend.notify()
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
  name: "why", // depend对象
  age: 18 // depend对象
})

const infoProxy = reactive({
  address: "广州市",
  height: 1.88
})

watchFn(() => {
  console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
  name: "foo"
})

watchFn(() => {
  console.log(foo.name)
})

foo.name = "bar"