JS監(jiān)聽(tīng)變量改變的實(shí)現(xiàn)
需求和背景
在業(yè)務(wù)中,由于項(xiàng)目采用微前端架構(gòu),需要通過(guò)A應(yīng)用的某個(gè)值的變化對(duì)B應(yīng)用中的DOM進(jìn)行改變(如彈出一個(gè)Modal),第一個(gè)想到的可能是發(fā)布訂閱模式,其實(shí)不如將問(wèn)題縮小化,采用原生的能力去解決。
下面給出兩種解決方案,同時(shí)也是尤大寫(xiě)Vue
時(shí)的思路
- ES5 的
Object.defineProperty
- ES6 的
Proxy
Object.defineProperty
Object.defineProperty()
方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象——MDN
用法如下:
Object.defineProperty(obj, prop, option)
入?yún)⒂梅ǎ?/h3>
- obj:代理對(duì)象;
- prop:代理對(duì)象中的key;
- option:配置對(duì)象,
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
對(duì)一個(gè)對(duì)象進(jìn)行整體響應(yīng)式監(jiān)聽(tīng):
// 監(jiān)視對(duì)象 function observe(obj) { // 遍歷對(duì)象,使用 get/set 重新定義對(duì)象的每個(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)視對(duì)象 observe(data) data.a // get: 1 data.a = 2 // set: 2
整體思路就是遇到子對(duì)象就遞歸,和深拷貝一樣的讀參順序。
缺陷
如果學(xué)習(xí)過(guò)Vue2
源碼的同學(xué)可能比較熟,基于下面的缺陷,也是出現(xiàn)了$set
、$get
的用法。
- IE8 及更低版本 IE 是不支持的
- 無(wú)法檢測(cè)到對(duì)象屬性的新增或刪除
- 如果修改數(shù)組的
length
(Object.defineProperty
不能監(jiān)聽(tīng)數(shù)組的長(zhǎng)度),以及數(shù)組的push
等變異方法是無(wú)法觸發(fā)setter
的
Proxy
Proxy 對(duì)象用于創(chuàng)建一個(gè)對(duì)象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)
— MDN
const obj = new Proxy(target, handler)
其中:
target
:要使用Proxy
包裝的目標(biāo)對(duì)象(可以是任何類型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)handler
:一個(gè)通常以函數(shù)作為屬性的對(duì)象,各屬性中的函數(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)擊頁(yè)面某個(gè)按鈕(打開(kāi)/關(guān)閉彈窗),觸發(fā)window.obj.showModal
的切換,從而被監(jiān)聽(tīng)到全局變量的變化,從而改變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)勢(shì):
- 基于
Proxy
和Reflect
,可以原生監(jiān)聽(tīng)數(shù)組,可以監(jiān)聽(tīng)對(duì)象屬性的添加和刪除 - 不需要深度遍歷監(jiān)聽(tīng):判斷當(dāng)前
Reflect.get
的返回值是否為Object
,如果是則再通過(guò)reactive
方法做代理, 這樣就實(shí)現(xiàn)了深度觀測(cè) - 只在
getter
時(shí)才對(duì)對(duì)象的下一層進(jìn)行劫持(優(yōu)化了性能)
而Proxy
除了兼容性不足以外,其他方面的表示都優(yōu)于Object.defineProperty
。
所以,建議使用 Proxy
監(jiān)測(cè)變量變化
到此這篇關(guān)于JS監(jiān)聽(tīng)變量改變的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)JS監(jiān)聽(tīng)變量改變內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript 動(dòng)態(tài)table添加colspan\rowspan 參數(shù)的方法
動(dòng)態(tài)的給某個(gè)表對(duì)象添加列屬性和行屬性,采用obj.setAttribute("rowspan",n)(即rowspan=n)不能生效。2009-07-07js中用window.open()打開(kāi)多個(gè)窗口的name問(wèn)題
這篇文章主要介紹了js中用window.open()打開(kāi)多個(gè)窗口的問(wèn)題,需要的朋友可以參考下2014-03-03js實(shí)現(xiàn)Select列表各項(xiàng)上移和下移的方法
這篇文章主要介紹了js實(shí)現(xiàn)Select列表各項(xiàng)上移和下移的方法,涉及javascript動(dòng)態(tài)操作頁(yè)面元素屬性值的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08Javascript中正則表達(dá)式的應(yīng)用詳解
這篇文章主要為大家詳細(xì)介紹了Javascript中正則表達(dá)式的應(yīng)用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02JS 中LocalStorage和SessionStorage的使用
最近因?yàn)轫?xiàng)目上需要使用到客戶端存儲(chǔ),所以稍微研究了一下,以下說(shuō)說(shuō)自己的理解和使用經(jīng)驗(yàn),特此分享到腳本之家平臺(tái),感興趣的朋友參考下吧2017-08-08探索JavaScript函數(shù)的無(wú)限可能(函數(shù)基本概念)
JavaScript中的函數(shù)是一種重要的編程概念,它允許我們封裝可重用的代碼塊,并在需要時(shí)進(jìn)行調(diào)用,本文將深入介紹JavaScript函數(shù)的各個(gè)方面,包括函數(shù)定義和調(diào)用、參數(shù)和返回值、作用域和閉包、高階函數(shù)以及常見(jiàn)的函數(shù)應(yīng)用場(chǎng)景,感興趣的朋友一起看看吧2023-08-08