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