温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Vue2响应式系统之分支切换怎么实现

发布时间:2022-04-13 10:24:28 来源:亿速云 阅读:185 作者:iii 栏目:开发技术

本篇内容介绍了“Vue2响应式系统之分支切换怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    场景

    我们考虑一下下边的代码会输出什么。

    import { observe } from "./reactive";
    import Watcher from "./watcher";
    const data = {
        text: "hello, world",
        ok: true,
    };
    observe(data);
    
    const updateComponent = () => {
        console.log("收到", data.ok ? data.text : "not");
    };
    
    new Watcher(updateComponent); // updateComponent 执行一次函数,输出 hello, world
    
    data.ok = false; // updateComponent 执行一次函数,输出 not
    
    data.text = "hello, liang"; // updateComponent 会执行吗?

    我们来一步一步理清:

    observer(data)

    拦截了 中 和 的 ,并且各自初始化了一个 实例,用来保存依赖它们的 对象。datatextokget、setDepWatcher

    Vue2响应式系统之分支切换怎么实现

    new Watcher(updateComponent)

    这一步会执行 函数,执行过程中用到的所有对象属性,会将 收集到相应对象属性中的 中。updateComponentWatcherDep

    Vue2响应式系统之分支切换怎么实现

    当然这里的 其实是同一个,所以用了指向的箭头。Watcher

    data.ok = false

    这一步会触发 ,从而执行 中所有的 ,此时就会执行一次 。setDepWatcherupdateComponent

    执行 就会重新读取 中的属性,触发 ,然后继续收集 。updateComponentdatagetWatcher

    Vue2响应式系统之分支切换怎么实现

    重新执行 函数 的时候:updateComponent

    const updateComponent = () => {
        console.log("收到", data.ok ? data.text : "not");
    };

    因为 的值变为 ,所以就不会触发 的 , 的 就不会变化了。data.okfalsedata.textgettextDep

    而 会继续执行,触发 收集 ,但由于我们 中使用的是数组,此时收集到的两个 其实是同一个,这里是有问题,会导致 重复执行,一会儿我们来解决下。data.okgetWatcherDepWacherupdateComponent

    data.text = "hello, liang"

    执行这句的时候,会触发 的 ,所以会执行一次 。但从代码来看 函数中由于 为 , 对输出没有任何影响,这次执行其实是没有必要的。textsetupdateComponentupdateComponentdata.okfalsedata.text

    之所以执行了,是因为第一次执行 读取了 从而收集了 ,第二次执行 的时候, 虽然没有读到,但之前的 也没有清除掉,所以这一次改变 的时候 依旧会执行。updateComponentdata.textWatcherupdateComponentdata.textWatcherdata.textupdateComponent

    所以我们需要的就是当重新执行 的时候,如果 已经不依赖于某个 了,我们需要将当前 从该 中移除掉。updateComponentWatcherDepWatcherDep

    Vue2响应式系统之分支切换怎么实现

    问题

    总结下来我们需要做两件事情。

    • 去重, 中不要重复收集 。DepWatcher

    • 重置,如果该属性对 中的 已经没有影响了(换句话就是, 中的 已经不会读取到该属性了 ),就将该 从该属性的 中删除。DepWacherWatcherupdateComponentWatcherDep

    去重

    去重的话有两种方案:

    • Dep 中的 数组换为 。subsSet

    • 每个 对象引入 , 对象中记录所有的 的 ,下次重新收集依赖的时候,如果 的 已经存在,就不再收集该 了。DepidWatcherDepidDepidWatcher

    Vue2 源码中采用的是方案 这里我们实现下:2

    Dep 类的话只需要引入 即可。id

    /*************改动***************************/
    let uid = 0;
    /****************************************/
    export default class Dep {
        static target; //当前在执行的函数
        subs; // 依赖的函数
      	id; // Dep 对象标识
        constructor() {
          /**************改动**************************/
            this.id = uid++;
          /****************************************/
            this.subs = []; // 保存所有需要执行的函数
        }
    
        addSub(sub) {
            this.subs.push(sub);
        }
        depend() {
            if (Dep.target) {
                // 委托给 Dep.target 去调用 addSub
                Dep.target.addDep(this);
            }
        }
    
        notify() {
            for (let i = 0, l = this.subs.length; i < l; i++) {
                this.subs[i].update();
            }
        }
    }
    
    Dep.target = null; // 静态变量,全局唯一

    在 中,我们引入 来记录所有的 。Watcherthis.depIdsid

    import Dep from "./dep";
    export default class Watcher {
      constructor(Fn) {
        this.getter = Fn;
        /*************改动***************************/
        this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id
        /****************************************/
        this.get();
      }
    
      /**
         * Evaluate the getter, and re-collect dependencies.
         */
      get() {
        Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher
        let value;
        try {
          value = this.getter.call();
        } catch (e) {
          throw e;
        } finally {
          this.cleanupDeps();
        }
        return value;
      }
    
      /**
         * Add a dependency to this directive.
         */
      addDep(dep) {
        /*************改动***************************/
        const id = dep.id;
        if (!this.depIds.has(id)) {
          dep.addSub(this);
        }
        /****************************************/
    
      }
    
      /**
         * Subscriber interface.
         * Will be called when a dependency changes.
         */
      update() {
        this.run();
      }
    
      /**
         * Scheduler job interface.
         * Will be called by the scheduler.
         */
      run() {
        this.get();
      }
    }

    重置

    同样是两个方案:

    • 全量式移除,保存 所影响的所有 对象,当重新收集 的前,把当前 从记录中的所有 对象中移除。WatcherDepWatcherWatcherDep

    • 增量式移除,重新收集依赖时,用一个新的变量记录所有的 对象,之后再和旧的 对象列表比对,如果新的中没有,旧的中有,就将当前 从该 对象中移除。DepDepWatcherDep

    Vue2 中采用的是方案 ,这里也实现下。2

    首先是 类,我们需要提供一个 方法。DepremoveSub

    import { remove } from "./util";
    /*
    export function remove(arr, item) {
        if (arr.length) {
            const index = arr.indexOf(item);
            if (index > -1) {
                return arr.splice(index, 1);
            }
        }
    }
    */
    let uid = 0;
    
    export default class Dep {
        static target; //当前在执行的函数
        subs; // 依赖的函数
        id; // Dep 对象标识
        constructor() {
            this.id = uid++;
            this.subs = []; // 保存所有需要执行的函数
        }
    		
        addSub(sub) {
            this.subs.push(sub);
        }
      /*************新增************************/
        removeSub(sub) {
            remove(this.subs, sub);
        }
      /****************************************/
        depend() {
            if (Dep.target) {
                // 委托给 Dep.target 去调用 addSub
                Dep.target.addDep(this);
            }
        }
    
        notify() {
            for (let i = 0, l = this.subs.length; i < l; i++) {
                this.subs[i].update();
            }
        }
    }
    
    Dep.target = null; // 静态变量,全局唯一

    然后是 类,我们引入 来保存所有的旧 对象,引入 来保存所有的新 对象。Watcherthis.depsDepthis.newDepsDep

    import Dep from "./dep";
    export default class Watcher {
        constructor(Fn) {
            this.getter = Fn;
            this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id
          	/*************新增************************/
            this.deps = [];
            this.newDeps = []; // 记录新一次的依赖
            this.newDepIds = new Set();
          	/****************************************/
            this.get();
        }
    
        /**
         * Evaluate the getter, and re-collect dependencies.
         */
        get() {
            Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher
            let value;
            try {
                value = this.getter.call();
            } catch (e) {
                throw e;
            } finally {
              	/*************新增************************/
                this.cleanupDeps();
              	/****************************************/
            }
            return value;
        }
    
        /**
         * Add a dependency to this directive.
         */
        addDep(dep) {
            const id = dep.id;
          /*************新增************************/
            // 新的依赖已经存在的话,同样不需要继续保存
            if (!this.newDepIds.has(id)) {
                this.newDepIds.add(id);
                this.newDeps.push(dep);
                if (!this.depIds.has(id)) {
                    dep.addSub(this);
                }
            }
          /****************************************/
        }
    
        /**
         * Clean up for dependency collection.
         */
      	/*************新增************************/
        cleanupDeps() {
            let i = this.deps.length;
            // 比对新旧列表,找到旧列表里有,但新列表里没有,来移除相应 Watcher
            while (i--) {
                const dep = this.deps[i];
                if (!this.newDepIds.has(dep.id)) {
                    dep.removeSub(this);
                }
            }
    
            // 新的列表赋值给旧的,新的列表清空
            let tmp = this.depIds;
            this.depIds = this.newDepIds;
            this.newDepIds = tmp;
            this.newDepIds.clear();
            tmp = this.deps;
            this.deps = this.newDeps;
            this.newDeps = tmp;
            this.newDeps.length = 0;
        }
      	/****************************************/
        /**
         * Subscriber interface.
         * Will be called when a dependency changes.
         */
        update() {
            this.run();
        }
    
        /**
         * Scheduler job interface.
         * Will be called by the scheduler.
         */
        run() {
            this.get();
        }
    }

    测试

    回到开头的代码

    import { observe } from "./reactive";
    import Watcher from "./watcher";
    const data = {
        text: "hello, world",
        ok: true,
    };
    observe(data);
    
    const updateComponent = () => {
        console.log("收到", data.ok ? data.text : "not");
    };
    
    new Watcher(updateComponent); // updateComponent 执行一次函数,输出 hello, world
    
    data.ok = false; // updateComponent 执行一次函数,输出 not
    
    data.text = "hello, liang"; // updateComponent 会执行吗?

    此时 修改的话就不会再执行 了,因为第二次执行的时候,我们把 中 里的 清除了。data.textupdateComponentdata.textDepWatcher

    “Vue2响应式系统之分支切换怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    vue
    AI