JS監(jiān)聽(tīng)一個(gè)變量改變的兩種方法
需求和背景
在業(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/setPrototypeOfisExtensible/preventExtensionsownKeys/getOwnPropertyDescriptordefineProperty/deletePropertyget/set/hasapply/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è)變量變化
以上就是JS監(jiān)聽(tīng)一個(gè)變量改變的兩種方法的詳細(xì)內(nèi)容,更多關(guān)于JS監(jiān)聽(tīng)變量改變的資料請(qǐng)關(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)云支付那些事兒
我們?cè)谧鲂〕绦蛑Ц断嚓P(guān)的開(kāi)發(fā)時(shí),總會(huì)遇到這些難題,下面這篇文章主要給大家介紹了關(guān)于微信小程序?qū)崿F(xiàn)云支付那些事兒,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
jquery方法+js一般方法+js面向?qū)ο蠓椒▽?shí)現(xiàn)拖拽效果
多種方法制作的div拖拽,簡(jiǎn)單實(shí)用,包括了jquery方法、js一般方法、js面向?qū)ο蠓椒?/div> 2012-08-08
javascript鼠標(biāo)滑動(dòng)評(píng)分控件完整實(shí)例
這篇文章主要介紹了javascript鼠標(biāo)滑動(dòng)評(píng)分控件實(shí)現(xiàn)方法,以完整實(shí)例形式詳細(xì)分析了javascript操作鼠標(biāo)事件及頁(yè)面元素樣式實(shí)現(xiàn)評(píng)分效果的方法,需要的朋友可以參考下2015-05-05
javascript+iframe 實(shí)現(xiàn)無(wú)刷新載入整頁(yè)的代碼
用iframe也可以實(shí)現(xiàn),但沒(méi)有上述做法完美,參見(jiàn)discuz那些網(wǎng)站,如登陸彈出一個(gè)層,也是載入的一個(gè)頁(yè)面,但我發(fā)現(xiàn)狀態(tài)欄左邊出現(xiàn)的是 正在打開(kāi)about:blank2010-03-03
javascript修改IMG標(biāo)簽的src問(wèn)題
javascript修改IMG標(biāo)簽的SRC,在IE6下面圖片修改正常,但在IE7和Firefox下面卻不刷新,下面有個(gè)解決方法,大家可以參考下2014-03-03
Google Suggest ;-) 基于js的動(dòng)態(tài)下拉菜單
Google Suggest ;-) 基于js的動(dòng)態(tài)下拉菜單...2006-10-10
javascript發(fā)表評(píng)論或者留言時(shí)的展開(kāi)效果
javascript發(fā)表評(píng)論或者留言時(shí)的展開(kāi)效果...2007-07-07最新評(píng)論

