vue的雙向綁定和依賴收集

在掘金上買了一個關於解讀vue源碼的小冊,由於是付費的,因此還比較放心vue

在小冊裏看到了關於vue雙向綁定和依賴收集的部分,總感受有些怪怪的,而後就本身跟着敲了一遍。 敲完後,發現徹底沒法運行,  坑啊,  寫書人徹底沒有測試過。函數

而後本身完善代碼, 越寫愈加現坑, 問題有些大。。。。。。測試

最後本身從新實現了一遍,代碼較多。 用到觀察訂閱者模式實現依賴收集, Object.defineProperty() 實現雙向綁定this

/*
    本身寫的代碼, 實現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 依賴到了此數據, 更新
相關文章
相關標籤/搜索