VUE的响应式原理(小白都能看得懂的源码解析)

283 阅读4分钟

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对象。

  1. 遍历劫持

主要采用observer函数对数据进行遍历劫持,如果数据是个对象则进行对象的遍历,调用defineReactive函数将数据变成响应式的

// 对对象进行响应式处理
function observe(obj) {
    if (typeof obj!== 'object' || obj === null) {
        return;
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key]);
    });
}
  1. 数据变成响应式

主要采用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++;    
  1. 创建一个普通对象 original
  2. 使用 reactive 函数将 original 转换为响应式对象 observed
  3. 使用 effect 函数包装一个副作用函数,该函数会打印 observed.count 的值。在执行副作用函数时,会触发 observed.count 的 get 拦截器,从而进行依赖收集。
  4. 修改 observed.count 的值,触发 set 拦截器,进而调用 trigger 函数,执行之前收集的副作用函数,打印出新的 count 值。