JS監(jiān)聽一個(gè)變量改變的兩種方法
需求和背景
在業(yè)務(wù)中,由于項(xiàng)目采用微前端架構(gòu),需要通過A應(yīng)用的某個(gè)值的變化對B應(yīng)用中的DOM進(jìn)行改變(如彈出一個(gè)Modal),第一個(gè)想到的可能是發(fā)布訂閱模式,其實(shí)不如將問題縮小化,采用原生的能力去解決。
下面給出兩種解決方案,同時(shí)也是尤大寫Vue
時(shí)的思路
- ES5 的
Object.defineProperty
- ES6 的
Proxy
Object.defineProperty
Object.defineProperty()
方法會(huì)直接在一個(gè)對象上定義一個(gè)新屬性,或者修改一個(gè)對象的現(xiàn)有屬性,并返回此對象——MDN
用法如下:
Object.defineProperty(obj, prop, option)
入?yún)⒂梅ǎ?/h3>
- obj:代理對象;
- prop:代理對象中的key;
- option:配置對象,
get
、set
都在其中配置;
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
對一個(gè)對象進(jìn)行整體響應(yīng)式監(jiān)聽:
// 監(jiān)視對象 function observe(obj) { // 遍歷對象,使用 get/set 重新定義對象的每個(gè)屬性值 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 }, // 重新設(shè)置值時(shí),觸發(fā)收集器的通知機(jī)制 set: function reactiveSetter(newV) { console.log('set: ' + newV) v = newV }, }) } let data = {a: 1} // 監(jiān)視對象 observe(data) data.a // get: 1 data.a = 2 // set: 2
整體思路就是遇到子對象就遞歸,和深拷貝一樣的讀參順序。
缺陷
如果學(xué)習(xí)過Vue2
源碼的同學(xué)可能比較熟,基于下面的缺陷,也是出現(xiàn)了$set
、$get
的用法。
- IE8 及更低版本 IE 是不支持的
- 無法檢測到對象屬性的新增或刪除
- 如果修改數(shù)組的
length
(Object.defineProperty
不能監(jiān)聽數(shù)組的長度),以及數(shù)組的push
等變異方法是無法觸發(fā)setter
的
Proxy
Proxy 對象用于創(chuàng)建一個(gè)對象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)
— MDN
const obj = new Proxy(target, handler)
其中:
target
:要使用Proxy
包裝的目標(biāo)對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)handler
:一個(gè)通常以函數(shù)作為屬性的對象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理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中的實(shí)踐
這里展示兩段偽代碼,大概業(yè)務(wù)流程是,當(dāng)點(diǎn)擊頁面某個(gè)按鈕(打開/關(guān)閉彈窗),觸發(fā)window.obj.showModal
的切換,從而被監(jiān)聽到全局變量的變化,從而改變React
中的state
狀態(tài),最終觸發(fā)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
總結(jié)
Proxy
相比于 defineProperty
的優(yōu)勢:
- 基于
Proxy
和Reflect
,可以原生監(jiān)聽數(shù)組,可以監(jiān)聽對象屬性的添加和刪除 - 不需要深度遍歷監(jiān)聽:判斷當(dāng)前
Reflect.get
的返回值是否為Object
,如果是則再通過reactive
方法做代理, 這樣就實(shí)現(xiàn)了深度觀測 - 只在
getter
時(shí)才對對象的下一層進(jìn)行劫持(優(yōu)化了性能)
而Proxy
除了兼容性不足以外,其他方面的表示都優(yōu)于Object.defineProperty
。
所以,建議使用 Proxy
監(jiān)測變量變化
以上就是JS監(jiān)聽一個(gè)變量改變的兩種方法的詳細(xì)內(nèi)容,更多關(guān)于JS監(jiān)聽變量改變的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Bootstrap的fileinput插件實(shí)現(xiàn)多文件上傳的方法
這篇文章主要介紹了Bootstrap的fileinput插件實(shí)現(xiàn)多文件上傳的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09關(guān)于微信小程序?qū)崿F(xiàn)云支付那些事兒
我們在做小程序支付相關(guān)的開發(fā)時(shí),總會(huì)遇到這些難題,下面這篇文章主要給大家介紹了關(guān)于微信小程序?qū)崿F(xiàn)云支付那些事兒,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09jquery方法+js一般方法+js面向?qū)ο蠓椒▽?shí)現(xiàn)拖拽效果
多種方法制作的div拖拽,簡單實(shí)用,包括了jquery方法、js一般方法、js面向?qū)ο蠓椒?/div> 2012-08-08javascript鼠標(biāo)滑動(dòng)評(píng)分控件完整實(shí)例
這篇文章主要介紹了javascript鼠標(biāo)滑動(dòng)評(píng)分控件實(shí)現(xiàn)方法,以完整實(shí)例形式詳細(xì)分析了javascript操作鼠標(biāo)事件及頁面元素樣式實(shí)現(xiàn)評(píng)分效果的方法,需要的朋友可以參考下2015-05-05javascript+iframe 實(shí)現(xiàn)無刷新載入整頁的代碼
用iframe也可以實(shí)現(xiàn),但沒有上述做法完美,參見discuz那些網(wǎng)站,如登陸彈出一個(gè)層,也是載入的一個(gè)頁面,但我發(fā)現(xiàn)狀態(tài)欄左邊出現(xiàn)的是 正在打開about:blank2010-03-03Google Suggest ;-) 基于js的動(dòng)態(tài)下拉菜單
Google Suggest ;-) 基于js的動(dòng)態(tài)下拉菜單...2006-10-10javascript發(fā)表評(píng)論或者留言時(shí)的展開效果
javascript發(fā)表評(píng)論或者留言時(shí)的展開效果...2007-07-07最新評(píng)論