一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
1. js如何实现监听对象属性变化
// 普通对象
const data = {}
// 把普通对象变为可监听的对象,利用Object.defineProperty
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log('取值了')
return value
},
set(newV) {
console.log('赋值了')
// 拦截赋值
document.getElementById('test').innerText = newV
value = newV
}
})
}
// 把data的name属性变为可监听的
defineReactive(data, 'name', '')
// 赋值
data.name = 'liyajie' // 打印: 赋值了
// 取值
console.log(data.name) // 打印:取值了
这样我们就可以对取值和赋值做一层拦截,比如我们可以在赋值的时候修改dom,以达到绑定的效果,这样我们就只需要关注数据对象了
2. 对依赖进行收集,统一管理
-
收集依赖:当我们使用对象中属性的时候,说明这个时候是依赖这个属性的
-
触发依赖:当我们赋值的时候,是需要对取值的地方最一次通知,也就是需要触发一次依赖
-
也就是说我们需要在
getter中进行依赖收集,在setter中触发依赖
<template>
<div>{{ name }}</div>
</template>
如上vue的模板中使用到了name,则回触发响应的getter,同时也会触发依赖收集,下次触发setter的时候触发依赖,更新name
- 如果我们把依赖用函数来表示,那触发依赖的时候我们只需要触发这个函数即可
// 改进
function defineReactive(data, key, value) {
const dep = [] // 依赖列表
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
console.log('1.收集依赖')
dep.push(function(newV, oldV) {
console.log('执行了依赖', newV, oldV)
})
return value
},
set(newV) {
if (value === newV) {
return
}
// 触发依赖
for(let i = 0, l = dep.length; i < l; i++) {
console.log('触发了依赖')
dep[i](newV, value)
}
value = newV
}
})
}
const data = {}
defineReactive(data, 'name', '') // 变为可监听对象
console.log(data.name) // 打印:1.收集依赖
data.name = 'liyajie' // print: 触发了依赖 执行了依赖
升级之后新增了dep进行存放依赖,在set触发的时候循环dep执行依赖
3. 独立Dep类
上面写法有点耦合,将Dep封装成独立类,对defineReactive再改造一下
Dep类具有的功能- 依赖收集
- 依赖触发
- 依赖移除
// Dep
class Dep {
constructor() {
this.subs = [] // 存放依赖列表
}
// 添加依赖
addSub(sub) {
this.subs.push(sub)
}
// 移除依赖
removeSub(sub) {
remove(this.subs, sub)
}
// 快速收集依赖
depend() {
// 假设我们把依赖放到window.target
if (window.target) {
this.addSub(window.target)
}
}
// 触发依赖(通知)
notify() {
const subs = this.subs.slice()
for(let i = 0, len = subs.length; i < len; i++) {
subs[i].update() // 看到这里也就能明白,window.target中有一个update方法
}
}
}
// 借助该方法移除依赖
function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
arr.splice(index, 1)
}
}
}
改造defineReactive
function defineReactive(data, key, value) {
const dep = new Dep() // 依赖列表
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
console.log('1.收集依赖')
dep.depend() // 收集
return value
},
set(newV) {
if (value === newV) {
return
}
value = newV
// 触发依赖
dep.notify()
}
})
}
再次改造后的defineReactive已经把依赖全部放到了Dep中
4. 依赖到底是啥,有什么作用 Watcher
只要我们用到数据的地方都是依赖,有的是在模板中,有的也有可能是用户自定义的watcher,这时就得将这些情况进行统一管理,收集只收集它,通知也只通知它,他就是Watcher,把他理解为中介即可,用来做统一分发处理
watcher经典使用方式
// watcher常用的方式
vm.$watch('a.b.c', function(newV, oldV){
// 执行
})
先来实现一下a.b.c这种方式读取属性
// source vue/core/util/lang.js
const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/
const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`)
function parsePath(path) {
if (bailRE.test(path)) {
return
}
return function(obj) {
const segments = path.split('.')
// 循环读取属性
for(let i = i; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
4.1 实现Watcher
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm
// getter是用来获取a.b.c的值
this.getter = parsePath(expOrFn)
this.cb = cb
this.value = this.get()
}
// 获取最新value
get() {
window.target = this
// 执行getter就是执行parsePath的返回值函数
let value = this.getter.call(this.vm, this.vm)
window.target = null
return value
}
// 执行回调
update() {
// 缓存oldValue
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
// 使用
const data = {
person: {
name: '小李子'
}
}
new Watcher(data, 'person.name', function(newV, oldV){
console.log('newV=',newV,'oldV=', oldV)
})
5. 实现整个对象的深层监听
上面我们已经实现了一个对象属性的监听,现在我们要实现整个对象的深层监听,利用递归遍历整个对象属性,让每个属性都可监听
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, len = keys.length; i < len; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
function defineReactive(data, key, value) {
if (typeof value === 'object') {
// 如果是对象,则需要设置其属性为getter/setter
new Observer(value)
}
let dep = new Dep()
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
// 依赖收集
dep.depend()
return value
},
set(newValue) {
if (newValue === value) {
return
}
value = newValue
// 循环执行依赖列表
dep.notify()
}
})
}
6. 总结
defineReactive: 将对象变为getter/setter形式Dep:new Dep()收集依赖、通知依赖Observer:new Observer(data)将对象深度遍历为getter/setter形式Watcher:new Watcher(data, 'a.b', function(newV, oldV){})监听属性变化,初始化的时候取值,取值会触发属性的getter,触发后依赖自动收集(就是把Watcher收集了起来),当属性变化的时候,执行通知依赖(就是触发Watcher的update),也就是他的回调函数,或者更新模板
感谢:深入浅出Vue.js