使用Proxy实现watch监听对象
- 手写一个简单的
watch
函数用来监听某个数据的变化,比如Object
- 我们来写一个简单的形式的
watch
- 形式:
watch(target, (newVal, oldVal)=>{ })
target表示监听的对象
,newVal, oldVal
自然就是新值和旧值- 如下完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>变化</button>
<script>
function watch(target, callback) {
let oldObj = JSON.parse(JSON.stringify(target)); // 深拷贝对象保存一份旧值
const handler = {
set: function (obj, prop, value, receiver) {
/**
* Reflect.set(target, propertyKey, receiver, value)
* target 要设置其属性的对象
* propertyKey 要设置的属性的名称
* value 要赋给属性的值
* receiver 接收赋值的对象,一般就是target
* */
const result = Reflect.set(obj, prop, value, receiver);
callback(obj, oldObj); // 新旧值吐出去
oldObj = JSON.parse(JSON.stringify(obj)); // 旧值更新
return result;
}
};
return new Proxy(target, handler);
}
// 原始对象数据,只是用来代理一下,往后就不用了,vue3的ref也是类似的意思
let obj = {
name: '孙悟空',
age: 500,
}
// 代理了一个新对象,只更改这个新对象即可,后续操作都通过这个新对象
let proxyObj = watch(obj, (newVal, oldVal) => {
console.log(newVal, oldVal);
});
// 按钮的点击修改代理对象的数据值,就会被watch监听到,然后触发新旧值的展示
let btn = document.querySelector('button');
btn.onclick = () => {
proxyObj.age = proxyObj.age + 1;
setTimeout(() => {
proxyObj.name = proxyObj.name + '^_^ '
}, 1000);
};
</script>
</body>
</body>
</html>
上述代码不考虑深层次对象,若是深层次,那就是递归操作即可,不赘述
注意,vue3的原始数据,也是经过代理后,就不用了
如下代码:
<el-input v-model="vvv"></el-input>
let initVal = '我是初始值'
const vvv = ref(initVal)
- 当我们在输入框中输入内容的时候,vvv的值,会发生变化
- 但是初始值initVal的值,是不会再变化的
- 即 initVal 只是使用了一次,类似上边的代码
defineProperty监听形式
- 比如使用Object.defineProperty去监听某个对象的某个属性值变化
- 这种和上面的区别就是原始对象,依旧及时使用
- 如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>变化</button>
<script>
function watch(obj, cb) {
let oldObj = JSON.parse(JSON.stringify(obj))
for (const key in obj) {
let val = obj[key]
Object.defineProperty(obj, key, {
get() {
return val
},
set(newVal) {
cb(newVal, val, key)
val = newVal
}
})
}
}
let obj = {
name: '孙悟空',
age: 500,
}
watch(obj, (newVal, oldVal, key) => {
console.log('---', newVal, oldVal, key);
})
let btn = document.querySelector('button');
btn.onclick = () => {
obj.age = obj.age + 1;
setTimeout(() => {
obj.name = '猪八戒'
}, 1000);
};
</script>
</body>
</body>
</html>
- 实际上,Proxy功能更为强大,还可以代理函数,实现功能的重写
- 就类似于继承后的新加功能
- 如下:
用Proxy代理函数可添加额外逻辑
假设有一个函数,用来求和,求1~n的和,如下:
// 求1~n的累加的和
function sum(n) {
let result = 0
for (let i = 0; i < n; i++) {
result = result + i + 1
}
return result
}
- 某些情况下,我们不能修改这个函数
- 并且也不方便重写这个函数
- 我们得在这个函数中,添加一些功能
- 比如:当前的函数返回的是1~n的累加值
- 要修改成返回一个数组,
数组的第一项是1~n的累加值,数组的第2项是要返回1~n的累乘值
- 那么,这个时候,我们就可以Proxy来代理这个函数,去加功能
- 如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 求1~n的累加的和
function sum(n) {
let result = 0
for (let i = 0; i < n; i++) {
result = result + i + 1
}
return result
}
// 代理sum函数,新增相关逻辑
let proxySum = new Proxy(sum, {
/**
* target 是代理的东西,本例中代理的是sum函数
* proxySumThis 是proxySum执行时传递的this值,用得少,本案例没有用到
* args proxySum执行时,传来的参数数组,数组存放,代表可能传进来多个参数
* */
apply: (target, proxySumThis, args) => {
// 原始函数执行结果
let res1 = target.apply(proxySumThis, args)
// 新增逻辑函数 阶乘 函数
let res2 = Array.from({ length: args[0] }, (_, i) => i + 1).reduce((acc, cur) => acc * cur, 1);
// 原有函数返回一个累加值,代理函数改写返回数组,第一项累加值,第二项累乘值
return [res1, res2]
}
})
console.log('proxySum', proxySum(4));
</script>
</body>
</html>
- 上述案例中,我们也可以去新增逻辑代理函数
- 去计算函数执行时间
- 如下:
<script>
let proxySum = new Proxy(sum, {
apply: (target, proxySumThis, args) => {
console.time()
let res = target.apply(proxySumThis, args) // 原始函数执行
console.timeEnd()
return res
}
})
console.log('proxySum', proxySum(4));
</script>
这样我们就新增了一个及时
Proxy的一种角度的理解
- Vue3.0中通过
Proxy
来替换原本的Object.defineProperty
来实现数据响应式 - Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
- tips: ES6是2015年6月发布的
let p = new Proxy(target, handler)
target
代表需要添加代理的对象,handler
用来自定义对象中的操作,比如可以用来自定义 set
或者 get
函数。
当然,还有别的语法:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
实际上,功能超级强大
如果需要实现一个 Vue 中的响应式,需要在get
中收集依赖,在set
派发更新,之所以 Vue3.0 要使用Proxy
替换原本的 API 原因在于Proxy
无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是Proxy
可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
- 实际上,笔者认为,Proxy的兼容性已经不错了
- 毕竟是2015年发布的ES6里面的东西(现在是2024年了)
- 可是啊,IE11是微软在2013年10月17日发布的浏览器
- 所以,2013年还没ES6呢,也就没有Proxy
- 所以,vue3不兼容IE11,是因为IE11没法用ES6里面的Proxy
- 历史原因...
A good memory is better than a bad pen. Record it down...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。