首先它会去执行refreshView一遍初始化视图显示data的初始化值, 然后observer对data里的数据进行劫持, 说得简单点就是对data的数据都添加了取值、赋值的监听方法. 当get方法执行时候添加订阅操作, 当取值时候去通知视图更新方法refreshView更新视图. 最后还有个优化, 将更新视图的方法变成一个微任务, 待同步的代码执行完了, 再去更新视图, 并且多次数据变更只更新一次视图.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式源码分析</title>
</head>
<body>
<div id="app"></div>
<button id="btn">change data</button>
<script>
// 用于响应式的值仿vue的data
let data = {
value: '',
name: '',
age: ''
}
// 更新视图的回调
let active = null
const refreshView = function (cb) {
active = cb
cb()
active = null
}
// 异步执行队列, 将通知更新视图的方法变成一个微任务, 待同步的数据更改代码执行完了, 才去执行一次页面的更新
let queue = []
let nextTick = cb => Promise.resolve().then(cb)
let queueJob = job => {
// 较少视图更新的方法执行
if (!queue.includes(job)) {
queue.push(job)
nextTick(flushJobs)
}
}
let flushJobs = () => {
let job
while ((job = queue.shift()) !== undefined) {
job()
}
}
// 发布订阅
class Dep {
constructor () {
this.deps = new Set()
}
depend (cb) {
active && this.deps.add(cb)
}
notify () {
this.deps.forEach(dep => queueJob(dep))
}
}
// 劫持data的数据, 给data属性的取值赋值成get,set方法
const observer = obj => {
const dep = new Dep()
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
// 取值执行的方法
get () {
dep.depend(active) // 将更新视图回调函数添加到订阅
return value
},
// 赋值执行的方法, 当数据发生变化通知视图更新
set (newVal) {
if (value === newVal) return
value = newVal
dep.notify() // 通知更新视图
}
})
})
}
observer(data)
// 更新视图
refreshView (() => {
document.querySelector('#app').innerHTML = `时间:${data.value} - 姓名:${data.name} - 年龄: ${data.age}岁`
})
// 改变data.value就可以更新视图了
const btn = document.querySelector('#btn')
btn.onclick = function () {
let num = Math.ceil(Math.random() * 10)
data.value = new Date()
data.name = 'biu' + num
data.age = num
}
</script>
</body>
</html>