zoukankan      html  css  js  c++  java
  • Vue3响应式的简单实现

    vue3与vue2响应式的区别

    1.vue2响应式

    • 数据在data中注册,编译时直接将data中的所有数据绑定监听
    • 利用Object.defineProperyty()监听数据的get和set
    • 用Observe,Dep,Watcher三个类实现依赖收集

    缺点:对于在html中未使用的数据也设置了监听,需要对每一个基本数据类型都要设置劫持,defineProperty监听不到数组/对象内部变化,同时多次调用observe函数进行递归,性能不高。

    2.vue3响应式

    • 利用reactive注册响应式对象,对函数返回值操作
    • 利用Proxy劫持数据的get,set,deleteProperty,has,own
    • 利用WeakMap,Map,Set来实现依赖收集

    缺点:使用大量ES6新增特性,旧版本浏览器兼容性差。

    Proxy与Reflect

    Proxy和Reflect是ES6新增的两个类,Proxy相比Object.defineProperty更加好用,解决了后者不能监听数组改变的缺点,并且还支持劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都远强于Object.defineProperty,Reflect的作用是可以拿到Object内部的方法,并且在操作对象出错时返回false不会报错。

    实现

    主要分为两大步,设置响应式对象和依赖收集(发布订阅):
    1.设置响应式对象
    首先创建Proxy,传入将要监听的对象,然后通过handler设置对象的监听,通过get等函数的形参对数据进行劫持处理,然后创建两个WeakMap实例toProxy,toRow来记录当前对象的代理状态,防止重复代理,在set函数中,通过判断属性的类别(新增属性/修改属性)来减少不必要的操作。

    /* ----------------响应式对象---------------- */
    function reactive(target) {
        /* 创建响应式对象 */
        return createReactiveObject(target);
    }
    /* 防止重复设置代理(target,observer) */
    let toProxy = new WeakMap();
    /* 防止重复被代理(observer,target) */
    let toRow = new WeakMap();
    /* 设置响应监听 */
    function createReactiveObject(target) {
        /* 非对象或被代理过则直接返回 */
        if (!isObject(target) || toRow.has(target)) return target;
        /* 已经有代理则直接返回 */
        let proxy = toProxy.get(target);
        if (proxy) {
            return proxy;
        }
        /* 监听 */
        let handler = {
            get(target, key) {
                console.log(`get---key(${key})`);
                let res = Reflect.get(target, key);
                /* 添加追踪 */
                track(target, key);
                /* 如果是对象则继续往下设置响应 */
                return isObject(res) ? reactive(res) : res;
            },/* 获取属性 */
            set(target, key, val, receiver) {
                console.log(`set---key(${key})`);
                /* 判断是否为新增属性 */
                let hasKey = hasOwn(target, key);
                /* 存储旧值用于比对 */
                let oldVal = target[key];
                let res = Reflect.set(target, key, val, receiver);
                if (!hasKey) {
                    console.log(`新增属性---key(${key})`);
                    /* 调用追踪器,绑定新增属性 */
                    track(target, key);
                    /* 调用触发器,更改视图 */
                    trigger(target, key);
                } else if (val !== oldVal) {
                    console.log(`修改属性---key(${key})`);
                    trigger(target, key);
                }
                return res;
            },/* 修改属性 */
            deleteProperty(target, key) {
                console.log(`delete---key(${key})`);
                let res = Reflect.deleteProperty(target, key);
                return res;
            }/* 删除属性 */
        }
        /* 创建代理 */
        let observer = new Proxy(target, handler);
        /* 记录与target的联系 */
        toProxy.set(target, observer);
        toRow.set(observer, target);
        return observer;
    }
    

    2.收集依赖(发布订阅)
    vue3中主要通过嵌套的方式实现,原理如下图:
    vue3响应式
    每次向effect函数传入一个fun----console.log(person.name)后,会先执行一遍run函数,将effect推入栈中,然后执行fun,在执行fun的过程中,会读取person对象,进而触发get函数

    /* 事件栈 */
    let effectStack = [];
    /* ----effect函数---- */
    function effect(fun) {
        /* 将fun压入栈 */
        let effect = createReactiveEffect(fun);
        /* 初始化执行一次 */
        effect();//实际上是运行run
    }
    function createReactiveEffect(fun) {
        /* 创建响应式effect */
        let effect = function () {
            return run(effect, fun);
        }
        return effect;
    }
    function run(effect, fun) {
        /* 防止报错导致栈内元素无法弹出 */
        try {
            effectStack.push(effect);
            fun();
        } finally {
            effectStack.pop();
        }
    }
    

    get函数调用追踪器track并传入(person,name),在track中先获取栈顶元素,也就是刚刚触发的fun,假设当前targetsMap是空的,那么此时将会创建一个新的映射target->new Map(),此时depsMap必然也要创建一个新的映射,把key映射到new Set(),然后向key对应的deps中放入effect,此时,name和fun函数之间的绑定已经实现,执行完后effectStack将会把fun函数弹出,防止越堆越多。

    /* 目标Map */
    let targetsMap = new WeakMap();
    /* ----追踪器---- */
    function track(target, key) {
        /* 获取触发track的事件 */
        let effect = effectStack[effectStack.length - 1];
        if (effect) {
            /* 获取以target作为标识的depsMap */
            let depsMap = targetsMap.get(target);
            if (!depsMap) {
                /* 如果不存在就创建一个新Map */
                targetsMap.set(target, depsMap = new Map());
            }
            /* 获取以key为标识的deps */
            let deps = depsMap.get(key);
            if (!deps) {
                depsMap.set(key, deps = new Set());
            }
            /* 向deps中加入事件 */
            if (!deps.has(effect)) {
                deps.add(effect);
            }
        }
    }
    

    接下来是触发的过程,当每次进行类似person.name='lisi’这样的改值操作时,就会触发响应的set函数,set函数对比属性的新旧值后调用trigger函数将(person,name)传入,trigger根据两个传入值结合targetsMap->depsMap->deps的顺序找到name对应的事件数组,然后执行所有事件达到响应更新的目的,至此,简化版的vue3响应机制就实现了。

    /* ----触发器---- */
    function trigger(target, key) {
        /* 获取depsMap */
        let depsMap = targetsMap.get(target);
        if (depsMap) {
            /* 获取deps */
            let deps = depsMap.get(key);
            /* 执行deps数组中所有的事件 */
            deps.forEach(effect => {
                effect();
            });
        }
    }
    

    代码:https://gitee.com/aeipyuan/simple_implementation_of_vue3
    以上内容均为我个人理解,如有错误,欢迎指正。

  • 相关阅读:
    总结Spring Set注入方式及对property标签的理解
    spring基于Annotation装配Bean时在bean.xml中添加<context:component-scan>标签报错
    java web(struts2)+python+mysql 的简单实践
    罗辑思维--怎样成为一个高手
    教练助手
    小组作业(第五组)
    个人开发总结
    第五组小组作业
    个人作业
    小组总结
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12726202.html
Copyright © 2011-2022 走看看