vue實(shí)現(xiàn)雙向綁定和依賴收集遇到的坑
在掘金上買了一個(gè)關(guān)于解讀vue源碼的小冊(cè),因?yàn)槭歉顿M(fèi)的,所以還比較放心
在小冊(cè)里看到了關(guān)于vue雙向綁定和依賴收集的部分,總感覺有些怪怪的,然后就自己跟著敲了一遍。 敲完后,發(fā)現(xiàn)完全無法運(yùn)行, 坑啊, 寫書人完全沒有測試過。
然后自己完善代碼, 越寫越發(fā)現(xiàn)坑, 問題有些大。。。。。。
最后自己重新實(shí)現(xiàn)了一遍,代碼較多。 用到觀察訂閱者模式實(shí)現(xiàn)依賴收集, Object.defineProperty() 實(shí)現(xiàn)雙向綁定
/*
自己寫的代碼, 實(shí)現(xiàn)vue的雙向綁定和依賴收集
場景: 多個(gè)子組件用到父組件data中的數(shù)據(jù), 當(dāng)父組件data中的此數(shù)據(jù)發(fā)生改變時(shí),
所有依賴它的 子組件全部更新
通常子組件的從父組件中拿取的數(shù)據(jù)不允許發(fā)生改變
*/
//訂閱者 Dep
//一個(gè)訂閱者只管理一個(gè)數(shù)據(jù)
class Dep {
constructor () {
this.subs = [] //存放vue組件
}
addSubs (sub) {
this.subs.push(sub)
console.log('add watcher: ', sub._name)
}
notify () {
this.subs.forEach( sub => { //通知vue組件更新
sub.update()
})
}
}
//監(jiān)聽者
//一個(gè)vue實(shí)例包含一個(gè)Watcher實(shí)例
class Watcher {
// 在實(shí)例化Watcher時(shí), 將Dep的target指向此實(shí)例, 在依賴收集中使用
// 因?yàn)橐蕾囀占窃诮M件初始化時(shí)觸發(fā)的, 而數(shù)據(jù)變更后視圖相應(yīng)變更是在初始化后
// 所以讓Dep.target指向此實(shí)例, 當(dāng)此vue實(shí)例初始化完成后, 再指向下一個(gè)正在初始化的vue實(shí)例完成依賴收集
constructor (name) {
Dep.target = this
this._name = name
}
update () {
// 這里模擬視圖更新
// 其實(shí)還應(yīng)該讓子組件的props相應(yīng)值與父組件更新的數(shù)據(jù)同步
console.log("子組件視圖更新了..." + this._name)
}
}
//對(duì)data中的數(shù)據(jù)設(shè)置讀寫監(jiān)聽, 并且創(chuàng)建訂閱者, 用于收集子組件的依賴和發(fā)布
function defineReactive (obj, key, value) {
// 對(duì)vue實(shí)例中data對(duì)象的每一個(gè)屬性都 設(shè)置一個(gè)訂閱者Dep
let dep = new Dep()
// 第二個(gè)vue實(shí)例的監(jiān)聽 覆蓋了第一個(gè)vue實(shí)例的監(jiān)聽, 因?yàn)橐玫膐bj是同一個(gè)
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get () {
// 在讀此屬性時(shí), 將當(dāng)前 watcher 對(duì)象收集到此屬性的 dep 對(duì)象中
// 在實(shí)例化vue時(shí)將Dep.target指向當(dāng)前Watcher
// get()依賴收集的時(shí)候是vue組件初始化的時(shí)候, set()是在初始化后
if (dep.subs.indexOf(Dep.target) === -1) {
dep.addSubs(Dep.target)
}
//return obj[key] 此寫法報(bào)錯(cuò) 提示棧溢出 原因是無限調(diào)用get()
return value
},
set (newVal) { // 此屬性改變時(shí), 通知所有視圖更新
if (newVal !== value) {
value = newVal
dep.notify()
}
}
})
}
//接收一個(gè)對(duì)象作為參數(shù), 將該對(duì)象的所有屬性調(diào)用defineReactive設(shè)置讀寫監(jiān)聽
function observer (obj) {
if (!obj || (typeof obj !== 'object')) {
return
}
Object.keys(obj).forEach( key => {
defineReactive(obj, key, obj[key])
})
}
// 構(gòu)造函數(shù), 監(jiān)聽 配置options中的data()方法返回的對(duì)象的所有屬性 的讀寫
class Vue {
constructor (options) {
this._name = options.name
this._data = options.data
// 每個(gè)vue組件都是一個(gè)vue實(shí)例, 在一個(gè)頁面中有多個(gè)vue實(shí)例
// 在初始化該vue實(shí)例時(shí), new一個(gè)Watcher對(duì)象, 使Dep.target指向此實(shí)例
new Watcher(options.name)
// 給data中的數(shù)據(jù)掛載讀寫監(jiān)聽
observer(this._data)
//模擬vue解析template過程, 獲取從父組件傳遞過來的props
//在這里進(jìn)行依賴收集
this._props = options.props ? getProps() : {}
// 實(shí)例化該組件的子組件
this._children = options.render ? (options.render() || {}) : {}
}
}
// 父組件數(shù)據(jù)
let data = {
first: "hello",
second: 'world',
third: ['啦啦啦']
}
let times = 0
// 第一次調(diào)用返回的是第一個(gè)子組件的從父組件繼承的數(shù)據(jù)(vue中props屬性的值)
// 第二次調(diào)用返回的是第二個(gè)子組件的從父組件繼承的數(shù)據(jù)(vue中props屬性的值)
function getProps () {
times++
if (times == 1) {
let obj = {first: "", second: ""}
Object.keys(obj).forEach( key => {
// 如果是對(duì)象, 則進(jìn)行深拷貝
// 這里使用到了父組件的數(shù)據(jù), 觸發(fā)依賴收集
if (data[key] instanceof Object) {
obj[key] = JSON.parse(JSON.stringify(data[key]))
} else {
obj[key] = data[key]
}
})
return obj
} else if (times == 2) {
let obj = {first: "", third: ""}
Object.keys(obj).forEach( key => {
if (data[key] instanceof Object) {
obj[key] = JSON.parse(JSON.stringify(data[key]))
} else {
obj[key] = data[key]
}
})
return obj
}
}
let vue_root = new Vue({
name: 'vue_root',
data,
//模擬編譯template和實(shí)例化vue的過程
//在編譯父組件 并且傳遞參數(shù)給子組件時(shí), 將子組件的 watcher 添加進(jìn)父組件的 dep
render () {
let vue_1 = new Vue({
name: 'vue_1',
data: {},
props: true,
render () {}
})
let vue_2 = new Vue({
name: 'vue_2',
data: {},
props: true,
render () {}
})
return {
vue_1,
vue_2
}
}
})
console.log(vue_root)
vue_root._data.first = 'hello hello' // vue_1 和 Vue_2 都依賴此數(shù)據(jù), 都更新
vue_root._data.third = "aaa" // 只有 vue_2 依賴到了此數(shù)據(jù), 更新
總結(jié)
以上所述是小編給大家介紹的vue的雙向綁定和依賴收集遇到的坑,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
基于vue.js實(shí)現(xiàn)側(cè)邊菜單欄
這篇文章主要為大家詳細(xì)介紹了基于vue.js實(shí)現(xiàn)側(cè)邊菜單欄的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
vue中的任務(wù)隊(duì)列和異步更新策略(任務(wù)隊(duì)列,微任務(wù),宏任務(wù))
這篇文章主要介紹了vue中的任務(wù)隊(duì)列和異步更新策略(任務(wù)隊(duì)列,微任務(wù),宏任務(wù)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
通過vue-cli3構(gòu)建一個(gè)SSR應(yīng)用程序的方法
這篇文章主要介紹了通過vue-cli3構(gòu)建一個(gè)SSR應(yīng)用程序,以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。2018-09-09

