【面试题】Vue是如何实现响应式的

【面试题】Vue是如何实现响应式的

前端经典面试题,Vue如何实现的响应式,这里做一下总结。

1. Object.defineProperty

这应该是大多数人的回答,当然,的确是它,但它再往里一点点呢,又是什么?

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建依赖实例,通过闭包的方式让
  // set get 函数使用
  const dep = new Dep()
  // 获得属性对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 获取自定义的 getter 和 setter
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 如果 val 是对象的话递归监听
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 拦截 getter,当取值时会触发该函数
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // 进行依赖收集
      // 初始化时会在初始化渲染 Watcher 时访问到需要双向绑定的对象
      // 从而触发 get 函数
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // 拦截 setter,当赋值时会触发该函数
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // 判断值是否发生变化
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 如果新值是对象的话递归监听
      childOb = !shallow && observe(newVal)
      // 派发更新
      dep.notify()
    }
  })
}

上面这段代码是Object.defineProperty的内部原理,简单些说就是以下几个步骤:

  • vue将data初始化为一个Observer并对对象中的每个值,重写了其中的get、set,data中的每个key,都有一个独立的Dep
  • 在get中,向Dep添加了监听
  • 在mount时,实例了一个Watcher,将Dep的目标指向了当前Watcher
  • 在data值发生变更时,触发set,触发了Dep中的所有监听的更新,来触发Watcher.update
2. 这个方法是存在缺陷的:

如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写以下函数的方式解决了这个问题。

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
3. Vue3 => Proxy

为什么:

  1. proxy可以直接监听数组的变化;
  2. proxy可以监听对象而非属性.它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy直接可以劫持整个对象,并返回一个新对象。

参考内容:
博主【风若能够_dxf】|原文链接:https://juejin.im/post/5d5bbb545188253d6e5ec758
博主【晨曦时梦见兮】|原文链接:https://juejin.im/post/5e23b20f51882510073eb571

你可能感兴趣的:(前端)