摘要:進(jìn)階系列一之響應(yīng)式原理及實(shí)現(xiàn)進(jìn)階系列二之插件原理及實(shí)現(xiàn)進(jìn)階系列三之函數(shù)原理及實(shí)現(xiàn)什么是響應(yīng)式表示一個(gè)狀態(tài)改變之后,如何動(dòng)態(tài)改變整個(gè)系統(tǒng),在實(shí)際項(xiàng)目應(yīng)用場(chǎng)景中即數(shù)據(jù)如何動(dòng)態(tài)改變。描述符必須是這兩種形式之一,但二者不能共存,不然會(huì)出現(xiàn)異常。
(關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo))
Vue進(jìn)階系列匯總?cè)缦拢瑲g迎閱讀,歡迎加高級(jí)前端進(jìn)階群一起學(xué)習(xí)(文末)。
Vue 進(jìn)階系列(一)之響應(yīng)式原理及實(shí)現(xiàn)
Vue 進(jìn)階系列(二)之插件原理及實(shí)現(xiàn)
Vue 進(jìn)階系列(三)之Render函數(shù)原理及實(shí)現(xiàn)
什么是響應(yīng)式ReactivityReactivity表示一個(gè)狀態(tài)改變之后,如何動(dòng)態(tài)改變整個(gè)系統(tǒng),在實(shí)際項(xiàng)目應(yīng)用場(chǎng)景中即數(shù)據(jù)如何動(dòng)態(tài)改變Dom。
需求現(xiàn)在有一個(gè)需求,有a和b兩個(gè)變量,要求b一直是a的10倍,怎么做?
簡(jiǎn)單嘗試1:let a = 3; let b = a * 10; console.log(b); // 30
乍一看好像滿足要求,但此時(shí)b的值是固定的,不管怎么修改a,b并不會(huì)跟著一起改變。也就是說b并沒有和a保持?jǐn)?shù)據(jù)上的同步。只有在a變化之后重新定義b的值,b才會(huì)變化。
a = 4; console.log(a); // 4 console.log(b); // 30 b = a * 10; console.log(b); // 40簡(jiǎn)單嘗試2:
將a和b的關(guān)系定義在函數(shù)內(nèi),那么在改變a之后執(zhí)行這個(gè)函數(shù),b的值就會(huì)改變。偽代碼如下。
onAChanged(() => { b = a * 10; })
所以現(xiàn)在的問題就變成了如何實(shí)現(xiàn)onAChanged函數(shù),當(dāng)a改變之后自動(dòng)執(zhí)行onAChanged,請(qǐng)看后續(xù)。
結(jié)合view層現(xiàn)在把a(bǔ)、b和view頁面相結(jié)合,此時(shí)a對(duì)應(yīng)于數(shù)據(jù),b對(duì)應(yīng)于頁面。業(yè)務(wù)場(chǎng)景很簡(jiǎn)單,改變數(shù)據(jù)a之后就改變頁面b。
document .querySelector(".cell.b") .textContent = state.a * 10
現(xiàn)在建立數(shù)據(jù)a和頁面b的關(guān)系,用函數(shù)包裹之后建立以下關(guān)系。
onStateChanged(() => { document .querySelector(‘.cell.b’) .textContent = state.a * 10 })
再次抽象之后如下所示。
{{ state.a * 10 }}
onStateChanged(() => {
view = render(state)
})
view = render(state)是所有的頁面渲染的高級(jí)抽象。這里暫不考慮view = render(state)的實(shí)現(xiàn),因?yàn)樾枰婕暗紻OM結(jié)構(gòu)及其實(shí)現(xiàn)等一系列技術(shù)細(xì)節(jié)。這邊需要的是onStateChanged的實(shí)現(xiàn)。
實(shí)現(xiàn)實(shí)現(xiàn)方式是通過Object.defineProperty中的getter和setter方法。具體使用方法參考如下鏈接。
MDN之Object.defineProperty
需要注意的是get和set函數(shù)是存取描述符,value和writable函數(shù)是數(shù)據(jù)描述符。描述符必須是這兩種形式之一,但二者不能共存,不然會(huì)出現(xiàn)異常。
實(shí)例1:實(shí)現(xiàn)convert()函數(shù)要求如下:
1、傳入對(duì)象obj作為參數(shù)
2、使用Object.defineProperty轉(zhuǎn)換對(duì)象的所有屬性
3、轉(zhuǎn)換后的對(duì)象保留原始行為,但在get或者set操作中輸出日志
示例:
const obj = { foo: 123 } convert(obj) obj.foo // 輸出 getting key "foo": 123 obj.foo = 234 // 輸出 setting key "foo" to 234 obj.foo // 輸出 getting key "foo": 234
在了解Object.defineProperty中getter和setter的使用方法之后,通過修改get和set函數(shù)就可以實(shí)現(xiàn)onAChanged和onStateChanged。
實(shí)現(xiàn):
function convert (obj) { // 迭代對(duì)象的所有屬性 // 并使用Object.defineProperty()轉(zhuǎn)換成getter/setters Object.keys(obj).forEach(key => { // 保存原始值 let internalValue = obj[key] Object.defineProperty(obj, key, { get () { console.log(`getting key "${key}": ${internalValue}`) return internalValue }, set (newValue) { console.log(`setting key "${key}" to: ${newValue}`) internalValue = newValue } }) }) }實(shí)例2:實(shí)現(xiàn)Dep類
要求如下:
1、創(chuàng)建一個(gè)Dep類,包含兩個(gè)方法:depend和notify
2、創(chuàng)建一個(gè)autorun函數(shù),傳入一個(gè)update函數(shù)作為參數(shù)
3、在update函數(shù)中調(diào)用dep.depend(),顯式依賴于Dep實(shí)例
4、調(diào)用dep.notify()觸發(fā)update函數(shù)重新運(yùn)行
示例:
const dep = new Dep() autorun(() => { dep.depend() console.log("updated") }) // 注冊(cè)訂閱者,輸出 updated dep.notify() // 通知改變,輸出 updated
首先需要定義autorun函數(shù),接收update函數(shù)作為參數(shù)。因?yàn)檎{(diào)用autorun時(shí)要在Dep中注冊(cè)訂閱者,同時(shí)調(diào)用dep.notify()時(shí)要重新執(zhí)行update函數(shù),所以Dep中必須持有update引用,這里使用變量activeUpdate表示包裹update的函數(shù)。
實(shí)現(xiàn)代碼如下。
let activeUpdate = null function autorun (update) { const wrappedUpdate = () => { activeUpdate = wrappedUpdate // 引用賦值給activeUpdate update() // 調(diào)用update,即調(diào)用內(nèi)部的dep.depend activeUpdate = null // 綁定成功之后清除引用 } wrappedUpdate() // 調(diào)用 }
wrappedUpdate本質(zhì)是一個(gè)閉包,update函數(shù)內(nèi)部可以獲取到activeUpdate變量,同理dep.depend()內(nèi)部也可以獲取到activeUpdate變量,所以Dep的實(shí)現(xiàn)就很簡(jiǎn)單了。
實(shí)現(xiàn)代碼如下。
class Dep { // 初始化 constructor () { this.subscribers = new Set() } // 訂閱update函數(shù)列表 depend () { if (activeUpdate) { this.subscribers.add(activeUpdate) } } // 所有update函數(shù)重新運(yùn)行 notify () { this.subscribers.forEach(sub => sub()) } }
結(jié)合上面兩部分就是完整實(shí)現(xiàn)。
實(shí)例3:實(shí)現(xiàn)響應(yīng)式系統(tǒng)要求如下:
1、結(jié)合上述兩個(gè)實(shí)例,convert()重命名為觀察者observe()
2、observe()轉(zhuǎn)換對(duì)象的屬性使之響應(yīng)式,對(duì)于每個(gè)轉(zhuǎn)換后的屬性,它會(huì)被分配一個(gè)Dep實(shí)例,該實(shí)例跟蹤訂閱update函數(shù)列表,并在調(diào)用setter時(shí)觸發(fā)它們重新運(yùn)行
3、autorun()接收update函數(shù)作為參數(shù),并在update函數(shù)訂閱的屬性發(fā)生變化時(shí)重新運(yùn)行。
示例:
const state = { count: 0 } observe(state) autorun(() => { console.log(state.count) }) // 輸出 count is: 0 state.count++ // 輸出 count is: 1
結(jié)合實(shí)例1和實(shí)例2之后就可以實(shí)現(xiàn)上述要求,observe中修改obj屬性的同時(shí)分配Dep的實(shí)例,并在get中注冊(cè)訂閱者,在set中通知改變。autorun函數(shù)保存不變。
實(shí)現(xiàn)如下:
class Dep { // 初始化 constructor () { this.subscribers = new Set() } // 訂閱update函數(shù)列表 depend () { if (activeUpdate) { this.subscribers.add(activeUpdate) } } // 所有update函數(shù)重新運(yùn)行 notify () { this.subscribers.forEach(sub => sub()) } } function observe (obj) { // 迭代對(duì)象的所有屬性 // 并使用Object.defineProperty()轉(zhuǎn)換成getter/setters Object.keys(obj).forEach(key => { let internalValue = obj[key] // 每個(gè)屬性分配一個(gè)Dep實(shí)例 const dep = new Dep() Object.defineProperty(obj, key, { // getter負(fù)責(zé)注冊(cè)訂閱者 get () { dep.depend() return internalValue }, // setter負(fù)責(zé)通知改變 set (newVal) { const changed = internalValue !== newVal internalValue = newVal // 觸發(fā)后重新計(jì)算 if (changed) { dep.notify() } } }) }) return obj } let activeUpdate = null function autorun (update) { // 包裹update函數(shù)到"wrappedUpdate"函數(shù)中, // "wrappedUpdate"函數(shù)執(zhí)行時(shí)注冊(cè)和注銷自身 const wrappedUpdate = () => { activeUpdate = wrappedUpdate update() activeUpdate = null } wrappedUpdate() }
結(jié)合Vue文檔里的流程圖就更加清晰了。
Job Done!?。?/p>
本文內(nèi)容參考自VUE作者尤大的付費(fèi)視頻交流
本人Github鏈接如下,歡迎各位Star
http://github.com/yygmind/blog
我是木易楊,網(wǎng)易高級(jí)前端工程師,跟著我每周重點(diǎn)攻克一個(gè)前端面試重難點(diǎn)。接下來讓我?guī)阕哌M(jìn)高級(jí)前端的世界,在進(jìn)階的路上,共勉!
如果你想加群討論每期面試知識(shí)點(diǎn),公眾號(hào)回復(fù)[加群]即可
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98945.html
摘要:進(jìn)階系列一之響應(yīng)式原理及實(shí)現(xiàn)進(jìn)階系列二之插件原理及實(shí)現(xiàn)進(jìn)階系列三之函數(shù)原理及實(shí)現(xiàn)函數(shù)原理根據(jù)第一篇文章介紹的響應(yīng)式原理,如下圖所示。在初始化階段,本質(zhì)上發(fā)生在函數(shù)中,然后通過函數(shù)生成,根據(jù)生成。負(fù)責(zé)收集依賴,清除依賴和通知依賴。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo))showImg(https://segmentfa...
摘要:示例輸出第一步先不考慮插件,在已有的中是沒有這個(gè)公共方法的,如果要簡(jiǎn)單實(shí)現(xiàn)的話可以通過鉤子函數(shù)來,即在里面驗(yàn)證邏輯。按照插件的開發(fā)流程,應(yīng)該有一個(gè)公開方法,在里面使用全局的方法添加一些組件選項(xiàng),方法包含一個(gè)鉤子函數(shù),在鉤子函數(shù)中驗(yàn)證。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo))showImg(https://segmen...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
閱讀 2550·2021-11-24 10:20
閱讀 2398·2021-09-10 10:51
閱讀 3384·2021-09-06 15:02
閱讀 3122·2019-08-30 15:55
閱讀 2845·2019-08-29 18:34
閱讀 3086·2019-08-29 12:14
閱讀 1224·2019-08-26 13:53
閱讀 2936·2019-08-26 13:43