變化偵測(cè)
解決了 確定狀態(tài)中發(fā)生了什么變化 的問題城榛。
背景
vue會(huì)自動(dòng)通過狀態(tài)生成DOM,并將其輸出到頁面上顯示出來歉提,這個(gè)過程叫渲染笛坦。vue的渲染過程是聲明式的,我們通過模板來描述狀態(tài)與DOM之間的映射關(guān)系苔巨。通常在運(yùn)行中應(yīng)用內(nèi)部的狀態(tài)會(huì)不斷發(fā)生變化版扩,此時(shí)需要不停地重新渲染。這時(shí)如何確定狀態(tài)中發(fā)生了什么變化侄泽?
object的變化偵測(cè)
代碼示意如下:
const bailRE = /[^\w.$]/
function parsePath(path) {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) {
return
}
obj = obj[segments[i]]
}
return obj
}
}
function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
arr.splice(index, 1)
}
}
}
export default class Dep {
constructor () {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
remove(sub) {
this.remove(this.subs, sub)
}
depend() {
if ( window.target ) {
this.addSub(window.target)
}
}
notify() {
let subs = this.subs.slice()
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
export default class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm
this.getter = parsePath(expOrFn)
this.cb = cb
this.value = this.get()
}
get() {
window.target = this
let value = this.getter.call(this.vm, this.vm)
window.target = undefined
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
export default class Observer {
constructor(value) {
this.value = value
if ( !Array.isArray(value) ) {
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
defineReactive(data, key, val) {
if ( typeof val === 'object' ) {
new Observer(val)
}
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend()
return val
},
set: function (newVal) {
if ( val === newVal ) {
return
}
val = newVal
dep.notify()
}
})
}