看這篇之前,假如沒有看過之前的文章,可拉到文章末端檢察之前的文章。
回憶
在 step4
中,我們大抵完成了一個 MVVM
的框架,由3個部份構成:
-
defineReactive
掌握了對象屬性,使變成可監聽構造 -
Dep
網絡治理依靠 -
Watcher
一個籠統的依靠
defineReactive
和 Dep
革新了對象下的某個屬性,將目的變成了觀察者形式中的目的,當目的發生變化時,會挪用觀察者;
Watcher
就是一個詳細的觀察者,會註冊到目的中。
之前的代碼完成了觀察者形式,使得數據的變化得以相應,然則照樣有兩個須要優化的處所:
- 假如我們想讓對象的屬性都得以相應,那我們必需對對象下的一切屬性舉行遍歷,順次挪用
defineReactive
這不是很輕易 - 代碼都在一個文件中,不利於治理
處理
題目2
先處理第二個題目,我們僅僅須要把代碼舉行分別即可,然後用 webpack/babel
打包即可,固然這裏就不說怎樣去設置 webpack
,運用 webpack/babel
我們就能夠運用 ES6
的語法和模塊體系了。
然則為了偷懶,我把代碼直接放在 node
環境中實行了,然則 import
語法須要特定的 node
版本,我這裏運用的是 8.11.1
(版本網上都應該是支撐的),同時須要特定的文件後綴(.mjs)和敕令 node --experimental-modules xxx.mjs
。
實行體式格局進入到 step5
的目次下,敕令行運轉 node --experimental-modules test.mjs
即可。
固然你也能夠用 webpack/babel
舉行打包和轉碼,然後放到瀏覽器上運轉即可。
題目1
關於題目1,我們須要做的僅僅是完成一個要領舉行遍歷對象屬性即可。我們把這個歷程籠統成一個對象 Observe
。至於為何要把這個歷程籠統成一個對象,背面會說。
注: 由因而在 node
環境下運轉代碼,這裏就直接用 ES6
的語法了。一樣的我把別的模塊也用 ES6
語法寫了一遍。
export class Observer {
constructor(value) {
this.value = value
this.walk(value)
// 標誌這個對象已被遍歷過,同時保留 Observer
Object.defineProperty(value, '__ob__', {
value: this,
enumerable: false,
writable: true,
configurable: true
})
}
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
從代碼能夠看出,這個類在實例化的時刻自動遍歷了傳入參數下的一切屬性(value
),並把每一個屬性都應用了 defineReactive
。
為了確保傳入的值為對象,我們再寫一個要領來推斷。
export function observe (value) {
// 確保 observe 為一個對象
if (typeof value !== 'object') {
return
}
let ob
// 假如對象下有 Observer 則不須要再次天生 Observer
if (value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (Object.isExtensible(value)) {
ob = new Observer(value)
}
return ob
}
函數返回該對象的 Observer
實例,這裏推斷了假如該對象下已有 Observer
實例,則直接返回,不再去臨盆 Observer
實例。這就確保了一個對象下的 Observer
實例僅被實例化一次。
上面代碼完成了對某個對象下一切屬性的轉化,然則假如對象下的某個屬性是對象呢?
所以我們還需革新一下 defineReactive
詳細代碼為:
export function defineReactive(object, key, value) {
let dep = new Dep()
// 遍歷 value 下的屬性,因為在 observe 中已推斷是不是為對象,這裏就不推斷了
observe(value)
Object.defineProperty(object, key, {
configurable: true,
enumerable: true,
get: function () {
if (Dep.target) {
dep.addSub(Dep.target)
Dep.target.addDep(dep)
}
return value
},
set: function (newValue) {
if (newValue !== value) {
value = newValue
dep.notify()
}
}
})
}
ok 我們來測試下
import Watcher from './Watcher'
import {observe} from "./Observe"
let object = {
num1: 1,
num2: 1,
objectTest: {
num3: 1
}
}
observe(object)
let watcher = new Watcher(object, function () {
return this.num1 + this.num2 + this.objectTest.num3
}, function (newValue, oldValue) {
console.log(`監聽函數,${object.num1} + ${object.num2} + ${object.objectTest.num3} = ${newValue}`)
})
object.num1 = 2
// 監聽函數,2 + 1 + 1 = 4
object.objectTest.num3 = 2
// 監聽函數,2 + 1 + 2 = 5
固然為了更好的相識這個歷程,最好把 step5
目次中的代碼拉下來一同看。至於之前完成的功用這裏就不特地寫測試了。
末了
末了詮釋下為何要把遍歷對象屬性這個歷程籠統成一個對象
對象在
js
下寄存是是援用,也就是說有能夠幾個對象下的某個屬性是同一個對象下的援用,以下let obj1 = {num1: 1} let obj2 = {obj: obj1} let obj3 = {obj: obj1}
假如我們籠統成對象,而僅僅是函數挪用的話,那末
obj1
這個對象就會遍歷兩次,而籠統成一個對象的話,我們能夠把這個對象保留在obj1
下(__ob__ 屬性),遍歷的時刻推斷一下就好。- 固然處理上面題目我們也能夠在
obj1
下設置一個標誌位即可,然則這個對象在以後會有特別的用處,先如許寫吧。(與數組和Vue.set
有關)
在代碼中我為 Dep
和 Watch
添加了 id
這個臨時用不到,先加上。
系列文章地點
- VUE – MVVM – part1 – defineProperty
- VUE – MVVM – part2 – Dep
- VUE – MVVM – part3 – Watcher
- VUE – MVVM – part4 – 優化Watcher
- VUE – MVVM – part5 – Observe
- VUE – MVVM – part6 – Array
- VUE – MVVM – part7 – Event
- VUE – MVVM – part8 – 優化Event
- VUE – MVVM – part9 – Vue
- VUE – MVVM – part10 – Computed
- VUE – MVVM – part11 – Extend
- VUE – MVVM – part12 – props
- VUE – MVVM – part13 – inject & 總結