vue3响应式原理

本文深入解析Vue.js的响应式实现,包括effect、track、trigger等核心函数,展示了如何通过proxy和WeakMap实现数据劫持和依赖收集,以及如何在属性变更时触发更新。通过对源码的分析,帮助理解Vue.js的数据响应机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下代码是使用rollup打包成Vue.js的
./dist/Vue.js

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VueReactivity = {}));
}(this, (function (exports) { 'use strict';

    function isObject(val) {
        return typeof val == 'object' && val !== null;
    }
    function hasChanged(oldValue, newValue) {
        return oldValue !== newValue;
    }
    let isArray = Array.isArray;
    const extend = Object.assign;
    //判断key是否是数字,key肯定是字符串形式的,arr[key]中key也是数字型的字符串
    const isIntegerKey = (key) => {
        return parseInt(key) + '' === key;
    };
    //判断该对象上是否有该属性
    const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);

    function effect(fn, options = {}) {
        const effect = createReactiveEffect(fn, options);
        if (!options.lazy) {
            effect();
        }
        return effect; //返回响应式的effect
    }
    let activeEffect;
    const effectStack = [];
    let id = 0;
    //当用户取值的时候需要将activeEffect 和属性做关联
    //当用户更改的时候 要通过属性找到effect重新执行
    function createReactiveEffect(fn, options) {
        const effect = function reactiveEffect() {
            try {
                effectStack.push(effect);
                activeEffect = effect;
                return fn(); //会取值
            }
            finally {
                effectStack.pop();
                activeEffect = effectStack[effectStack.length - 1];
            }
        };
        effect.id = id++; //构建的是一个id
        effect.__isEffect = true;
        effect.options = options;
        effect.deps = []; //effect用来收集依赖了哪些属性
        return effect;
    }
    //依赖收集,目的是为了让effect和属性关联起来
    const targetMap = new WeakMap;
    function track(target, type, key) {
        if (activeEffect == undefined) {
            return; //用户只是取了值,而且这个值不是在effect中使用的,什么都不用收集
        }
        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()));
        }
        if (!dep.has(activeEffect)) {
            dep.add(activeEffect);
        }
        //console.log(targetMap)
    }
    function trigger(target, type, key, newValue, oldValue) {
        //去映射表里找到属性对应的 effect, 让它重新执行
        const depsMap = targetMap.get(target);
        if (!depsMap)
            return;
        const effectsSet = new Set();
        const add = (effectsAdd) => {
            if (effectsAdd) { //有可能只是改了属性,然而这个属性之前没有在effect中使用,所以没有将该属性和effect关联起来
                effectsAdd.forEach(effect => effectsSet.add(effect));
            }
        };
        //1.如果更改的数组长度 小于依赖收集的长度 要触发重新渲染
        //2.如果调用了push方法 或者其它新增数组的方法(必须能改变长度的方法),也要触发更新
        if (key === 'length' && isArray(target)) { //如果是数组,你改了length
            depsMap.forEach((dep, key) => {
                console.log(dep, key); //我对index为2的这一项收集了effect
                if (key >= newValue || key === 'length') {
                    add(dep); //更改的数组长度 小于等于收集到的属性的值
                }
            });
        }
        else {
            add(depsMap.get(key));
            // add(depsMap.get(key));
            // add(depsMap.get(key));
            switch (type) {
                case 'add':
                    if (isArray(target) && isIntegerKey(key)) {
                        add(depsMap.get('length')); //增加属性 需要触发length的依赖收集
                    }
            }
        }
        effectsSet.forEach((effect) => effect());
        console.log('me');
    }

    function createGetter(isReadonly = false, shallow = false) {
        /**
         * target 是原对象
         * key 是取什么属性
         * receiver 代理对象
         */
        return function get(target, key, receiver) {
            //return target[key]
            //return receiver[key]这样取值会造成无限递归
            //Refelct 就是要后续慢慢替换掉Object对象,一般使用proxy 会配合Reflect
            const res = Reflect.get(target, key, receiver); //Reflect.ownKey Reflect.definedProperty
            //Reflect.ownKey Reflect.definedProperty(Object中的很多方法移植到了Relfect)
            //更新视图的方法是在set里做的,所以对于Readonly来说不需要收集依赖
            if (!isReadonly) {
                //console.log('收集当前属性,如果这个属性变化了,稍后可能要更新视图',key);
                track(target, 'get', key);
            }
            //如果是浅的,浅的不需要递归代理
            if (shallow) {
                return res;
            }
            //懒递归 当我们取值的时候才去做递归代理 这样做的原因是proxy只代理第一层
            if (isObject(res)) {
                return isReadonly ? readonly(res) : reactive(res);
            }
            return res;
        };
    }
    //readonly不能set
    function createSetter(shallow = false) {
        //针对数组而言 如果调用push方法,就会产生两次触发 
        //1.给数组新增了一项,同时也更改了长度
        //2.因为更改了长度再次触发set (第二次的触发是无意义的)
        return function set(target, key, value, receiver) {
            const oldValue = target[key]; //获取旧值
            //target[key]=value;//如果设置失败 没有返回值
            //console.log('用户设置值了,我要去更新了')
            //假设有一个属性不能被修改 target[key]=value,不会报错;但是通过Reflect.set 会返回false
            //设置属性,可能以前有,还有可能以前没有 (新增和修改)
            //如何判断数组是新增还是修改
            //这里有五种情况:obj.新增属性 obj.存在属性 arr.push(x)=>数组添加数据,然后又改变了长度 arr[i]=x 
            let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
            //这个不能放在上面 如果放在上面 通过Reflect.set后,值肯定是有的,hasOwn(target,key)一定为true
            const res = Reflect.set(target, key, value, receiver);
            if (!hadKey) {
                //console.log('新增')
                trigger(target, 'add', key, value);
            }
            else if (hasChanged(oldValue, value)) {
                //console.log('修改')
                trigger(target, 'set', key, value);
            }
            return res;
        };
    }
    const get = createGetter(); //不是只读的也不是浅的
    const shallowGet = createGetter(false, true);
    const readonlyGet = createGetter(true);
    const shallowReadonlyGet = createGetter(true, true);
    const set = createSetter();
    const shallowSet = createSetter(true);
    const mutableHandlers = {
        get,
        set
    };
    const shallowreactiveHandlers = {
        get: shallowGet,
        set: shallowSet
    };
    let readonlySet = {
        set(target, key) {
            console.warn(`cannot set ${JSON.stringify(target)} on key ${key} falied`);
        }
    };
    //readonly没有set
    const readonlyHandlers = extend({
        get: readonlyGet
    }, readonlySet);
    const shallowReadonlyHandlers = extend({
        get: shallowReadonlyGet
    }, readonlySet);

    //是否是浅的,默认是深度
    //是否是只读的 默认不是只读的
    function reactive(target) {
        return createReactiveObject(target, false, mutableHandlers);
    }
    function shallowReactive(target) {
        return createReactiveObject(target, false, shallowreactiveHandlers);
    }
    function readonly(target) {
        return createReactiveObject(target, true, readonlyHandlers);
    }
    function shallowReadonly(target) {
        return createReactiveObject(target, true, shallowReadonlyHandlers);
    }
    /**
     *
     * @param target 创建代理的目标
     * @param isReadonly 当前是不是只读的
     * @param baseHandler 针对不同的方式创建不同的代理对象
     */
    //weakMap(key只能是对象) map(key可以是其他类型)
    const reactiveMap = new WeakMap(); //目的是添加缓存
    const readonlyMap = new WeakMap();
    function createReactiveObject(target, isReadonly, baseHandler) {
        if (!isObject(target)) {
            return target;
        }
        const proxyMap = isReadonly ? readonlyMap : reactiveMap;
        const existProxy = proxyMap.get(target);
        if (existProxy) {
            return existProxy; //如果已经代理过了,那就直接把上次的代理返回就可以的
        }
        //如果是对象 就做一个代理 new proxy
        const proxy = new Proxy(target, baseHandler);
        proxyMap.set(target, proxy);
        return proxy;
    }
    //数组,对象是如何劫持 effect的实现 ref的实现。。。

    exports.effect = effect;
    exports.reactive = reactive;
    exports.readonly = readonly;
    exports.shallowReactive = shallowReactive;
    exports.shallowReadonly = shallowReadonly;

    Object.defineProperty(exports, '__esModule', { value: true });

})));

./index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <script src="./dist/Vue.js"></script>
    <script>
        let {reactive,shallowReactive,readonly,shallowReadonly,effect}=VueReactivity;
        let school={name:'zf',age:12,address:{num:517},arr:[1,2,3]}
        let proxy=reactive(school)
        let proxy2=reactive({name:'zf'})
        // effect(()=>{
        //     //默认这个函数会执行一次,执行的时候应该把用到的属性和这个effect关联起来
        //     console.log(proxy.name)
        //     effect(()=>{
        //         console.log(proxy.age)
        //     })
        // })
        // effect(()=>{
        //     console.log(proxy.name)
        //     // console.log(proxy2.name)
        // })
        effect(()=>{
            //当你调用stringfy的时候 会访问数组中每一个属性,包括length;
            // console.log(JSON.stringify(proxy.arr))
            console.log(proxy.arr[2])
        })
        //下次更新属性的时候,会再次执行这个effect
        // proxy.arr.push(100)
        proxy.arr.length=2
        //1.通过索引更新数组
        //2.可以通过length来修改
        //3.去length,新增
        // proxy.arr[1]=5
        // setTimeout(() => {
        //     proxy.name='zf1'
        //     proxy.name='zf2'
        //     proxy.name='zf3'
        //     proxy.name='zf4'
        //     proxy.name='zf5'
        //     proxy.name='zf6'
        //     proxy.name='zf7'
        // }, 1000);
        
    </script>
</body>
</html>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值