Proxy和Reflect

本文深入探讨了Vue2和Vue3中实现响应式原理的方式,从Object.defineProperty到Proxy的使用。通过示例展示了如何使用Proxy监听对象属性的增删改查操作,以及Reflect API在其中的角色。同时,介绍了依赖收集和管理,以及在Vue2和Vue3中如何实现对象的响应式转换。最后,通过WeakMap优化了依赖管理,实现了更高效的响应式系统。
摘要由CSDN通过智能技术生成

Proxy-Reflect vue2-vue3的响应式原理

首先我们先来看一个需求,有一个对象,我们希望监听这个对象中的属性被设置或获取的过程

我们可以通过属性描述符中的存储属性描述符来做到

Object.defineProperty(obj,key,{
	set:function(newValue){
		console.log(`监听到给${key}设置值`)
		value=newValue
	},
	get:function(){
		console.log(`监听到获取${key}的值`)
		return value
	}
})

但是我们要知道Object.defineProperty设置的初衷并不是为了去监听一个对象的所有属性的,只是为了定义属性
而且如果我们想监听更加丰富的操作,比如新增属性,删除属性,那么他是无能为力的

Proxy的基本使用

在ES6中新增了Proxy类,用于帮助我们创建一个代理

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

利用proxy实现监听对象的思路

  1. 创建Proxy对象 传入需要监听的对象以及一个处理对象(handler)
  2. 在handler里面进行监听

如果我们想监听某些具体的操作,那么可以在handler中添加对应的捕获器

set和get分别对应的是函数,

set函数有四个参数:

  1. target:目标对象(监听的对象)
  2. property:将被设置的属性key
  3. value:新属性值
  4. receiver:调用的代理对象

get函数有三个参数

  1. target:目标对象(监听的对象)
  2. property:被获取的属性key
  3. receiver:调用的代理对象

receiver的作用是
如果我们的源对象(obj)有setter,getter的访问器属性,那么可以通过receiver来改变里面的this

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

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

其他捕获器:

const obj = {
  name: "why", // 数据属性描述符
  age: 18
}


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

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

  // 监听in的捕获器
  has: function(target, key) {
    console.log(`监听到对象的${key}属性in操作`, target)
    return key in target
  },

  // 监听delete的捕获器
  deleteProperty: function(target, key) {
    console.log(`监听到对象的${key}属性in操作`, target)
    delete target[key]
  }
})


// in操作符
// console.log("name" in objProxy)

// delete操作
delete objProxy.name

所有的捕获器作用
在这里插入图片描述

我们会看到捕捉器中还有construct和apply,他们是应用于函数对象的

function foo() {

}

const fooProxy = new Proxy(foo, {
  apply: function(target, thisArg, argArray) {
    console.log("对foo函数进行了apply调用")
    return target.apply(thisArg, argArray)
  },
  construct: function(target, argArray, newTarget) {
    console.log("对foo函数进行了new调用")
    return new target(...argArray)
  }
})

fooProxy.apply({}, ["abc", "cba"])
new fooProxy("abc", "cba")

Reflect的基本使用

Reflect也是ES6新增的一个API,他是一个对象,字面的意思是反射

那么这个Reflect有什么用呢?

它主要提供了很多操作JS对象的方法,有点像Object中操作对象的方法

比如:
Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf() ;
Reflect.defineProperty(target,propertyKey,atttibutes)类似于Object.defineProperty()

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

  1. 早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计会更加规范,所以直接将这些API放在了Object上面
  2. 但是Object作为一个构造函数,这些操作放到他身上并不合适
  3. 另外还包含了类似于in,delete操作符,让JS看起来会有点奇怪
  4. 所以在ES6中新增了Reflect,让我们把操作对象的方法都集中到了Reflect对象上

Reflect的常见方法
在这里插入图片描述

我们可以将之前Proxy案例中对原对象的操作都修改为Reflect来操作

const objProxy = new Proxy(obj,{
	has:function(target,key){
		return Reflect.has(target,key)
	}
	set:function(target,key,value){
		return Reflect.set(target,key,value)
	}
	get:function(target,key){
		return Reflect.get(target,key)
	}
	deleteProperty:function(target,key){
		return Reflect.deleteProperty(target,key)
	}
})

响应式

我们先来看一下响应式意味着什么

  • m有一个初始化的值,有一段代码使用了这个值
  • 那么在m有一个新的值的时候,这段代码可以自动重新执行
let m = 20
console.log(m)
console.log(m*2)

m=40

为此我们就需要设计一个响应式函数,当数据变化的时候,自动去执行某一个函数

但是有一个问题:如何区分一个函数是否需要响应式?

我们来封装一个新的函数watchFn,凡是传到这个函数的就是需要响应式的,其他默认定义的函数都是不需要响应式的

原理是当obj对象某个属性的值发生改变的时候,我们就需要调用一个函数,函数就会执行函数体里面的内容,从而实现响应式,
我们将需要执行的函数都放在一个数组里,让对象某个属性的值发生改变的时候,遍历这个数组执行函数方法即可

// 封装一个响应式的函数
let reactiveFns = []
function watchFn(fn){
	reactiveFns.push(fn)
}

// 对象的响应式
const obj = {
	name:"why",
	age:18
}

watchFn(function() {
  const newName = obj.name
  console.log("你好啊, 李银河")
  console.log("Hello World")
  console.log(obj.name) // 100行
})

watchFn(function() {
  console.log(obj.name, "demo function -------")
})

function bar() {
  console.log("普通的其他函数")
  console.log("这个函数不需要有任何响应式")
}

obj.name = "kobe"
reactiveFns.forEach(fn => {
  fn()
})

响应式的依赖收集

目前我们收集的以来是放到一个数组中去保存的,但是这样会存在数据管理的问题

  • 我们在开发中需要监听很多对象的响应式
  • 这些对象需要监听的不只是一个属性,他们很多属性的变化,都有对应的响应式函数
  • 我们不可能在全局维护一大堆的数组来保存这些响应函数

所以我们需要设计一个类,这个类用来管理某一个对象的某一个属性的所有响应式函数
相当于替代了原来简单的reactiveFns的数组

class Depend{
	constructor(){
		this.reactiveFns = []
	}
	
	addDepend(reactiveFn){
		this.reactiveFns.push(reactiveFn)
	}
	
	notify(){
		this.reactiveFns.forEach(fn=>{
			fn()
		})
	}
}

// 封装一个响应式的函数
const depend = new Depend()
function warchFn(fn){
	depend.addDepend(fn)
}

// 对象的响应式
watchFn(function() {
  const newName = obj.name
  console.log("你好啊, 李银河")
  console.log("Hello World")
  console.log(obj.name) // 100行
})

watchFn(function() {
  console.log(obj.name, "demo function -------")
})

obj.name = "kobe"
depend.notify()
监听对象的变化

我们接下来就可以通过之前的方法来监听对象的变化

  1. 通过Object.defineProperty的方法(vue2)
  2. 通过new Proxy的方法(vue3)

通过proxy方法:

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(reactiveFn) {
    this.reactiveFns.push(reactiveFn)
  }

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

// 封装一个响应式的函数
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

// 对象的响应式
const obj = {
  name: "why", // depend对象
  age: 18 // depend对象
}

// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = new Proxy(obj, {
  get: function(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set: function(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    depend.notify()
  }
})

watchFn(function() {
  const newName = objProxy.name
  console.log("你好啊, 李银河")
  console.log("Hello World")
  console.log(objProxy.name) // 100行
})

watchFn(function() {
  console.log(objProxy.name, "demo function -------")
})

watchFn(function() {
  console.log(objProxy.age, "age 发生变化是需要执行的----1")
})

watchFn(function() {
  console.log(objProxy.age, "age 发生变化是需要执行的----2")
})

objProxy.name = "kobe"
objProxy.name = "james"
objProxy.name = "curry"

objProxy.age = 100

对象的依赖管理

我们目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数

但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理,我们如何可以使用一种数据结构来管理不同对象的不同依赖关系?

我们可以使用ES6中的WeakMap,WeakMap可以让我们在对象上添加属性,并且不干扰垃圾回收机制。

对象的响应式操作vue3

// 保存当前需要收集的响应式函数
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)
  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
}

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"

对象的响应式操作vue2

// 保存当前需要收集的响应式函数
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)
  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
}

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
}

// 监听对象的属性变量: 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"
foo.name = "hhh"

set: function(newValue) {
value = newValue
const depend = getDepend(obj, key)
depend.notify()
}
})
})
return obj
}

// 监听对象的属性变量: 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”
foo.name = “hhh”




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值