1. 程式人生 > >vue的雙向繫結和依賴收集

vue的雙向繫結和依賴收集

在掘金上買了一個關於解讀vue原始碼的小冊,因為是付費的,所以還比較放心

在小冊裡看到了關於vue雙向繫結和依賴收集的部分,總感覺有些怪怪的,然後就自己跟著敲了一遍。 敲完後,發現完全無法執行,  坑啊,  寫書人完全沒有測試過。

然後自己完善程式碼, 越寫越發現坑, 問題有些大。。。。。。

最後自己重新實現了一遍,程式碼較多。 用到觀察訂閱者模式實現依賴收集, Object.defineProperty() 實現雙向繫結

/*
    自己寫的程式碼, 實現vue的雙向繫結和依賴收集
    場景: 多個子元件用到父元件data中的資料, 當父元件data中的此資料發生改變時, 
    所有依賴它的 子元件全部更新
    通常子元件的從父元件中拿取的資料不允許發生改變
*/ //訂閱者 Dep //一個訂閱者只管理一個數據 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() }) } } //監聽者 //一個vue例項包含一個Watcher例項 class Watcher { // 在例項化Watcher時, 將Dep的target指向此例項, 在依賴收集中使用 // 因為依賴收集是在元件初始化時觸發的, 而資料變更後檢視相應變更是在初始化後 // 所以讓Dep.target指向此例項, 當此vue例項初始化完成後, 再指向下一個正在初始化的vue例項完成依賴收集 constructor (name) { Dep.target
= this this._name = name } update () { // 這裡模擬檢視更新 // 其實還應該讓子元件的props相應值與父元件更新的資料同步 console.log("子元件檢視更新了..." + this._name) } } //對data中的資料設定讀寫監聽, 並且建立訂閱者, 用於收集子元件的依賴和釋出 function defineReactive (obj, key, value) { // 對vue例項中data物件的每一個屬性都 設定一個訂閱者Dep let dep = new Dep() // 第二個vue例項的監聽 覆蓋了第一個vue例項的監聽, 因為引用的obj是同一個 Object.defineProperty(obj, key, { configurable: true, enumerable: true, get () { // 在讀此屬性時, 將當前 watcher 物件收集到此屬性的 dep 物件中 // 在例項化vue時將Dep.target指向當前Watcher // get()依賴收集的時候是vue元件初始化的時候, set()是在初始化後 if (dep.subs.indexOf(Dep.target) === -1) { dep.addSubs(Dep.target) } //return obj[key] 此寫法報錯 提示棧溢位 原因是無限呼叫get() return value }, set (newVal) { // 此屬性改變時, 通知所有檢視更新 if (newVal !== value) { value = newVal dep.notify() } } }) } //接收一個物件作為引數, 將該物件的所有屬性呼叫defineReactive設定讀寫監聽 function observer (obj) { if (!obj || (typeof obj !== 'object')) { return } Object.keys(obj).forEach( key => { defineReactive(obj, key, obj[key]) }) } // 建構函式, 監聽 配置options中的data()方法返回的物件的所有屬性 的讀寫 class Vue { constructor (options) { this._name = options.name this._data = options.data // 每個vue元件都是一個vue例項, 在一個頁面中有多個vue例項 // 在初始化該vue例項時, new一個Watcher物件, 使Dep.target指向此例項 new Watcher(options.name) // 給data中的資料掛載讀寫監聽 observer(this._data) //模擬vue解析template過程, 獲取從父元件傳遞過來的props //在這裡進行依賴收集 this._props = options.props ? getProps() : {} // 例項化該元件的子元件 this._children = options.render ? (options.render() || {}) : {} } } // 父元件資料 let data = { first: "hello", second: 'world', third: ['啦啦啦'] } let times = 0 // 第一次呼叫返回的是第一個子元件的從父元件繼承的資料(vue中props屬性的值) // 第二次呼叫返回的是第二個子元件的從父元件繼承的資料(vue中props屬性的值) function getProps () { times++ if (times == 1) { let obj = {first: "", second: ""} 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 } 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和例項化vue的過程 //在編譯父元件 並且傳遞引數給子元件時, 將子元件的 watcher 新增進父元件的 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 都依賴此資料, 都更新 vue_root._data.third = "aaa" // 只有 vue_2 依賴到了此資料, 更新