1、Proxy代理对象
Proxy用于对一个普通对象代理,实现对象的拦截和自定义,如拦截其赋值、枚举、函数调用等。里面包含了很多组捕获器(trap),在代理对象执行相应的操作时捕获,然后在内部实现自定义。
const data = {foo: 1}
// 代理data对象
const obj = new Proxy(data, {
// obj元素赋值触发
set(target, key, newValue){
// 赋值自定义
},
// 读取obj元素值触发
get(target, key){
// 读取时自定义
}
})
如上代码中obj
对象为data
对象的代理对象,此时obj内的元素和data一样,当在修改obj内部元素的时候会触发相应的捕获器,这样就可以实现自定义。
那么Proxy代理和响应式系统的设计有什么关系呢?请看以下关于副作用函数的介绍。
2、副作用函数
副作用函数是指会产生副作用的函数, 如:
function effect() {
document.body.innerText = "hello world"
}
在执行effect函数
时会改变body的文本内容,但是如果有其它函数会读取body的文本内容,这时body内容被effect函数修改了,就可以说effect在执行时产生了副作用,即如果一个函数的执行会直接或者间接的影响其他函数的执行就说这个函数产生了副作用。
如果此时有这么一段代码:
const obj = {content: "hello world"}
function effect() {
document.body.innerText = obj.content
}
在执行effect函数的时候会将obj.content
的内容显示到body的文本内容中,那么如果我们每次修改obj.content的内容都会触发effect函数的执行,那么是不是就可以说obj.content是 一个响应式数据呢。
修改obj.content的内容然后触发effect函数,是不是使用就上面所述的Proxy代理即可实现,给obj进行代理,在修改content的值时会触发捕获器,此时我们可以在捕获器内自定义功能,就可以调用effect函数。
3、响应式数据的基本实现
接着上文思考,当obj变成了响应式数据会发生什么:
- 修改obj.content的值,这会触发effect函数的执行
- 触发effect函数,这会获取obj.content的值。
关于第二点触发effect函数一定会获取obj.content的值吗,这其实是肯定的,如果effect函数不会获取obj.content的值那么也就没有绑定的必要了。
使用代码也很容易实现,即:
// 原始对象
const data = {content: "hello world"}
function effect() {
document.body.innerText = obj.content
}
const obj = new Proxy(data, {
// 拦截读取操作,此时target为原始对象,key为元素键
get(target, key) {
return target[key]
},
// 拦截赋值操作
set(target, key, newVal) {
target[key] = newVal
// 设置新值之后执行副作用函数
effect()
return true
}
})
// 修改后触发副作用函数
obj.content = 2
其执行逻辑也很简单,如图所示:
这里主要注意的是当原始对象被代理之后,与副作用函数交互的都是代理对象,而不是原始对象,此时修改原始对象是不会触发副作用函数的。
其实以上的设计并不完善,比如对象内元素的值影响的副作用函数并不止一个,这在开发中很常见,一个数据不一定仅仅绑定一个组件,以上的代码设计就太简单了。其实我们可以设计一个桶,将对象元素的副作用函数都加入桶中,当这个元素值被修改的时候将桶内的副作用函数全部拿出来执行。
代码如下:
// 用Set来模拟一个桶
const bucket = new Set()
const data = {text: "hello world"}
function effect() {
document.body.innerText = obj.text
}
const obj = new Proxy(data, {
// 拦截读取操作,此时target为原始对象,key为元素键
get(target, key) {
// 在读取时,将副作用函数装进去
bucket.add(effect)
return target[key]
},
// 拦截赋值操作
set(target, key, newVal) {
target[key] = newVal
// 设置新的值后取出所有副作用函数并执行
bucket.forEach(fn => fn())
return true
}
})
以上就是一个非常简单且简陋的响应式系统,实现了响应式最基本的功能,但是其实还有非常多的问题都没有解决,如副作用函数是直接使用的effect函数名字获取,但是如果是匿名函数等无法获取,比如这个桶也非常的粗糙,将副作用函数完全塞进去,并没有细分,还有无限递归死循环问题等没有解决,这在之后的内容中会一一解决。