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实现监听对象的思路
- 创建Proxy对象 传入需要监听的对象以及一个处理对象(handler)
- 在handler里面进行监听
如果我们想监听某些具体的操作,那么可以在handler中添加对应的捕获器
set和get分别对应的是函数,
set函数有四个参数:
- target:目标对象(监听的对象)
- property:将被设置的属性key
- value:新属性值
- receiver:调用的代理对象
get函数有三个参数
- target:目标对象(监听的对象)
- property:被获取的属性key
- 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这样的新增对象呢?
- 早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计会更加规范,所以直接将这些API放在了Object上面
- 但是Object作为一个构造函数,这些操作放到他身上并不合适
- 另外还包含了类似于in,delete操作符,让JS看起来会有点奇怪
- 所以在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()
监听对象的变化
我们接下来就可以通过之前的方法来监听对象的变化
- 通过Object.defineProperty的方法(vue2)
- 通过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"