JS如何监听一个变量改变?

简介: 在业务中,由于项目采用微前端架构,需要通过A应用的某个值的变化对B应用中的DOM进行改变(如弹出一个Modal),第一个想到的可能是发布订阅模式,其实不如将问题缩小化,采用原生的能力去解决。

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

需求和背景

在业务中,由于项目采用微前端架构,需要通过A应用的某个值的变化对B应用中的DOM进行改变(如弹出一个Modal),第一个想到的可能是发布订阅模式,其实不如将问题缩小化,采用原生的能力去解决。

下面给出两种解决方案,同时也是尤大写Vue时的思路

  • ES5 的 Object.defineProperty
  • ES6 的 Proxy

Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

——MDN

用法如下:

Object.defineProperty(obj, prop, option)

入参用法:

  • obj:代理对象;
  • prop:代理对象中的key;
  • option:配置对象,getset都在其中配置;

例子:

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 是不支持的
  • 无法检测到对象属性的新增或删除
  • 如果修改数组的 lengthObject.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

总结

Proxy 相比于 defineProperty 的优势:

  • 基于 ProxyReflect ,可以原生监听数组,可以监听对象属性的添加和删除
  • 不需要深度遍历监听:判断当前 Reflect.get 的返回值是否为 Object ,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测
  • 只在 getter 时才对对象的下一层进行劫持(优化了性能)

Proxy除了兼容性不足以外,其他方面的表示都优于Object.defineProperty

所以,建议使用 Proxy 监测变量变化

目录
相关文章
|
3月前
|
JavaScript 前端开发
JavaScript如何判断变量undefined
JavaScript如何判断变量undefined
|
3月前
|
JavaScript 前端开发
JavaScript基础知识-变量的声明提前
关于JavaScript变量声明提前特性的基础知识介绍。
50 0
JavaScript基础知识-变量的声明提前
|
1月前
|
前端开发 JavaScript
如何在 JavaScript 中访问和修改 CSS 变量?
【10月更文挑战第28天】通过以上方法,可以在JavaScript中灵活地访问和修改CSS变量,从而实现根据用户交互、页面状态等动态地改变页面样式,为网页添加更多的交互性和动态效果。在实际应用中,可以根据具体的需求和场景选择合适的方法来操作CSS变量。
|
1月前
|
前端开发 JavaScript 数据处理
CSS 变量的作用域和 JavaScript 变量的作用域有什么不同?
【10月更文挑战第28天】CSS变量和JavaScript变量虽然都有各自的作用域概念,但由于它们所属的语言和应用场景不同,其作用域的定义、范围、覆盖规则以及与其他语言特性的交互方式等方面都存在明显的差异。理解这些差异有助于更好地在Web开发中分别运用它们来实现预期的页面效果和功能逻辑。
|
3月前
|
JavaScript
在 Vue.js 中使用 watch 来监听对象属性的变化
在 Vue.js 中使用 watch 来监听对象属性的变化
|
1月前
|
前端开发 JavaScript UED
如何使用 JavaScript 动态修改 CSS 变量的值?
【10月更文挑战第28天】使用JavaScript动态修改CSS变量的值可以为页面带来更丰富的交互效果和动态样式变化,根据不同的应用场景和需求,可以选择合适的方法来实现CSS变量的动态修改,从而提高页面的灵活性和用户体验。
|
3月前
|
存储 前端开发 JavaScript
前端基础(二)_JavaScript变量、JavaScript标识符、JavaScript获取元素、JavaScript的鼠标事件
本文介绍了JavaScript变量的声明和使用、标识符的命名规则、如何获取和操作HTML元素,以及JavaScript的鼠标事件处理,通过示例代码展示了这些基础知识点在实际开发中的应用。
49 2
前端基础(二)_JavaScript变量、JavaScript标识符、JavaScript获取元素、JavaScript的鼠标事件
|
2月前
|
JavaScript 前端开发
局部 JavaScript 变量
JavaScript 中,函数内部使用 `var` 声明的变量为局部变量,仅在函数内可见,函数执行完毕后被删除。全局变量则在函数外部声明,整个页面的脚本和函数均可访问,页面关闭后才被删除。未声明的变量赋值会自动成为 `window` 对象的属性,且在非严格模式下可被删除。
|
2月前
|
JavaScript API
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
146 0
|
3月前
|
JavaScript 前端开发
js 变量作用域与解构赋值| 22
js 变量作用域与解构赋值| 22