vue2响应式原理
响应式原理
vue会递归遍历data()返回的对象,使用es5的Object.defineProperty()覆盖data上的属性,从而拦截对data上属性的读写。比如data上有一个username属性:
data.username = (()=>{
let value = data.username // 必须将其保存为一个副本,因为如果在get/set中直接对data.username进行读写的话,会陷入死循环
Object.defineProperty(data, 'username', {
// 拦截对data.username的取值
get value() {
return value
},
// 拦截对data.username的赋值
set value(newValue) {
value = newValue
}
})
})()
实现了对data的拦截后,就可以使用观察者模式对数据变化的发起者和依赖者进行管理。注意,vue使用的是观察者模式,而不是发布/订阅者模式。两者的区别是后者多了一个“经纪人(Broker)”。
简单的vue实现
被观察者/依赖
class Dep {
current = null // 当前待注册的观察者
watchers = [] // 观察者列表
// 注册观察者
depend() {
if (Dep.current) {
this.watchers.push(Dep.current)
}
}
// 通知观察者
notify() {
this.watchers.forEach(watcher => watcher.onNofity())
}
}
观察者
class Watcher {
data = null // 数据对象
key = null // 被观察属性的键
value = null // 被观察属性的旧值
cb = null // 被观察属性变化后的回调
constructor(data, key, cb) {
this.data = data
this.key = key
this.cb = cb
// 注册依赖
Dep.current = this // 缓存自己
this.value = this.data[this.key] // 观察数据
Dep.current = null // 释放自己
}
// 被通知时调用
onNofity() {
let newValue = this.data[this.key]
let oldValue = this.value
if (newValue !== oldValue) {
this.value = newValue
this.cb(newValue, oldValue)
}
}
}
Vue类
// 简陋版Vue类
class MyVue {
/* data是一个对象。
* inputs数组,是<template/>中输入数据的部分。
* 其元素结构为{key, ele, event},key为data中属性的键,ele为输入控件,event为输入事件名。
* outputs是数组,是<template/>中输出数据的部分。
* 其元素结构为{key, ele, attr},key为data中属性的键,ele为输出控件,attr代表输出到ele的什么属性上。
*/
constructor(data, inputs, outputs) {
this.data = data
// 给obj中的每个属性创建一个依赖管理器
Object.keys(data).forEach(key => {
let dep = new Dep()
let val = data[key] // 得将值保存为一个副本,因为defineProperty()后直接读写obj[key]会陷入死循环
Object.defineProperty(data, key, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
})
// 给inputs中每个元素注册监听器
inputs.forEach(input => {
input.ele.addEventListener(input.event, e => {
this.data[input.key] = e.target.value
})
})
// 给outputs中每个元素创建观察者
outputs.forEach(output => {
// 将data[key]首次渲染到视图
output.ele[output.attr] = this.data[output.key]
// 观察数据变化,更新视图
new Watcher(this.data, output.key, value => {
output.ele[output.attr] = value
})
})
}
}
测试
window.onload = function {
const data = {
name: 'initial value'
}
// inputs和outputs是编译<template/>后得到的
const inputs = [
{
key: 'name',
ele: document.querySelector('#input'),
event: 'input'
}
]
const outputs = [
{
key: 'name',
ele: document.querySelector('#output'),
attr: 'innerHTML'
}
]
const myVue = new MyVue(data, inputs, outputs)
}
<div id="output"></div>
<input id="input" />

浙公网安备 33010602011771号