VUE - MVVM - part9 - Vue

看這篇以前,若是沒有看過以前的文章,可拉到文章末尾查看以前的文章。vue

前言

激動人心的時候即未來臨,以前咱們作的 8 步,其實都在爲這一步打基礎,這一步,咱們來簡單實現一個 Vue 對象,尚未看過以前代碼的同窗,請確認看過以前的文章。git

主要實現內容

咱們從測試代碼入手,來看咱們這個 Vue 實現了什麼,而後在根據要實現的內容來編寫這個 Vue 對象:github

let test = new Vue({
    data() {
        return {
            baseTest: 'baseTest',
            objTest: {
                stringA: 'stringA',
                stringB: 'stringB'
            }
        }
    },
    methods: {
        methodTest() {
            console.log('methodTest')
            this.$emit('eventTest', '事件測試')
        }
    },
    watch: {
        'baseTest'(newValue, oldValue) {
            console.log(`baseTest change ${oldValue} => ${newValue}`)
        },
        'objTest.stringA'(newValue, oldValue) {
            console.log(`objTest.stringA change ${oldValue} => ${newValue}`)
        }
    }
})

test.$on('eventTest', function (event) {
    console.log(event)
})

test.methodTest()

test.baseTest

主要實現的內容有:函數

  1. 有屬性的監聽 Watcher
  2. 實例下 data/methods 數據的代理(直接使用 this.xxx 就能訪問到具體的屬性/方法)
  3. 有事件 $on/$emit

實現

咱們根據實現的難易程度來實現上面 3 點。測試

實現第 3 點,只要繼承 Event 這個類便可:優化

注:Vue 源碼中並非經過這個方式實現的事件,有興趣的能夠本身去了解下,可是在我看來這樣是最容易理解的方式。ui

class Vue extends Event {
    constructor() {
        // 調用父類的 constructor 方法
        super()
        ...
    }
    ...
}

Event 類在咱們上一步已經實現。this

接着咱們來處理第二點。爲了方便代碼的管理,咱們在類下定義一個 _init 方法,來實現 Vue 的初始化。代理

咱們先實現 methods 的綁定,由於 data 是要被監聽,因此要進行進一步的處理。code

class Vue extends Event {
    constructor(options) {
        // 調用父類的 constructor 方法
        super()
        this._init(options)
    }
    
    _init(options) {
        let vm = this
        if (options.methods) {
            for (let key in options.methods) {
                vm[key] = options.methods[key].bind(vm)
            }
        }
    }
}

ok methods 方法綁定完事,其實就這麼簡單。

接下來咱們來處理 data ,因爲 data 是須要被變換成可監聽結構,因此咱們先處理一下,而後代理到 this 對象下,若是直接賦值而不代理的話 data 的可監聽結構就會被破壞,咱們須要一個完整的對象,這個可監聽結構才能完整。

這裏先實現一下代理的方法:

export function proxy(target, sourceKey, key) {
    const sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get() {
        },
        set() {
        }
    }
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

原理仍是經過 Object.defineProperty 方法來實現,當訪問(gettarget 下的某個屬性的時候,就會去找 target[sourceKey] 下的同名屬性,設置(settarget 下的某個屬性,就會讓設置 target[sourceKey] 下的同名屬性。這就實現了代理。

ok 代理實現,咱們繼續爲 _init 添加方法,具體的步驟看代碼中的註釋

class Vue extends Event {
    constructor(options) {
        // 調用父類的 constructor 方法
        super()
        this._init(options)
    }
    
    _init(options) {
        let vm = this
        if (options.methods) {
            for (let key in options.methods) {
                // 綁定 this 指向
                vm[key] = options.methods[key].bind(vm)
            }
        }
        // 因爲 data 是個函數,因此須要調用,並綁定上下文環境
        vm._data = options.data.call(vm)
        // 將 vm._data 變成可監聽結構,實現 watcher 的添加
        observe(vm._data)
        // 代理屬性,這保證了監聽結構是一個完成的對象
        for (let key in vm._data) {
            proxy(vm, '_data', key)
        }
    }
}

最後一步,添加 watcher ,仔細分析咱們在實例化時寫的 watcher

watch: {
    'baseTest'(newValue, oldValue) {
        console.log(`baseTest change ${oldValue} => ${newValue}`)
    },
    'objTest.stringA'(newValue, oldValue) {
        console.log(`objTest.stringA change ${oldValue} => ${newValue}`)
    }
}

key 爲須要監聽的屬性的路徑,value 爲觸發監聽時的回調。

ok 咱們來實現它

class Vue extends Event {
    constructor(options) {
        super()
        this._init(options)
    }

    _init(options) {
        ...

        // 循環取出 key/value
        for (let key in options.watch) {
            // 用咱們以前實現的 Watcher 來註冊監聽
            // 參一:watcher 的運行環境
            // 參二:獲取註冊該 watcher 屬性
            // 參三:觸發監聽時的回調 
            new Watcher(vm, () => {
                // 須要監聽的值,eg: 'objTest.stringA' ==> vm.objTest.stringA
                return key.split('.').reduce((obj, name) => obj[name], vm)
            }, options.watch[key])
        }

    }
}

ok watcher 也已經實現,如下就是完整的代碼:

export function proxy(target, sourceKey, key) {
    const sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get() {
        },
        set() {
        }
    }
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

let uid = 0

export class Vue extends Event {
    constructor(options) {
        super()
        this._init(options)
    }

    _init(options) {
        let vm = this
        vm.uid = uid++

        if (options.methods) {
            for (let key in options.methods) {
                vm[key] = options.methods[key].bind(vm)
            }
        }

        vm._data = options.data.call(vm)
        observe(vm._data)
        for (let key in vm._data) {
            proxy(vm, '_data', key)
        }

        for (let key in options.watch) {
            new Watcher(vm, () => {
                return key.split('.').reduce((obj, name) => obj[name], vm)
            }, options.watch[key])
        }

    }
}

接下來,咱們來測試一下

let test = new Vue({
    data() {
        return {
            baseTest: 'baseTest',
            objTest: {
                stringA: 'stringA',
                stringB: 'stringB'
            }
        }
    },
    methods: {
        methodTest() {
            console.log('methodTest')
            this.$emit('eventTest', '事件測試')
        }
    },
    watch: {
        'baseTest'(newValue, oldValue) {
            console.log(`baseTest change ${oldValue} => ${newValue}`)
        },
        'objTest.stringA'(newValue, oldValue) {
            console.log(`objTest.stringA change ${oldValue} => ${newValue}`)
        }
    }
})

test.$on('eventTest', function (event) {
    console.log(event)
})

test.methodTest()
// methodTest
// 事件測試

test.baseTest = 'baseTestChange'
// baseTest change baseTest => baseTestChange

test.objTest.stringA = 'stringAChange'
// objTest.stringA change stringA => stringAChange

剛開始使用 Vue 的時候,感受代碼裏面都是些黑魔法,在看了源碼以後驚覺:其實 Vue 的整個實現並無什麼黑魔法,有的是精心的結構和處理,耐心點看下去,我相信個人收穫會很大。

點擊查看相關代碼

系列文章地址

  1. VUE - MVVM - part1 - defineProperty
  2. VUE - MVVM - part2 - Dep
  3. VUE - MVVM - part3 - Watcher
  4. VUE - MVVM - part4 - 優化Watcher
  5. VUE - MVVM - part5 - Observe
  6. VUE - MVVM - part6 - Array
  7. VUE - MVVM - part7 - Event
  8. VUE - MVVM - part8 - 優化Event
  9. VUE - MVVM - part9 - Vue
  10. VUE - MVVM - part10 - Computed
  11. VUE - MVVM - part11 - Extend
  12. VUE - MVVM - part12 - props
  13. VUE - MVVM - part13 - inject & 總結
相關文章
相關標籤/搜索