本文实现一个最基本的watch,当data中的属性改变时,回去触发watch函数,并返回新值和旧值。
<!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>
let depId = 0
let watcherId = 0
const vm = {
_data: {
name: "盲僧",
actionInfo: '会马氏三角杀吗'
},
watch: {
name(newVal,oldVal) {
console.log(newVal,oldVal);
vm.actionInfo = '会eq闪吗?'
}
}
};
function render() {
document.body.innerHTML = '你的' + vm.name + vm.actionInfo
}
function defineReactive(target, key) {
const dep = new Dep();
Object.defineProperty(target, key, {
get() {
if (Dep.target) {
dep.depend(Dep.target);
}
return vm._data[key];
},
set(newVal) {
vm._data[key] = newVal;
dep.notify();
},
});
}
class Dep {
constructor() {
this.subs = [];
this.id = depId++
}
depend() {
Dep.target.addDep(this)
}
notify() {
this.subs.forEach((watcher) => watcher.update());
}
addWatcher(watcher) {
this.subs.push(watcher)
}
}
const stack = []
Dep.target = null
function pushStack(watcher) {
stack.push(watcher)
Dep.target = watcher
}
function popStack() {
stack.pop()
Dep.target = stack[stack.length - 1]
}
class Watcher {
constructor(vm, effectFn, options = {},cb) {
this.getter = effectFn;
this.vm = vm;
this.id = watcherId++
this.depsId = new Set()
this.deps = []
this.isWatch = options.isWatch
this.cb = cb
this.get();
}
get() {
pushStack(this)
this.value = this.getter();
popStack()
return this.value
}
update() {
if(this.isWatch){
const oldVal = this.value
const newVal = this.get()
this.cb(newVal,oldVal)
}else{
this.get();
}
}
addDep(dep) {
if (!this.depsId.has(dep.id)) {
this.depsId.add(dep.id)
this.deps.push(dep)
dep.addWatcher(this)
}
}
}
function observer(obj) {
Object.keys(obj).forEach(key => {
defineReactive(vm, key)
})
}
function initWatch() {
const watch = vm.watch
for (let key in watch) {
if(typeof key ==='string'){
const getter = function (){
return vm[key]
}
const cb = watch[key]
new Watcher(vm,getter,{isWatch:true},cb)
}
}
}
observer(vm._data)
initWatch()
new Watcher(vm, render)
setTimeout(()=>{
vm.name ='亚索'
},3000)
</script>
</body>
</html>
总结 - 代码解读
function initWatch() {
const watch = vm.watch
for (let key in watch) {
const getter = function (){
return vm[key]
}
const cb = watch[key]
new Watcher(vm,getter,{isWatch:true},cb)
}
}
- 其实现原理就是初始化的时候去遍历watch上的属性,并为每个属性去添加一个观察者(Watcher),以上initWatch方法中,声明了一个getter方法 ,一个cb, 这getter的主要目的是触发属性的get,将当前的watcher收集到该属性的dep中,这个cb,很明显就是当属性改变了,需要执行的回调函数。
get() {
pushStack(this)
this.value = this.getter();
popStack()
return this.value
}
update() {
if(this.isWatch){
const oldVal = this.value
const newVal = this.get()
this.cb(newVal,oldVal)
} else {
this.get();
}
}
- 上面说到我们在new Watcher的时候会第一次调用这个watcher.get(),那么会将第一次的值存起来,在代码后面我们修改了name的值,那么会触发name的set方法,会去通知相应的观察者watcher更新 - watcher.update()
最后说下执行过程:new Wacther -> 触发watcher.get() -> 触发name的get -> 收集到name的dep中 -> 修改name -> 同时观察者更新watcher.update() -> 拿到旧值,再调用watcher.get()拿到新值,执行回调cb()