下面描述一下响应式原理过程,首先创建一个obj对象,对象中添加一系列属性数据。使用observe函数封装。observe函数充当数据监听劫持的第一个关卡,判断data数据是否是一个‘object’,如果不是直接返回。如果是object的话,就调用new一个实例Observer对象。自动调用walk函数进行遍历data中的key,每次遍历调用defineReactive函数,该函数是响应式中核心代码,负责对每个属性的数据劫持行为。举一个例子,文中obj.a中的a作为key被劫持到,此时在defineReactive中会先new一个Dep的实例对象,Dep作为数据依赖者管理对象在new后,创建了一个subs数组,在对应的Dep实例对象中。当我们作为一个使用者去调用obj对象中的a前需要创建一个Watcher实例对象作为数据依赖对象,并为构造器中的属性赋值。同时赋值结束调用this.get()方法拿到当前这个调用属性的属性值,赋值给value。在get方法中同时也将Watcher实例对象放入一个数组中,同时将实例对象赋值给Dep.target属性保存。当使用者真正调用obj.a时,将会触发obj.a的getter函数,触发其中的dep.depend函数去添加当前新的Dep.target 中的值当subs数组中。同理若又有一个地方调用了obj.a,此时dep.depend也会被触发添加新依赖对象Watcher,此时obj.a的dep对象中的subs中就有了两个watcher对象。当监听的obj.a发生数据更新,obj.a的setter函数被触发,此时对新值做遍历劫持并且对调用了dep.notify函数触发Watcher实例对象,循环调用watcher的更新方法重新回去当前data中的obj.a的newValue,同时调用call执行函数回调。这样我们的所有obj.a的都可以实现响应式啦!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 调用该方法来检测数据
function observe(data) {
if (typeof data !== 'object') return
new Observer(data)
}
class Observer {
constructor(value) {
this.value = value
this.walk()
}
walk() {
Object.keys(this.value).forEach((key) => defineReactive(this.value, key))
}
}
// 数据拦截
function defineReactive(data, key, value = data[key]) {
const dep = new Dep()
console.log(`为${key}添加dep管理`);
observe(value)
Object.defineProperty(data, key, {
get: function reactiveGetter() {
dep.depend()
return value
},
set: function reactiveSetter(newValue) {
if (newValue === value) return
value = newValue
observe(newValue)
dep.notify()
}
})
}
// 依赖
class Dep {
constructor() {
this.subs = []
}
depend() {
if (Dep.target) {
console.log('添加wather',Dep.target);
this.addSub(Dep.target)
}
}
notify() {
const subs = [...this.subs]
subs.forEach((s) => s.update())
}
addSub(sub) {
this.subs.push(sub)
}
}
Dep.target = null
const TargetStack = []
function pushTarget(_target) {
TargetStack.push(Dep.target)
Dep.target = _target
console.log(11, TargetStack);
}
function popTarget() {
Dep.target = TargetStack.pop()
}
// watcher
class Watcher {
constructor(data, expression, cb) {
this.data = data
this.expression = expression
this.cb = cb
this.value = this.get()
}
get() {
pushTarget(this)
const value = parsePath(this.data, this.expression)
console.log(value);
popTarget()
return value
}
update() {
const oldValue = this.value
this.value = parsePath(this.data, this.expression)
this.cb.call(this.data, this.value, oldValue)
}
}
// 工具函数
function parsePath(obj, expression) {
const segments = expression.split('.')
for (let key of segments) {
if (!obj) return
obj = obj[key]
}
return obj
}
// for test
let obj = {
a: 1,
b: {
m: {
n: 4
}
}
}
observe(obj)
//创建一个Watcher作为订阅者
let w1 = new Watcher(obj, 'b.m.n', (val, oldVal) => {
console.log(`obj.a 从 ${oldVal}(oldVal) 变成了 ${val}(newVal)`)
})
// obj.b.m.n = 23
</script>
</body>
</html>