看這篇以前,若是沒有看過以前的文章,可拉到文章末尾查看以前的文章。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
主要實現的內容有:函數
Watcher
data/methods
數據的代理(直接使用 this.xxx
就能訪問到具體的屬性/方法)$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
方法來實現,當訪問(get
) target
下的某個屬性的時候,就會去找 target[sourceKey]
下的同名屬性,設置(set
) target
下的某個屬性,就會讓設置 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
的整個實現並無什麼黑魔法,有的是精心的結構和處理,耐心點看下去,我相信個人收穫會很大。