• Vue双向绑定原理(源码解析)getter setter


       Vue双向绑定原理     

         大部分都知道Vue是采用的是对象的get 和set方法来实现数据的双向绑定的过程,本章将讨论他是怎么利用他实现的。

         vue双向绑定其实是采用的观察者模式,get和set方法只是实现观察者模式的切入点,即在我们set的时候向观察者发布消息,执行观察者的操作,get的时候是为实现set能够通知watcher进行相关处理做准备。下面我们来看一下数据初始化的流程;

    数据初始化流程:

           数据在初始化时,会调用方法 defineReactive 为数据绑定dep对象(以备之后使用),在进行挂载时会实例化一个进行页面更新的watcher($watcher).,该watcher调用渲染函数(上图的第5步),会调用模板里涉及到的属性的get方法,即为每个属性的dep对象加载对应的依赖$watcher,同时在$watcher下备份涉及到的dep对象。数据初始化时主要是调用data下的属性的get方法,在数据更新时才会调用属性的set方法,详细可看下面的代码注释。

    /**
     * 采用观察者模式进行数据更新的监听,subject为data下的所有属性以及子孙属性等
     * 
     * subject与watcher是多对多的关系
     * 一个subject,如属性data.A,可能对于处理页面更新的Watcher,也可能对应$watch函数传入的表达式更新的Watcher对象,或者观察计算属性的Watcher对象等
     *  一个更新页面的Watcher会对象data下的所有属性以及子孙属性等
     * 
     * 
     * */
    
        function defineReactive(
            obj,
            key,
            val,
            customSetter,
            shallow
        ) {
            var dep = new Dep();
    
            var property = Object.getOwnPropertyDescriptor(obj, key);
            if (property && property.configurable === false) {
                return
            }
    
            // cater for pre-defined getter/setters
            var getter = property && property.get;
            if (!getter && arguments.length === 2) {
                val = obj[key];
            }
            var setter = property && property.set;
            // 如果 val为对象,创建一个观察者Observe实例ob,先创建一个dep实例,赋值给Observe.dep ,保存实例只val.__ob__属性, 类似val.__ob__ = ob; 
            var childOb = !shallow && observe(val); 
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get: function reactiveGetter() {
                    var value = getter ? getter.call(obj) : val; //如果属性之前就定义getter方法,则执行getter方法,并返回属性值
                    if (Dep.target) { // Dep.target为执行更新的watcher对象
                        dep.depend();  //为该属性添加观察者(订阅者),挂在对应的dep对象的subs:[]属性下,对应的watcher也会存对应的目标关系与watcher.deps与watcher.depIds
                        if (childOb) {
                            //如果是对象,为对象添加该Watcher实例,可用于set, 数组改变,计算属性等操作
                            childOb.dep.depend();  
                            if (Array.isArray(value)) { //如果value是数据 采用递归的方式为为value下的对象成员添加Observe实例属性 __ob__
                                dependArray(value);
                            }
                        }
                    }
                    return value
                },
                set: function reactiveSetter(newVal) {
                    var value = getter ? getter.call(obj) : val;
                    /* eslint-disable no-self-compare */
                    if (newVal === value || (newVal !== newVal && value !== value)) {
                        return
                    }
                    /* eslint-enable no-self-compare */
                    if ("development" !== 'production' && customSetter) {//如果是开发环境,者打印一些警告
                        customSetter();
                    }
                    if (setter) { //如果属性之前就定义了setter方法,则执行setter方法
                        setter.call(obj, newVal);
                    } else {
                        val = newVal;
                    }
                    childOb = !shallow && observe(newVal);
                    dep.notify();  // 数据有改变,通知观察者进行操作
                }
            });
        }

    数据更新流程: 

      这里将讲解数据的更新流程,如果我重设了数据如data.A = 5;它将出发A属性的set方法,该方法会对比当前设置的值与之前的值是否相等,如果不相等,在出发dep.notify()函数,类似触发更新事件,代用更新操作,该方法会去调用dep对象下所有依赖watcher的update方法,该方法会排除重复的watcher,最终采用微任务或者定时的方式去执行watcher对应的run方法(run方法调用了watcher的get方法),之后将会调用渲染函数,如此又将与初始化的过程过程的第五步一致,调用data下的get方法,同时备份watcher至涉及到的dep对象下,将上次执行备份中涉及到的dep而本次执行没涉及到dep下的依赖当前watcher删除,想见 Watcher.prototype.cleanupDeps函数注释

         在初始化以及数据更新工程,都将调用watcher.get方法,为什么执行该方法就能保证以上功能正常执行呢

         watcher.get方法会去调用watcher实例的getter,如果是进行页面更新与渲染的watcher,getter方法这是去执行render函数并将render函数生成的vnode进行渲染。再执行render函数时会涉及到调用模板里的data属性。从而触发属性的get方法。那又是采用什么方式保证属性对应的dep是触发对应的watcher?见Watcher.prototype.get注释

     Watcher.prototype.get = function get() {
            pushTarget(this);  //该函数的作用是,将Dep.target的值推入堆栈中,并将当前的Watcher实例赋值给Dep.target 
            var value;
            var vm = this.vm;
            try {
                value = this.getter.call(vm, vm);
            } catch (e) {
                if (this.user) {
                    handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
                } else {
                    throw e
                }
            } finally {
                // "touch" every property so they are all tracked as
                // dependencies for deep watching
           //如果是深度监听watching,进行递归加载监听观察者
    if (this.deep) { traverse(value); } popTarget(); //将堆栈中栈顶的值弹出堆栈并赋值给 Dep.target this.cleanupDeps();//与上一次的watcher对象依赖下的dep数据对比,清除没有使用的dep对象 } return value };

       在以上代码,在初始化的时候执行watcher.get,其中会调用pushTarget(this),将当前的值赋给Dep.target ,即相当于赋给了一个全局变量。之后将执行watcher的this.getter方法,即render函数,在执行这个函数期间Dep.target一直保持为该watcher,以此保证属性下的dep对象都是对应的watcher,如果在执行render期间有其他watcher执行打断当前的执行,也会在执行其他watcher之后恢复该值,执行完this.getter执行popTarget();将从堆栈中取出执行上下文的watcher值并赋给Dep.target。

           下面的代码这是用于保证watcher对象与dep对象的正确依赖关系。同事备份执行watcher依赖的dep对象。

           由于dep对象用于通知watcher执行相关的操作,所以他们之间会有一个多对多的对应关系。即dep需要通知哪些watcher,watcher又注册到哪些dep对象中,下面的就是保证每次执行之后这个依赖关系都是正确的

     /**
         * 备份本次更新的deps对象和depIds,同时将上次Watcher更新依赖的dep实例,但本次Watcher更新更新不依赖的dep实例下依赖Watcher备份数据删除该Watcher实例
         */
        Watcher.prototype.cleanupDeps = function cleanupDeps() {
            var this$1 = this;
    
            var i = this.deps.length;
            while (i--) {
                // 获取上一次触发Wactcher更新的dep对象--dep
                var dep = this$1.deps[i]; 
                // 如果该对象dep在新的watcher依赖下没有则清除dep对象下依赖的该Watcher对象
                if (!this$1.newDepIds.has(dep.id)) { 
                    dep.removeSub(this$1);
                }
            }
            var tmp = this.depIds;  //将上次更新的depIds缓存
            this.depIds = this.newDepIds;  //备份本次更新的Watcher对应的deps对象的id
            this.newDepIds = tmp; //将上次更新的depId赋值给newDepIds,并在下一行进行清空
            this.newDepIds.clear(); //将newDepIds清空,以备下次更新使用
            tmp = this.deps;    //deps 对象的备份与 depIds逻辑一致
            this.deps = this.newDeps;  //备份本次更新的Watcher对应的deps对象
            this.newDeps = tmp;
            this.newDeps.length = 0;  //将newDeps清空,以备下次更新使用
        }; 
  • 相关阅读:
    PostgreSQL数据损坏与checksum
    go get命令无响应解决方法
    vscode离线安装插件
    windows搭建Go语言环境
    centos7安装zabbix 5.0
    搭建人生重开模拟器
    博客园增加打赏
    记一次halo报错
    VM operation inconsistent with current state
    nextcloud安装onlyoffice
  • 原文地址:https://www.cnblogs.com/riona/p/10064542.html
Copyright © 2020-2023  润新知