-
前言
本文不会长篇大论论述Proxy的基础语法和基本使用,也不会说与文章无关或关系不大的内容,不扯别的,纯应用,有好的实际应用会不定期更新本文章 -
什么是Proxy对象?
JavaScript中的Proxy对象是ES6中的一个新特性,可以用来拦截JavaScript对象的底层操作,比如属性访问、属性赋值、函数调用等等。它可以用于创建一个代理对象,这个代理对象可以用来代替原始对象进行操作,从而可以在操作的过程中添加自己的逻辑。使用Proxy对象可以使得代码更加简洁、易于维护。 -
响应式数据
-
数据响应的概念
数据响应是指当数据发生变化时,相应的UI界面也会随之改变的能力。在现代Web开发中,数据响应是一个非常重要的概念,因为它可以使得应用程序更加灵活、交互性更好。
-
使用Proxy实现数据响应
在JavaScript中,我们可以使用Proxy对象来实现数据响应的功能。具体地说,我们可以使用Proxy对象来拦截对象的属性访问和属性赋值,从而实现数据响应的功能。以下是一个基础示例代码,展示了如何使用Proxy对象实现数据响应的功能:
const data = { name: '张三', age: 18, }; const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); const result = Reflect.set(target, key, value, receiver); return result; }, }; const proxy = new Proxy(data, handler); proxy.name; // 输出:读取了属性name proxy.age = 20; // 输出:设置了属性age的值为20
注意: proxy.age = 20; 只触发了set
在上述代码中,我们定义了一个普通的JavaScript对象data,然后创建了一个Proxy对象proxy来代理这个普通对象。我们还定义了一个handler对象来拦截Proxy对象的属性访问和属性赋值操作。在handler对象的get方法和set方法中,我们添加了一些打印信息的逻辑,以便更好地理解Proxy对象的工作原理。
在使用Proxy对象的过程中,我们可以通过添加一些自己的逻辑来实现数据响应的功能。例如,在上述示例代码中,我们可以在set方法中添加一些代码,当数据发生变化时,更新UI界面的内容。
-
实现简单的数据响应
在实际开发中,我们通常需要实现更加复杂的数据响应功能,例如当数据发生变化时,自动更新UI界面的内容。以下是一个示例代码,展示了如何使用Proxy对象实现一个简单的数据响应功能:
// 定义 effect 函数 const effects = []; const watchEffect = (fn) => { if(effects.includes(fn)) { return } effects.push(fn) }; const trigger = (target, key) => { // 触发更新 effects.forEach(effect => { effect(); }); }; function reactive(target) { const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`) return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); const result = Reflect.set(target, key, value, receiver); // 触发更新 trigger(target, key); return result; }, }; const proxy = new Proxy(target, handler); return proxy; } const reactiveData = reactive({ name: '张三', age: 18, }); // 访问数据对象 watchEffect(() => { console.log(reactiveData.name); // 输出:李四, 李四 }); watchEffect(() => { console.log(reactiveData.age); // 输出:18,20 }); // 修改数据对象 reactiveData.name = '李四'; reactiveData.age = 20;
上述代码中 通过Proxy实现了简单的响应式数据, 通过watchEffect可以监听到数据的变化,但目前还存在很多问题
-
资源释放问题
目前watchEffect监听数据变化不支持取消监听,会导致性能问题,比如需要在组件卸载的时候进行资源释放,只需要给watchEffect函数加一个返回一个可以取消监听的函数即可,如下所示:
const unWatchEffect = (effectFn) => { return () => { const index = effects.find(val => val == effectFn) if(index < 0) { return } effects.splice(index, 1) } } const watchEffect = (fn) => { if(effects.includes(fn)) { return unWatchEffect(fn) } effects.push(fn) return unWatchEffect(fn) }; const unWatch = watchEffect(() => { console.log(reactiveData.age); // 输出:20 }); // ... 省略 reactiveData.age = 20; unWatch() reactiveData.age = 22;
-
对象嵌套问题
目前访问嵌套对象的属性是无法触发监听器的,因为 Proxy 只能代理对象本身的属性。如果要代理嵌套属性,需要通过递归的方式代理每个嵌套对象的属性,以达到监听嵌套属性的目的。加入递归,代码如下所示:
function reactive(target) { const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`) const result = Reflect.get(target, key, receiver); // typeof null 也等于 object if(typeof result === "object" && result !== null) { // 递归代理 return reactive(result) } return result; }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); const old = Reflect.get(target, key, receiver); const result = Reflect.set(target, key, value, receiver); if(old !== value) { // 触发更新 trigger(target, key); } return result; } }; const proxy = new Proxy(target, handler); return proxy; }
-
监听特定key的变化问题
目前我们实现了监听某个对象任意属性的变化,但无法指定监听特定数据,可以增加watch函数来实现这一个功能,watch函数有两个参数,一个是getter,callback,代码如下:
function watch(getter, callback) { // 获取旧的值 let oldValue = getter(); console.log(`oldValue: ${oldValue}`); return watchEffect(() => { // 获取最新的值 const newValue = getter(); // 如果上一个值跟当前值不相等 则说明数据发生了变化 if (oldValue !== newValue) { callback(newValue, oldValue); // 重置 oldValue oldValue = newValue; } }); }
使用watch对特定key进行监听示例:
const reactiveData = reactive({ name: '张三', age: 18, a: { b: 1 }, arr: [0, 1, 2] }); // 同样可以使用unWatch进行取消监听 const unWatch = watch(() => reactiveData.a.b, (newVal, oldVal) => { // 输出 newVal: 11 oldVal: 1 console.log(`newVal: ${newVal} oldVal: ${oldVal}`) }) reactiveData.a.b = 11 unWatch() // 取消监听
-
支持访问源对象
有时候我们可能会有访问源对象的需求 可以在代理层留个口子
function reactive(target) { const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`) if (key === '__original__') { // 如果要获取原始对象,返回 target 对象 return target; } const result = Reflect.get(target, key, receiver); // typeof null 也等于 nulll if(typeof result === "object" && result !== null) { // 递归代理 return reactive(result) } return result; }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); const old = Reflect.get(target, key, receiver); const result = Reflect.set(target, key, value, receiver); if(old !== value) { // 触发更新 trigger(target, key); } return result; } }; const proxy = new Proxy(target, handler); return proxy; }
-
使用缓存
function reactive(target) { const deepProxyCacheMap = new Map(); const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`) if (key === '__original__') { // 如果要获取原始对象,返回 target 对象 return target; } const result = Reflect.get(target, key, receiver); // typeof null 也等于 object if(typeof result === "object" && result !== null) { // 从缓存中拿 if (deepProxyCacheMap.has(key)) { return deepProxyCacheMap.get(key); } else { // 递归代理 const deepProxyResult = reactive(result); deepProxyCacheMap.set(key, deepProxyResult); return deepProxyResult; } } // 否则就直接返回不再proxy return result; }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); // 把之前的缓存删除掉 deepProxyCacheMap.delete(key); const old = Reflect.get(target, key, receiver); const result = Reflect.set(target, key, value, receiver); if(old !== value) { // 触发更新 trigger(); } return result; } }; const proxy = new Proxy(target, handler); return proxy; }
-
set权限问题
在实际情况中并不是任意情况下都能进行set,set有如下约束,我们需要对这些情况进行相应处理
- 当目标属性是一个不可写或者不可配置的数据属性,则不能去改变它的值
- 当目标属性没有配置
set
方法,也就是说[[Set]]
属性的是undefined
,则不能去设置它的值
例如: 不可以写入时候在严格模式下会报
TypeError
const data = { name: '张三', } Object.defineProperty(data, 'age', { value: 18, configurable: true, // age不可以写 只读 writable: false, // 同样 如果enumerable为false 在严格模式下也会报错 enumerable: true }) const reactiveData = reactive(data); watch(() => reactiveData.age, (newVal, oldVal) => { console.log(`newVal: ${newVal} oldVal: ${oldVal}`) }) reactiveData.age = 11
-
支持代理多个源对象
目前
reactive
已经支持进行多次代理了,但watchEffect目前还是全局的,也就是说,不管reactive
代理多少个对象,但任意个代理对象的数据发生变化watchEffect都会触发,将所有AIP封装成一个整体即可,优化如下:function reactive(target) { const effects = []; const unWatchEffect = (effectFn) => { return () => { const index = effects.find(val => val == effectFn) if (index < 0) { return } effects.splice(index, 1) } } const watchEffect = (fn) => { if (effects.includes(fn)) { return unWatchEffect(fn) } effects.push(fn) return unWatchEffect(fn) }; function watch(getter, callback) { // 获取旧的值 let oldValue = getter(); console.log(`oldValue: ${oldValue}`); return watchEffect(() => { // 获取最新的值 const newValue = getter(); // 如果上一个值跟当前值不相等 则说明数据发生了变化 if (oldValue !== newValue) { callback(newValue, oldValue); // 重置 oldValue oldValue = newValue; } }); } const trigger = () => { // 触发更新 effects.forEach(effect => effect()); }; const observeData = (() => { const deepProxyCacheMap = new Map(); const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`) if (key === '__original__') { // 如果要获取原始对象,返回 target 对象 return target; } const result = Reflect.get(target, key, receiver); // typeof null 也等于 nulll if (typeof result === "object" && result !== null) { // 层缓存中拿 if (deepProxyCacheMap.has(key)) { return deepProxyCacheMap.get(key); } else { const deepProxyResult = reactive(result); deepProxyCacheMap.set(key, deepProxyResult); return deepProxyResult; } } // 否则就直接返回不再proxy return result; }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); // 把之前的缓存删除掉 deepProxyCacheMap.delete(key); const old = Reflect.get(target, key, receiver); const result = Reflect.set(target, key, value, receiver); if (old !== value) { // 触发更新 trigger(); } return result; } }; const proxy = new Proxy(target, handler); return proxy; })() return { data: observeData, watchEffect: watchEffect, watch: watch } }
用法如下:
const data = { name: '张三', } Object.defineProperty(data, 'age', { value: 18, configurable: true, writable: true, enumerable: true }) const {data: reactiveData, watch, watchEffect} = reactive(data); watch(() => reactiveData.age, (newVal, oldVal) => { // 输出 newVal: 11 oldVal: 18 console.log(`newVal: ${newVal} oldVal: ${oldVal}`) }) reactiveData.age = 11
-
所有代码
function reactive(target) { const effects = []; const unWatchEffect = (effectFn) => { return () => { const index = effects.find(val => val == effectFn) if (index < 0) { return } effects.splice(index, 1) } } const watchEffect = (fn) => { if (effects.includes(fn)) { return unWatchEffect(fn) } effects.push(fn) return unWatchEffect(fn) }; function watch(getter, callback) { // 获取旧的值 let oldValue = getter(); console.log(`oldValue: ${oldValue}`); return watchEffect(() => { // 获取最新的值 const newValue = getter(); // 如果上一个值跟当前值不相等 则说明数据发生了变化 if (oldValue !== newValue) { callback(newValue, oldValue); // 重置 oldValue oldValue = newValue; } }); } const trigger = () => { // 触发更新 effects.forEach(effect => effect()); }; const observeData = (() => { const deepProxyCacheMap = new Map(); const handler = { get(target, key, receiver) { console.log(`读取了属性${key}`) if (key === '__original__') { // 如果要获取原始对象,返回 target 对象 return target; } const result = Reflect.get(target, key, receiver); // typeof null 也等于 nulll if (typeof result === "object" && result !== null) { // 层缓存中拿 if (deepProxyCacheMap.has(key)) { return deepProxyCacheMap.get(key); } else { const deepProxyResult = reactive(result); deepProxyCacheMap.set(key, deepProxyResult); return deepProxyResult; } } // 否则就直接返回不再proxy return result; }, set(target, key, value, receiver) { console.log(`设置了属性${key}的值为${value}`); // 把之前的缓存删除掉 deepProxyCacheMap.delete(key); const old = Reflect.get(target, key, receiver); const result = Reflect.set(target, key, value, receiver); if (old !== value) { // 触发更新 trigger(); } return result; } }; const proxy = new Proxy(target, handler); return proxy; })() return { data: observeData, watchEffect: watchEffect, watch: watch } } const data = { name: '张三', } Object.defineProperty(data, 'age', { value: 18, configurable: true, writable: true, enumerable: true }) const {data: reactiveData, watch, watchEffect} = reactive(data); watch(() => reactiveData.age, (newVal, oldVal) => { console.log(`newVal: ${newVal} oldVal: ${oldVal}`) }) reactiveData.age = 11
-
结语
如有不对之处,欢迎各位道友批评指正
-
-
-