这篇文章主要介绍“JS监听变量改变如何实现”,在日常操作中,相信很多人在JS监听变量改变如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JS监听变量改变如何实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
需求和背景
在业务中,由于项目采用微前端架构,需要通过A应用的某个值的变化对B应用中的DOM进行改变(如弹出一个Modal),第一个想到的可能是发布订阅模式,其实不如将问题缩小化,采用原生的能力去解决。
下面给出两种解决方案,同时也是尤大写
Vue时的思路
ES5 的
Object.defineProperty
ES6 的
Proxy
Object.defineProperty
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
——MDN
用法如下:
Object.defineProperty(obj, prop, option)
入参用法:
obj:代理对象;
prop:代理对象中的key;
option:配置对象,
get、
set都在其中配置;
例子:
var obj = { name: 'sorryhc' } var rocordName = 'sorryhc'; Object.defineProperty(obj, 'name', { enumerable: true, configurable:true, set: function(newVal) { rocordName = newVal console.log('set: ' + rocordName) }, get: function() { console.log('get: ' + rocordName) return rocordName } }) obj.name = 'sorrycc' // set: sorrycc console.log(obj.name) // get: sorrycc
对一个对象进行整体响应式监听:
// 监视对象 function observe(obj) { // 遍历对象,使用 get/set 重新定义对象的每个属性值 Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]) }) } function defineReactive(obj, k, v) { // 递归子属性 if (typeof(v) === 'object') observe(v) // 重定义 get/set Object.defineProperty(obj, k, { enumerable: true, configurable: true, get: function reactiveGetter() { console.log('get: ' + v) return v }, // 重新设置值时,触发收集器的通知机制 set: function reactiveSetter(newV) { console.log('set: ' + newV) v = newV }, }) } let data = {a: 1} // 监视对象 observe(data) data.a // get: 1 data.a = 2 // set: 2
整体思路就是遇到子对象就递归,和深拷贝一样的读参顺序。
缺陷
如果学习过
Vue2源码的同学可能比较熟,基于下面的缺陷,也是出现了
$set、
$get的用法。
IE8 及更低版本 IE 是不支持的
无法检测到对象属性的新增或删除
如果修改数组的
length(
Object.defineProperty不能监听数组的长度),以及数组的
push等变异方法是无法触发
setter的
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
— MDN
const obj = new Proxy(target, handler)
其中:
target:要使用
Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
obj的行为
例子
const handler = { get: function(target, name){ return name in target ? target[name] : 'no prop!' }, set: function(target, prop, value, receiver) { target[prop] = value; console.log('property set: ' + prop + ' = ' + value); return true; } }; var user = new Proxy({}, handler) user.name = 'sorryhc' // property set: name = sorryhc console.log(user.name) // sorryhc console.log(user.age) // no prop!
并且
Proxy提供了更丰富的代理能力:
getPrototypeOf/
setPrototypeOf
isExtensible/
preventExtensions
ownKeys/
getOwnPropertyDescriptor
defineProperty/
deleteProperty
get/
set/
has
apply/
construct
感兴趣的可以查看 MDN ,一一尝试一下,这里不再赘述
在React中的实践
这里展示两段伪代码,大概业务流程是,当点击页面某个按钮(打开/关闭弹窗),触发
window.obj.showModal的切换,从而被监听到全局变量的变化,从而改变
React中的
state状态,最终触发
Modal的弹窗。
Object.defineProperty
window.obj = { showModal: false } const [visible, setVisible] = useState(false); useEffect(() => { visible && Modal.show({ // ... }) }, [visible]) Object.defineProperty(window.obj, 'showModal', { enumerable: true, configurable:true, set: function(newVal) { setVisible(newVal); console.log('set: ' + newVal) }, get: function() { console.log('get: ' + visible) return visible } }) window.obj.showModal = !window.obj.showModal // set: true console.log(window.obj.showModal) // get: true
Proxy
const [visible, setVisible] = useState(false); useEffect(() => { visible && Modal.show({ // ... }) }, [visible]) const handler = { get: function(target, name){ return name in target ? target[name] : 'no prop!' }, set: function(target, prop, value, receiver) { target[prop] = value; setVisible(value); console.log('property set: ' + prop + ' = ' + value); return true; } }; window.obj = new Proxy({showModal: false}, handler) window.obj.showModal = !window.obj.showModal // property set: showModal = true console.log(window.obj.showModal) // true