手寫Vue2.0響應式原理
阿新 • • 發佈:2020-08-12
今天來實現一個簡易版的Vue2.0響應式
class Vue { constructor(options) { this.$options = options this.$data = options.data // 重寫陣列方法 let arrayPrototype = Array.prototype const methods = ['pop', 'push', 'shift', 'unshift'] this.proto = Object.create(arrayPrototype) methods.forEach(method => { this.proto[method] = function() { arrayPrototype[method].call(this, ...arguments) } }) // 響應化 this.observe(this.$data) // 測試程式碼 // new Watcher(this, 'test') // this.test // 建立編譯器 // new Compile(options.el, this) if (options.created) { options.created.call(this) } } // 遞迴遍歷,使傳遞進來的物件響應化 observe(value) { if (!value || typeof value !== 'object') { return } if (Array.isArray(value)) { Object.setPrototypeOf(value, this.proto) } Object.keys(value).forEach(key => { // 對key做響應式處理 this.defineReactive(value, key, value[key]) this.proxyData(key) }) } // 在Vue根上定義屬性代理data中的資料,這樣就能通過 this 呼叫資料 proxyData(key) { Object.defineProperty(this, key, { get() { return this.$data[key] }, set(newVal) { this.$data[key] = newVal } }) } defineReactive(obj, key, val) { // 遞迴響應,處理巢狀物件 this.observe(val) // 建立Dep例項: Dep和key一對一對應 const dep = new Dep() // 給obj定義屬性 Object.defineProperty(obj, key, { get() { // 將Dep.target指向的Watcher例項加入到Dep中, 這部分是收集依賴 Dep.target && dep.addDep(Dep.target) console.log('get') return val }, set(newVal) { if (newVal !== val) { val = newVal console.log('set') // console.log(`${key}屬性更新了`) dep.notify() // 通知檢視更新 } } }) } } // Dep: 管理若干watcher例項,它和key一對一關係 class Dep { constructor() { this.deps = [] } addDep(watcher) { this.deps.push(watcher) } notify() { this.deps.forEach(watcher => watcher.update()) } } // 實現update函式可以更新 class Watcher { constructor(vm, key, cb) { this.vm = vm this.key = key this.cb = cb // 將當前例項指向Dep.target Dep.target = this this.vm[this.key] Dep.target = null } update() { console.log(`${this.key}屬性更新了`) this.cb.call(this.vm, this.vm[this.key]) } }
這樣就實現了一個簡易版的Vue,Vue2.0有兩個比較明顯的問題:
- 需要注意的是Object.defineProperty的缺點是不能代理陣列,所以我們需要對陣列的方法進行重寫,詳細部分請重新看上面的程式碼,這部分是面試要考的重點。
- Vue2.0還有一個比較明顯的缺點就是,物件上不存在的屬性,不能被代理,因為從上面的程式碼,我們能看出observe遍歷的是物件上已經有的屬性,所以沒有的屬性就不會被代理,我們就必須通過呼叫$set()方法去將新加的屬性響應式,這個缺點會在Vue3.0中彌補,有興趣的同學可以看看Vue3.0的原始碼。
- 我們能看到在defineReactive一進來就呼叫了observe方法,一是需要代理根物件,二是能代理巢狀物件,而且在new Vue的過程中一次遞迴代理了所有的物件,這樣就會出現一個明顯的問題,就是一旦data中的資料層級特別深的時候,就會出現頁面渲染比較慢的現象。而且我有一點不明白的地方是為什麼在遞迴代理的時候,為什麼不優化一下演算法,可以利用棧的思想實現深度優先的非遞迴實現,然後迴圈這個棧去代理物件,當然了,尤大大這麼寫,肯定是人家的原因,但是這是我原始碼的時候,比較困惑的地方,如果有知道的同學,希望能在下方評論給我解答一下。