
最近一后端朋友想了解下vue的响应式,新公司的工作也不是很忙,正好抽时间把响应式编程研究一下
首先来看什么是响应式编程:
- 响应式编程是一种面向数据串流和变化传播的编程范式,wiki定义
- 这可以在编程语言中很方便的表示静态和动态的数据流,而相关的数据模型会自动将变化的值通过数据流进行传播
- example: 表达式a = b + c 中, 将结果赋值给a后, 改变b或者c的值, 在命令式编程中, a 的值不会进行变化,而在响应式编程中,a会随c或者b的更新而更新, 如excel的计算公式
根据定义,简单实现一个demo
- 首先来看我们的需求,有一表达式,y = x + 2, 当x 变化时,y 的值会自动更新
- 我们需要检测x的变化,目前可以有proxy,Object.defineProperty两种API可以支持,下面我们来简单实现下
let collectFlag
let makeReact = function (val) {
return new Proxy({}, {
get (target, prop) {
return Reflect.get(target, prop)
},
set (target, prop, value) {
if (Reflect.get(target, prop) !== value) {
let result = Reflect.set(target, prop, value);
collectFlag && collectFlag();
return result
}
}
})
}
let watchX = (fn) => {
collectFlag = fn
}
let x = makeReact(1)
let fy = () => {
console.log('invoked')
return x + 2
}
watchX(fy)
x.value = 2
x.value = 3
- 现在我们增加一下难度,让y = x + z * 2, 这样的话我们就要增加一个来收集依赖的机制了,代码如下
class Dep {
constructor () {
this.deps = new Set()
}
add (dep) {
if (typeof dep === 'function') {
this.deps.add(dep)
}
}
notify () {
this.deps.forEach(dep => dep())
}
}
let collectFlag
let ref = (value) => {
let dep = new Dep()
return new Proxy({
value: value
}, {
get (target, prop) {
dep.add(collectFlag)
return Reflect.get(target, prop)
},
set (target, prop, value) {
if (Reflect.get(target, prop) !== value) {
let result = Reflect.set(target, prop, value);
dep.notify()
return result
}
}
})
}
let watchReref = (fn) => {
collectFlag = fn
collectFlag()
collectFlag = null
}
let x = ref(1)
let z= ref(2)
watchReref(() => {
console.log('y', x.value + z.value * 2)
})
x = 2
x = 3
z = 6
- 好的,我们进一步增加需求,想想我们在vue中写代码的时候,一个交互可能会引起多个数据的更新,在上例中,每次x,z变化的时候,都会触发依赖,在例子中可能并没有什么大问题,但是在vue中,一个渲染函数里可能有多个依赖,如果每个依赖变化都要进行一遍diff和DOM 挂载,肯定性能不理想,所以vue里用了异步队列$nextTick来收集响应,当所有同步代码执行完再统一更新,异步队列代码如下:
let nextTick = (function () {
let queue = []
function dequeue () {
while (queue.length) {
queue.shift()()
}
}
return function (fn) {
if (!queue.includes(fn)) {
queue.push(fn)
}
Promise.resolve().then(dequeue)
}
})()
notify () {
this.deps.forEach(nextTick)
}
x.value = 3;
x.value = 6;
z.value = 10;
setTimout(() => {z.value = 12, x.value = 9})
- 好,现在我们就实现了 一个输入变化 输出自动跟随的响应式模型,这个模型对应到vue中就是数据(输入)到模板渲染(输出),把本例中的y = x.value + z.value * 2 改为 document.write(
<div>x:${x.value},z:${z.value}</div>)就是一个简化版数据到html渲染的响应式模型了(这个省略了模板编译,和虚拟DOM)
本篇主要描述了响应式模型和异步队列的构建,下一篇再论述vue 中watch 和computed 的实现