VUE2基本原理实现
1.1vue2响应式机制
vue2采用的是观察者模式(发布者订阅者模式),页面的标签比如
{{}}
为订阅者watcher,vue能够直接操作的Dep为发布者,发布者与订阅者直接关联,一旦data数据发生变化,vue就会找Dep,Dep就会通知订阅者watch进行数据更改(更新虚拟dom,然后虚拟dom映射到真实dom实现页面渲染),也就是说订阅者watcher负责页面的渲染- observer:使用defineproperty将对象属性转化为getter和setter,对数据进行劫持
- Dep:发布者,存储与这个属性相关的所有订阅者watcher
- watcher:订阅者,执行数据的更新,一般为标签
// 订阅者类
class Watcher {
constructor(callback) {
Dep.target=this//将标签watcher与dep联系到一起
this.callback = callback;
this.updata=null
Dep.target=null//更新完之后,断绝与watcher的联系,防止一直更新
}
update() {
//并不是直接修改,更新虚拟dom
this.callback()
}
}
首先将订阅者watcher和发布者dep通过Dep.target=this联系到一起,然后执行更新,更新之后要将其设置为null,防止一直更新
this.subs = [];// 依赖收集类
class Dep {
constructor() {
//收集订阅者
this.subs = [];
}
// 添加订阅者
addSub(sub) {
this.subs.push(sub);
}
// 通知订阅者更新
notify() {
this.subs.forEach(sub => sub.update());
}
}
发布者将订阅者watcher全部收集起来,然后当调用notify的时候,就会对所有的订阅者watcher进行遍历更新数据
1.2 vue2响应式原理
vue2中是采用object.defineProperty对数据劫持来实现对数据的响应式,当数据被访问的时候,会调用defineProperty的get方法,该函数的返回值作为属性的值,如果更改数据的时候会调用defineProperty的set方法接收一个参数,并传递给赋值时的this对象。
- 遍历劫持
主要采用observer函数对数据进行遍历劫持,如果数据是个对象则进行对象的遍历,调用defineReactive函数将数据变成响应式的
// 对对象进行响应式处理
function observe(obj) {
if (typeof obj!== 'object' || obj === null) {
return;
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
});
}
- 数据变成响应式
主要采用defineReactive函数,然后调用Object的defineProperty方法对数据进行劫持,使其变成响应式,当数据发生变化的时候,会调用defineReactive函数,然后创建发布者Dep,
在get函数中进行数据收集,当页面使用这个属性,就会产生一个订阅者watcher,然后将这个订阅者watcher放入到发布者dep中。在set函数中进行数据更新,更新页面,一旦修改某个属性,dep就会通知watcher进行更新。
// 定义响应式属性
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
//依赖收集,当页面使用了这个属性,就会产生一个watcher,将其放入dep中
//只收集标签内容,防止log输出时,也加入发布者dep里面
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set(newVal) {
//更新页面,一旦修改某个属性,dep就会通知watcher进行更新
if (val === newVal) {
return;
}
val = newVal;
dep.notify();
}
});
}
2.VUE3基本原理实现
vue3是通过Proxy对数据进行代理,当访问或者设置proxy代理的对象时,会执行相应的getter或者setter函数,从而实现数据的响应式。
创建响应式数据后,当变量数据变化时,会调用相应的proxy对数据进行监听,当数据被读取(get)时进行依赖收集,被修改(set)时触发副作用,副作用函数仅在数据设置(修改)后触发(通过trigger函数)。
- reative:创建响应式对象
- effect:副作用函数,追踪依赖(数据),当依赖变化的时候会重新执行。
- track:在getter中收集依赖,当访问对象属性时,它会将当前激活的副作用函数添加到对应属性的依赖集合中。
- trigger:在setter中触发更新,当对象属性值改变时,它会遍历该属性的依赖集合,并执行其中的所有副作用函数。
// 存储所有依赖
const targetMap = new WeakMap();
// 激活的副作用函数
let activeEffect;
// 依赖收集函数
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
// 触发依赖更新函数
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
// 副作用函数
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
// 响应式函数
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (value!== oldValue) {
trigger(target, key);
}
return result;
}
});
}
// 使用示例
const original = { count: 0 };
const observed = reactive(original);
effect(() => {
console.log('Effect triggered:', observed.count);
});
// 修改数据触发更新
observed.count++;
- 创建一个普通对象
original。 - 使用
reactive函数将original转换为响应式对象observed。 - 使用
effect函数包装一个副作用函数,该函数会打印observed.count的值。在执行副作用函数时,会触发observed.count的get拦截器,从而进行依赖收集。 - 修改
observed.count的值,触发set拦截器,进而调用trigger函数,执行之前收集的副作用函数,打印出新的count值。