Proxy-Reflect vue2-vue3响应式原理

190 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

前言

本文主要根据讲解Proxy-Reflect,来引出vue2-vue3响应式原理。该篇主要是自己得学习总结,供大家参考学习,如有写的不准确的地方欢迎大家指出,相互学习,共同进步!

一. Proxy

1.什么是proxy?

ES6中,新增了一个Proxy类,翻译过来即代理。顾名思义是用于帮助我们创建一个代理的。

  • 如果希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象),之后对该对象的所有操作,都通过Proxy对象来完成;
  • Proxy对象可以监听我们想要对原对象进行哪些操作;

const p = new Proxy(target, handler)

const obj = {
  name: "why",
  age: 18
}
const objProxy = new Proxy(obj, {})

如果需要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap) 而我们实际过程中用的最多得捕获器是get、set、has、deleteProperty,具体用法和其它别的捕获器可点击Proxy查看。

举例:

const obj = {
  name: "amy",
  age: 18
}

const objProxy = new Proxy(obj, {
  // 获取值时的捕获器
  get: function(target, key,receiver) {
    console.log(`监听到对象的${key}属性被访问了`, target, receiver)
    return target[key]
  },

  // 设置值时的捕获器  receiver是创建出来的代理对象
  set: function(target, key, newValue,receiver) {
    console.log(`监听到对象的${key}属性被设置值`, target, receiver)
    target[key] = newValue
  }
})

2.与Object.defineProperty有什么区别?

Object.defineProperty缺陷:

  • 不能监听数组变化
  • Object.defineProperty只能劫持对象的属性,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历
  • Object.defineProperty对新增属性需要手动进行Observe(因为劫持的是对象的属性,所以新增属性时,需要重新遍历对象,再对新增属性再使用defineproperty进行劫持,导致Vue2在给data中的数组对象新增属性时,需要用vm.$set来保证新增属性的响应)

所以我们想监听更加丰富的操作,比如新增属性、删除属性,那么就需要Proxy来进行处理更为合适。

二. Reflect

1.什么是Reflect?

Reflect(反射)也是ES6新增的一个API,它是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。Reflect不是一个函数对象,因此它是不可构造的。Reflect的所有属性和方法都是静态的(就像Math对象)。

将上方案例中对原对象的操作,都修改为Reflect来操作:

const obj = {
  name: "amy",
  age: 18
}

const objProxy = new Proxy(obj, {
  get: function(target, key, receiver) {
    console.log("get---------")
    return Reflect.get(target, key)
  },
  set: function(target, key, newValue, receiver) {
    console.log("set---------")
     return Reflect.set(target, key, newValue)
  }
})

objProxy.name = "kobe"
console.log(objProxy.name)

2.与Object上方法有什么区别?

  • Reflect.getPrototypeOf(target)对应 Object.getPrototypeOf()
  • Reflect.defineProperty(target, propertyKey, attributes)对应Object.defineProperty()

如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面,并且Object作为一个构造函数,这些操作实际上放到它身上并不合适,所以在ES6中新增了Reflect。具体一些方法的区别大家可点击: 比较 Reflect 和 Object 方法

3.Reflect.construct()

Reflect.construct()  方法的行为有点像 new 操作符 构造函数,相当于运行 new target(...args),简单来说就是改变构造函数的原型指向。

Reflect.construct(target, argumentsList[, newTarget])

举例:

function Student(name, age) {
  this.name = name
  this.age = age
}

function Teacher() {

}

// const stu = new Student("why", 18)
// console.log(stu)
// console.log(stu.__proto__ === Student.prototype)

// 执行Student函数中的内容, 但是创建出来对象是Teacher对象
const teacher = Reflect.construct(Student, ["why", 18], Teacher)
console.log(teacher)
console.log(teacher.__proto__ === Teacher.prototype)

三. vue2-vue3 响应式原理

简单提一句什么是响应式:可以自动响应数据变化的代码机制,我们就称之为是响应式的。

首先我们知道需要做到响应式我们需要这些东西:1.响应函数的封装 2.依赖收集类的封装 3.对象依赖管理 4.自动监听对象变化

1.响应函数的封装

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


// 封装一个响应式的函数
function watchFn(fn) {
  activeReactiveFn = fn
  fn() //默认会被执行一次
  activeReactiveFn = null
}

2.依赖收集类的封装

class Depend {
  constructor() {
    this.reactiveFns = new Set() //使用Set来保存依赖函数, 而不是数组[] ,数组会导致重复添加多次依赖函数
  }

 //添加
  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }
  
//通知
  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

3.对象依赖管理

// 封装一个获取depend函数
const targetMap = new WeakMap() //和map的区别自己百度
function getDepend(target, key) {
  // 根据target对象获取map的过程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // 根据key获取depend对象
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

4.自动监听对象变化

vue2:

function reactive(obj) {
  // {name: "why", age: 18}
  // ES6之前, 使用Object.defineProperty
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function() {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function(newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}

vue3:

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()
    }
  })
}