模拟Vue2.x的响应式系统
Vue 2.x主要采用的是观察者模式。在Vue 2.x中,当数据发生变化时,会触发视图的更新,这主要是通过观察者模式实现的。具体来说,Vue 2.x通过数据劫持的方式,使用Object.defineProperty
方法重写对象属性的getter
和setter
,从而实现对数据变化的监听。当数据变化时,会触发setter
方法,进而通知观察者(Watcher
实例)进行更新操作。
下面通过原生HTML+JavaScript模仿Vue 2.x的响应式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Reactive System</title>
</head>
<body>
<div id="app">
<label for="inputField">例:</label><input type="text" id="inputField" />
<p id="message"></p>
</div>
</body>
</html>
// 定义一个Dep类,用于管理订阅者
class Dep {
// 初始化存储订阅者(即Watcher实例)
constructor () {
// 使用Set来存储观察者,确保唯一性
this.subscribers = new Set();
}
// 添加订阅者
addSub (sub) {
if (!this.subscribers.has(sub)) { // 检查观察者是否已经订阅
this.subscribers.add(sub);
}
}
// 取消订阅
// unSub(sub) {
// this.subscribers.delete(sub);
// }
// 通知所有订阅者更新
notify () {
this.subscribers.forEach(sub => {
sub.update();
});
}
}
// 定义一个Watcher类,用于观察数据变化并更新视图
class Watcher {
/**
* 构造函数
* @param vm 视图模型,通常是数据对象
* @param exp 表达式,用于从视图模型中获取数据
* @param cb 回调函数,当数据变化时调用
*/
constructor (vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 初始化当前值,并触发getter,收集订阅者
}
/**
* 获取数据,并触发getter以收集订阅者
* @returns {*}
*/
get () {
console.log(this.vm)
Dep.target = this; // 将当前Watcher实例设置为Dep.target,用于收集订阅者
const value = this.vm[this.exp]; // 通过表达式从视图模型中获取数据
Dep.target = null; // 重置Dep.target为null
return value; // 返回获取到的数据
}
/**
* 更新方法,调用run方法更新视图
*/
update () {
this.run();
}
/**
* 运行方法,用于比较新旧值并更新视图
*/
run () {
// 重新获取值来与旧值比较
const value = this.get();
if (value !== this.value) {
this.value = value;
// 调用回调函数并传入新值和表达式
this.cb.call(this.vm, value, this.exp);
}
}
}
// data对象,包含message属性
const data = {
message: ''
};
// 创建一个Dep实例来管理message属性的订阅者
const dep = new Dep();
// 定义value变量,用于存储message属性的实际值
let value;
// 使用Object.defineProperty来劫持data对象的message属性的getter和setter
Object.defineProperty(data, 'message', {
enumerable: true, // 属性可枚举
configurable: true, // 属性可配置
get () {
if (Dep.target) {
// 将当前Watcher添加到dep的订阅者数组中
dep.addSub(Dep.target);
}
return value; // 返回value变量的值
},
set (newValue) {
if (newValue === value) return;
value = newValue; // 更新message属性的值为新值
dep.notify(); // 通知所有订阅者(Watcher)更新
}
});
// 创建一个Watcher实例来观察message属性的变化
new Watcher(data, 'message', function (newVal) {
const messageElement = document.getElementById('message');
if (messageElement) {
messageElement.textContent = newVal;
}
});
// 监听输入事件
const inputField = document.getElementById('inputField');
inputField.addEventListener('input', (event) => {
data.message = event.target.value; // 更新data.message的值
});
观察者模式是一种软件设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它的所有依赖者(观察者)都会自动收到通知并更新。
在观察者模式中:
- 主题(Subject) 或称为 发布者(Publisher):维护一个观察者列表,当状态发生变化时,会通知所有注册的观察者。
- 观察者(Observer) 或称为 订阅者(Subscriber):当主题状态改变时,会被通知并执行相应的操作。
在模拟的代码中:
Dep
类扮演了主题(Subject)的角色,维护了一个订阅者列表(subscribers
),并提供了添加订阅者(addSub
)和通知订阅者更新(notify
)的方法。Watcher
类扮演了观察者(Observer)的角色,它有一个更新方法(update
),当主题(Dep
实例)状态改变时,该方法会被调用。Object.defineProperty
被用来劫持data
对象中message
属性的getter
和setter
。当message
属性被访问时(即getter
被触发),如果当前有Watcher
正在收集依赖(Dep.target
不为null
),则将该Watcher
添加到Dep
实例的订阅者列表中。当message
属性的值被改变时(即setter
被触发),Dep
实例会通知所有订阅者(Watcher
)更新。
在这个简单的响应式系统中,观察者(Watcher
)订阅了主题(Dep
),当主题状态改变时也就是( data.message
的值发生变化时),所有订阅的观察者都会收到通知并更新自身状态。通过 Object.defineProperty
劫持的setter
会被触发,进而调用 dep.notify()
方法通知所有订阅的 Watcher
实例。