這篇文章主要介紹“Vue數(shù)據(jù)的響應(yīng)過(guò)程”,在日常操作中,相信很多人在Vue數(shù)據(jù)的響應(yīng)過(guò)程問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Vue數(shù)據(jù)的響應(yīng)過(guò)程”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)專(zhuān)注于新賓企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站建設(shè),商城網(wǎng)站制作。新賓網(wǎng)站建設(shè)公司,為新賓等地區(qū)提供建站服務(wù)。全流程定制設(shè)計(jì),專(zhuān)業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,成都創(chuàng)新互聯(lián)專(zhuān)業(yè)和態(tài)度為您提供的服務(wù)Vue 中可以用 $watch 實(shí)例方法觀察一個(gè)字段,當(dāng)該字段的值發(fā)生變化時(shí),會(huì)執(zhí)行指定的回調(diào)函數(shù)(即觀察者),實(shí)際上和 watch 選項(xiàng)作用相同。如下:
vm.$watch('box', () => { console.log('box變了') }) vm.box = 'newValue' // 'box變了'
以上例切入,我想實(shí)現(xiàn)一個(gè)功能類(lèi)似的方法 myWatch。
如何知道我觀察的屬性被修改了?
—— Object.defineProperty 方法
該方法可以為指定對(duì)象的指定屬性設(shè)置 getter-setter 函數(shù)對(duì),通過(guò)這對(duì) getter-setter 可以捕獲到對(duì)屬性的讀取和修改操作。示例如下:
const data = { box: 1 } Object.defineProperty(data, 'box', { set () { console.log('修改了 box') }, get () { console.log('讀取了 box') } }) console.log(data.box) // '讀取了 box' // undefined data.box = 2 // '修改了 box' console.log(data.box) // '讀取了 box' // undefined
如此,便攔截到了對(duì) box 屬性的修改和讀取操作。
但 res 為 undefined,data.box = 2 的修改操作也無(wú)效。
get 與 set 函數(shù)功能不健全
故修改如下:
const data = { box: 1 } let value = data.box Object.defineProperty(data, 'box', { set (newVal) { if (newVal === value) return value = newVal console.log('修改了 box') }, get () { console.log('讀取了 box') return value } }) console.log(data.box) // '讀取了 box' // 1 data.box = 2 // '修改了 box' console.log(data.box) // '讀取了 box' // 2
有了這些, myWatch 方法便可實(shí)現(xiàn)如下:
const data = { box: 1 } function myWatch(key, fn) { let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal fn() }, get () { return value } }) } myWatch('box', () => { console.log('box變了') }) data.box = 2 // 'box變了'
但存在一個(gè)問(wèn)題,不能給同一屬性添加多個(gè)依賴(lài)(觀察者):
myWatch('box', () => { console.log('我是觀察者') }) myWatch('box', () => { console.log('我是另一個(gè)觀察者') }) data.box = 2 // '我是另一個(gè)觀察者'
后面的依賴(lài)(觀察者)會(huì)將前者覆蓋掉。
如何能夠添加多個(gè)依賴(lài)(觀察者)?
—— 定義一個(gè)數(shù)組,作為依賴(lài)收集器:
const data = { box: 1 } const dep = [] function myWatch(key, fn) { dep.push(fn) let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach((f) => { f() }) }, get () { return value } }) } myWatch('box', () => { console.log('我是觀察者') }) myWatch('box', () => { console.log('我是另一個(gè)觀察者') }) data.box = 2 // '我是觀察者' // '我是另一個(gè)觀察者'
修改 data.box 后,兩個(gè)依賴(lài)(觀察者)都執(zhí)行了。
若上例 data 對(duì)象需新增兩個(gè)能夠響應(yīng)數(shù)據(jù)變化的屬性 foo bar:
const data = { box: 1, foo: 1, bar: 1 }
只需執(zhí)行以下代碼即可:
myWatch('foo', () => { console.log('我是foo的觀察者') }) myWatch('bar', () => { console.log('我是bar的觀察者') })
但問(wèn)題是,不同屬性的依賴(lài)(觀察者)都被收集進(jìn)了同一個(gè) dep,修改任何一個(gè)屬性,都會(huì)觸發(fā)所有的依賴(lài)(觀察者):
data.box = 2 // '我是觀察者' // '我是另一個(gè)觀察者' // '我是foo的觀察者' // '我是bar的觀察者'
我想可以這樣解決:
const data = { box: 1, foo: 1, bar: 1 } const dep = {} function myWatch(key, fn) { if (!dep[key]) { dep[key] = [fn] } else { dep[key].push(fn) } let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep[key].forEach((f) => { f() }) }, get () { return value } }) } myWatch('box', () => { console.log('我是box的觀察者') }) myWatch('box', () => { console.log('我是box的另一個(gè)觀察者') }) myWatch('foo', () => { console.log('我是foo的觀察者') }) myWatch('bar', () => { console.log('我是bar的觀察者') }) data.box = 2 // '我是box的觀察者' // '我是box的另一個(gè)觀察者' data.foo = 2 // '我是foo的觀察者' data.bar = 2 // '我是bar的觀察者'
但實(shí)際上這樣更好些:
const data = { box: 1, foo: 1, bar: 1 } let target = null for (let key in data) { const dep = [] let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach(f => { f() }) }, get () { dep.push(target) return value } }) } function myWatch(key, fn) { target = fn data[key] } myWatch('box', () => { console.log('我是box的觀察者') }) myWatch('box', () => { console.log('我是box的另一個(gè)觀察者') }) myWatch('foo', () => { console.log('我是foo的觀察者') }) myWatch('bar', () => { console.log('我是bar的觀察者') }) data.box = 2 // '我是box的觀察者' // '我是box的另一個(gè)觀察者' data.foo = 2 // '我是foo的觀察者' data.bar = 2 // '我是bar的觀察者'
聲明 target 全局變量作為依賴(lài)(觀察者)的中轉(zhuǎn)站,myWatch 函數(shù)執(zhí)行時(shí)用 target 緩存依賴(lài),然后調(diào)用 data[key] 觸發(fā)對(duì)應(yīng)的 get 函數(shù)以收集依賴(lài),set 函數(shù)被觸發(fā)時(shí)會(huì)將 dep 里的依賴(lài)(觀察者)都執(zhí)行一遍。這里的 get set 函數(shù)形成閉包引用了上面的 dep 常量,這樣一來(lái),data 對(duì)象的每個(gè)屬性都有了對(duì)應(yīng)的依賴(lài)收集器。
且這一實(shí)現(xiàn)方式不需要通過(guò) myWatch 函數(shù)顯式地將 data 里的屬性一一轉(zhuǎn)為訪問(wèn)器屬性。
但運(yùn)行以下代碼,會(huì)發(fā)現(xiàn)仍有問(wèn)題:
console.log(data.box) data.box = 2 // '我是box的觀察者' // '我是box的另一個(gè)觀察者' // '我是bar的觀察者'
四個(gè) myWatch 執(zhí)行完之后 target 緩存的值變成了最后一個(gè) myWatch 方法調(diào)用時(shí)所傳遞的依賴(lài)(觀察者),故執(zhí)行 console.log(data.box) 讀取 box 屬性的值時(shí),會(huì)將最后緩存的依賴(lài)存入 box 屬性所對(duì)應(yīng)的依賴(lài)收集器,故而再修改 box 的值時(shí),會(huì)打印出 '我是bar的觀察者'。
我想可以在每次收集完依賴(lài)之后,將全局變量 target 設(shè)置為空函數(shù)來(lái)解決這問(wèn)題:
const data = { box: 1, foo: 1, bar: 1 } let target = null for (let key in data) { const dep = [] let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach(f => { f() }) }, get () { dep.push(target) target = () => {} return value } }) } function myWatch(key, fn) { target = fn data[key] } myWatch('box', () => { console.log('我是box的觀察者') }) myWatch('box', () => { console.log('我是box的另一個(gè)觀察者') }) myWatch('foo', () => { console.log('我是foo的觀察者') }) myWatch('bar', () => { console.log('我是bar的觀察者') })
經(jīng)測(cè)無(wú)誤。
但開(kāi)發(fā)過(guò)程中,還常碰到需觀測(cè)嵌套對(duì)象的情形:
const data = { box: { gift: 'book' } }
這時(shí),上述實(shí)現(xiàn)未能觀測(cè)到 gift 的修改,顯出不足。
如何進(jìn)行深度觀測(cè)?
——遞歸
通過(guò)遞歸將各級(jí)屬性均轉(zhuǎn)為響應(yīng)式屬性即可:
const data = { box: { gift: 'book' } } let target = null function walk(data) { for (let key in data) { const dep = [] let value = data[key] if (Object.prototype.toString.call(value) === '[object Object]') { walk(value) } Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach(f => { f() }) }, get () { dep.push(target) target = () => {} return value } }) } } walk(data) function myWatch(key, fn) { target = fn data[key] } myWatch('box', () => { console.log('我是box的觀察者') }) myWatch('box.gift', () => { console.log('我是gift的觀察者') }) data.box = {gift: 'basketball'} // '我是box的觀察者' data.box.gift = 'guitar'
這時(shí) gift 雖已是訪問(wèn)器屬性,但 myWatch 方法執(zhí)行時(shí) data[box.gift] 未能觸發(fā)相應(yīng) getter 以收集依賴(lài), data[box.gift] 訪問(wèn)不到 gift 屬性,data[box][gift] 才可以,故 myWatch 須改寫(xiě)如下:
function myWatch(exp, fn) { target = fn let pathArr, obj = data if (/\./.test(exp)) { pathArr = exp.split('.') pathArr.forEach(p => { obj = obj[p] }) return } data[exp] }
如果要讀取的字段包括 . ,那么按照 . 將其分為數(shù)組,然后使用循環(huán)讀取嵌套對(duì)象的屬性值。
這時(shí)執(zhí)行代碼后發(fā)現(xiàn),data.box.gift = 'guitar' 還是未能觸發(fā)相應(yīng)的依賴(lài),即打印出 '我是gift的觀察者' 這句信息。調(diào)試之后找到問(wèn)題:
myWatch('box.gift', () => { console.log('我是gift的觀察者') })
執(zhí)行以上代碼時(shí),pathArr 即 ['box', 'gift'],循環(huán)內(nèi) obj = obj[p] 實(shí)際上就是 obj = data[box],讀取了一次 box,觸發(fā)了 box 對(duì)應(yīng)的 getter,收集了依賴(lài):
() => { console.log('我是gift的觀察者') }
收集完將全局變量 target 置為空函數(shù),而后,循環(huán)繼續(xù)執(zhí)行,又讀取了 gift 的值,但這時(shí),target 已是空函數(shù),導(dǎo)致屬性 gift 對(duì)應(yīng)的 getter 收集了一個(gè)“空依賴(lài)”,故,data.box.gift = 'guitar' 的操作不能觸發(fā)期望的依賴(lài)。
以上代碼有兩個(gè)問(wèn)題:
修改 box 會(huì)觸發(fā)“我是gift的觀察者”這一依賴(lài)
修改 gift 未能觸發(fā)“我是gift的觀察者”的依賴(lài)
第一個(gè)問(wèn)題,讀取 gift 時(shí),必然經(jīng)歷讀取 box 的過(guò)程,故觸發(fā) box 對(duì)應(yīng)的 getter 無(wú)可避免,那么,box 對(duì)應(yīng) getter 收集 gift 的依賴(lài)也就無(wú)可避免。但想想也算合理,因?yàn)?box 修改時(shí),隸屬于 box 的 gift 也算作修改,從這一點(diǎn)看,問(wèn)題一也不算作問(wèn)題,劃去。
第二個(gè)問(wèn)題,我想可以這樣解決:
function myWatch(exp, fn) { let pathArr, obj = data if (/\./.test(exp)) { pathArr = exp.split('.') pathArr.forEach(p => { target = fn obj = obj[p] }) return } target = fn data[exp] } data.box.gift = 'guitar' // '我是gift的觀察者' data.box = {gift: 'basketball'} // '我是box的觀察者' // '我是gift的觀察者'
保證屬性讀取時(shí) target = fn 即可。
那么:
const data = { box: { gift: 'book' } } let target = null function walk(data) { for (let key in data) { const dep = [] let value = data[key] if (Object.prototype.toString.call(value) === '[object Object]') { walk(value) } Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach(f => { f() }) }, get () { dep.push(target) target = () => {} return value } }) } } walk(data) function myWatch(exp, fn) { let pathArr, obj = data if (/\./.test(exp)) { pathArr = exp.split('.') pathArr.forEach(p => { target = fn obj = obj[p] }) return } target = fn data[exp] } myWatch('box', () => { console.log('我是box的觀察者') }) myWatch('box.gift', () => { console.log('我是gift的觀察者') })
現(xiàn)在我想,假如我有以下數(shù)據(jù):
const data = { player: 'James Harden', team: 'Houston Rockets' }
執(zhí)行以下代碼:
function render() { document.body.innerText = `The last season's MVP is ${data.player}, he's from ${data.team}` } render() myWatch('player', render) myWatch('team', render) data.player = 'Kobe Bryant' data.team = 'Los Angeles Lakers'
是不是就可以將數(shù)據(jù)映射到頁(yè)面,并響應(yīng)數(shù)據(jù)的變化?
執(zhí)行代碼發(fā)現(xiàn),data.player = 'Kobe Bryant' 報(bào)錯(cuò),究其原因,render 方法執(zhí)行時(shí),會(huì)去獲取 data.player 和 data.team 的值,但此時(shí),target 為 null,那么讀取 player 時(shí)對(duì)應(yīng)的依賴(lài)收集器 dep 便收集了 null,導(dǎo)致 player 的 setter 調(diào)用依賴(lài)時(shí)報(bào)錯(cuò)。
那么我想,在 render 執(zhí)行時(shí)便主動(dòng)去收集依賴(lài),就不會(huì)導(dǎo)致 dep 里收集了 null。
細(xì)看 myWatch,這方法做的事情其實(shí)就是幫助 getter 收集依賴(lài),它的第一個(gè)參數(shù)就是要訪問(wèn)的屬性,要觸發(fā)誰(shuí)的 getter,第二個(gè)參數(shù)是相應(yīng)要收集的依賴(lài)。
這么看來(lái),render 方法既可以幫助 getter 收集依賴(lài)(render 執(zhí)行時(shí)會(huì)讀取 player team),而且它本身就是要收集的依賴(lài)。那么,我能不能修改一下 myWatch 的實(shí)現(xiàn),以支持這樣的寫(xiě)法:
myWatch(render, render)
第一個(gè)參數(shù)作為函數(shù)執(zhí)行一下便有了之前第一個(gè)參數(shù)的作用,第二個(gè)參數(shù)還是需要被收集的依賴(lài),嗯,想來(lái)合理。
那么,myWatch 改寫(xiě)如下:
function myWatch(exp, fn) { target = fn if (typeof exp === 'function') { exp() return } let pathArr, obj = data if (/\./.test(exp)) { pathArr = exp.split('.') pathArr.forEach(p => { target = fn obj = obj[p] }) return } data[exp] }
但,對(duì) team 的修改未能觸發(fā)頁(yè)面更新,想來(lái)因?yàn)?render 執(zhí)行讀取 player 收集依賴(lài)后 target 變?yōu)榭蘸瘮?shù),導(dǎo)致讀取 team 收集依賴(lài)時(shí)收集到了空函數(shù)。這里大家的依賴(lài)都是 render,故可將 target = () => {} 這句刪去。
myWatch 這樣實(shí)現(xiàn)還有個(gè)好處,假如 data 中有許多屬性都需要通過(guò) render 渲染至頁(yè)面,一句 myWatch(render, render) 便可,無(wú)須如此這般繁復(fù):
myWatch('player', render) myWatch('team', render) myWatch('number', render) myWatch('height', render) ...
那么最終:
const data = { player: 'James Harden', team: 'Houston Rockets' } let target = null function walk(data) { for (let key in data) { const dep = [] let value = data[key] if (Object.prototype.toString.call(value) === '[object Object]') { walk(value) } Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach(f => { f() }) }, get () { dep.push(target) return value } }) } } walk(data) function myWatch(exp, fn) { target = fn if (typeof exp === 'function') { exp() return } let pathArr, obj = data if (/\./.test(exp)) { pathArr = exp.split('.') pathArr.forEach(p => { target = fn obj = obj[p] }) return } data[exp] } function render() { document.body.innerText = `The last season's MVP is ${data.player}, he's from ${data.team}` } myWatch(render, render)
到此,關(guān)于“Vue數(shù)據(jù)的響應(yīng)過(guò)程”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
當(dāng)前題目:Vue數(shù)據(jù)的響應(yīng)過(guò)程-創(chuàng)新互聯(lián)
標(biāo)題網(wǎng)址:http://vcdvsql.cn/article42/jidec.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開(kāi)發(fā)、企業(yè)建站、網(wǎng)站設(shè)計(jì)、網(wǎng)站排名、用戶(hù)體驗(yàn)、網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容