Vue2 响应式系统设计原理与实现
文章目录
- Vue2 响应式系统设计原理与实现
Vue2 响应式系统设计原理与实现
Vue2 的响应式原理主要基于以下几点:
使用 Object.defineProperty () 方法对数据对象的属性进行劫持
当数据发生变化时,通知依赖该数据的视图进行更新
实现一个发布 - 订阅模式,包含 Watcher(订阅者)、Dep(依赖收集器)等核心概念
创建以下几个核心部分:
Observer:递归地将数据对象的所有属性转换为响应式
Dep:依赖收集器,负责收集和通知订阅者
Watcher:订阅者,当数据变化时执行相应的回调函数
下面我们来简单实现以下
// 依赖收集器类:管理某个数据的所有依赖(订阅者)
class Dep {// 构造函数初始化constructor() {// 存储所有订阅者的数组this.subscribers = [];}// 添加订阅者到收集器addSub(sub) {// 检查订阅者是否存在且有update方法if (sub && sub.update) {this.subscribers.push(sub);}}// 通知所有订阅者数据已更新notify() {// 遍历所有订阅者并调用其update方法this.subscribers.forEach(sub => {sub.update();});}
}// 订阅者类:代表一个依赖,数据变化时执行更新操作
class Watcher {// 构造函数:接收Vue实例、属性名和回调函数constructor(vm, key, callback) {this.vm = vm; // 存储Vue实例的引用this.key = key; // 要监视的数据属性名this.callback = callback; // 数据变化时要执行的回调函数this.value = this.get(); // 初始化时获取值,触发getter完成依赖收集}// 获取数据并将当前订阅者添加到依赖收集器get() {// 将当前订阅者设为Dep的目标,标记为当前需要收集的依赖Dep.target = this;// 访问数据属性,触发其getter,从而完成依赖收集const value = this.vm[this.key];// 重置Dep.target,避免后续操作错误收集依赖Dep.target = null;// 返回获取到的值return value;}// 数据变化时执行的更新方法update() {// 获取新值const newValue = this.get();// 保存旧值const oldValue = this.value;// 只有当新旧值不同时才执行回调if (newValue !== oldValue) {// 更新当前值为新值this.value = newValue;// 调用回调函数,并将Vue实例作为上下文,传入新值和旧值this.callback.call(this.vm, newValue, oldValue);}}
}// 将普通对象转换为响应式对象的函数
function observe(data) {// 如果数据不是对象或为null,则无需处理if (!data || typeof data !== 'object') {return;}// 创建观察者实例处理数据return new Observer(data);
}// 观察者类:负责将对象的所有属性转换为响应式
class Observer {// 构造函数:接收需要处理的数据对象constructor(data) {this.data = data;// 遍历对象属性并处理this.walk(data);}// 遍历对象的所有属性walk(data) {// 获取对象所有自有属性的键名Object.keys(data).forEach(key => {// 为每个属性定义响应式this.defineReactive(data, key, data[key]);});}// 核心方法:使用Object.defineProperty定义响应式属性defineReactive(obj, key, val) {// 为当前属性创建一个依赖收集器const dep = new Dep();// 如果属性值是对象,递归处理使其也成为响应式observe(val);// 使用Object.defineProperty劫持属性的getter和setterObject.defineProperty(obj, key, {enumerable: true, // 允许属性被枚举(例如在for...in循环中)configurable: true, // 允许属性被配置(例如删除属性)// 当属性被访问时触发的getterget() {// 如果当前有需要收集的依赖(Dep.target存在)if (Dep.target) {// 将当前依赖添加到收集器中dep.addSub(Dep.target);}// 返回属性值return val;},// 当属性被修改时触发的setterset(newVal) {// 如果新值和旧值相同,则不做处理if (newVal === val) {return;}// 更新属性值val = newVal;// 如果新值是对象,需要将其转换为响应式observe(newVal);// 通知所有依赖当前属性的订阅者数据已更新dep.notify();}});}
}// 简化版Vue类:整合响应式系统
class Vue {// 构造函数:接收配置选项constructor(options) {this.$options = options; // 存储配置选项this.$data = options.data; // 存储数据对象// 将数据转换为响应式observe(this.$data);// 将data中的属性代理到Vue实例上,方便直接访问this.proxyData(this.$data);// 如果有created生命周期钩子,执行它if (options.created) {options.created.call(this);}}// 数据代理方法:使vm.xxx等价于vm.$data.xxxproxyData(data) {// 遍历data的所有属性Object.keys(data).forEach(key => {// 在Vue实例上定义与data属性同名的属性Object.defineProperty(this, key, {// 当访问vm.xxx时,返回vm.$data.xxx的值get() {return data[key];},// 当修改vm.xxx时,同步修改vm.$data.xxxset(newVal) {data[key] = newVal;}});});}// 提供$watch方法,用于监视数据变化$watch(key, callback) {// 创建一个新的订阅者,关联到指定的属性和回调new Watcher(this, key, callback);}
}// 使用示例
const vm = new Vue({data: {message: 'Hello Vue',count: 0,user: {name: 'John'}},created() {console.log('初始化完成:', this.message);}
});// 添加监听器,当message变化时触发
vm.$watch('message', (newVal, oldVal) => {console.log(`message变化: ${oldVal} -> ${newVal}`);
});// 添加监听器,当count变化时触发
vm.$watch('count', (newVal, oldVal) => {console.log(`count变化: ${oldVal} -> ${newVal}`);
});// 添加监听器,当user.name变化时触发
vm.$watch('user.name', (newVal, oldVal) => {console.log(`user.name变化: ${oldVal} -> ${newVal}`);
});// 测试数据变化,观察是否触发更新
vm.message = 'Hello World'; // 触发message的更新
vm.count = 1; // 触发count的更新
vm.user.name = 'Jane'; // 触发user.name的更新