zoukankan      html  css  js  c++  java
  • vue 函数配置项watch以及函数 $watch 源码分享

    Vue双向榜单的原理
     
     
    大家都知道Vue采用的是MVVM的设计模式,采用数据驱动实现双向绑定,不明白双向绑定原理的需要先补充双向绑定的知识,在watch的处理中将运用到Vue的双向榜单原理,所以再次回顾一下:
    Vue的数据通过Object.defineProperty设置对象的get和set实现对象属性的获取,vue的data下的数据对应唯一 一个dep对象,dep对象会存储改属性对应的watcher,在获取数据(get)的时候为相关属性添加具有对应处理函数的watcher,在设置属性的时候,触发def对象下watcher执行相关的逻辑
     
     
        // 为data的的所有属性添加getter 和 setter
        function defineReactive( obj,key,val,customSetter,shallow
        ) {
            //
            var dep = new Dep();
    
            /*....省略部分....*/
            var childOb = !shallow && observe(val);  //为对象添加备份依赖dep,val在此采用记忆模式进行存储
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get: function reactiveGetter() {
                    var value = getter ? getter.call(obj) : val;
                    if (Dep.target) {
                        dep.depend();  // 
                        if (childOb) {
                            childOb.dep.depend(); //依赖dep 添加watcher 用于set ,array改变等使用
                            if (Array.isArray(value)) {
                                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.call(obj, newVal);
                    } else {
                        val = newVal;
                    }
                    childOb = !shallow && observe(newVal);
                    dep.notify();//有改变触发watcher进行更新
                }
            });
        }
    

      

    在vue进行实例化的时候,将调用 initWatch(vm, opts.watch);进行初始化watch的初始化,该函数最终将调用 vm.$watch(expOrFn, handler, options) 进行watch的配置,下面我们将讲解 vm.$watch方法
     
     
     Vue.prototype.$watch = function (
                expOrFn,
                cb,
                options
            ) {
                var vm = this;
                if (isPlainObject(cb)) {
                    return createWatcher(vm, expOrFn, cb, options)
                }
                options = options || {};
                options.user = true;
                //为需要观察的 expOrFn 添加watcher ,expOrFn的值有改变时执行cb,
                //在watcher的实例化的过程中会对expOrFn进行解析,并为expOrFn涉及到的data数据下的def添加该watcher
                var watcher = new Watcher(vm, expOrFn, cb, options);
    
                //immediate==true 立即执行watch handler
                if (options.immediate) {  
                    cb.call(vm, watcher.value);
                }
    
                //取消观察函数
                return function unwatchFn() {
                    watcher.teardown();
                }
            };
    

      

    来看看实例化watcher的过程中(只分享是观察函数中的实例的watcher)
     
     
     
    var Watcher = function Watcher(
            vm,
            expOrFn,
            cb,
            options,
            isRenderWatcher
        ) {
            this.vm = vm;
            if (isRenderWatcher) {
                vm._watcher = this;
            }
            vm._watchers.push(this);
            // options
            if (options) {
                this.deep = !!options.deep; //是否观察对象内部值的变化
                this.user = !!options.user;
                this.lazy = !!options.lazy;
                this.sync = !!options.sync;
            } else {
                this.deep = this.user = this.lazy = this.sync = false;
            }
            this.cb = cb; // 观察属性改变时执行的函数
            this.id = ++uid$1; // uid for batching
            this.active = true;
            this.dirty = this.lazy; // for lazy watchers
            this.deps = [];
            this.newDeps = [];
            this.depIds = new _Set();
            this.newDepIds = new _Set();
            this.expression = expOrFn.toString();
            // parse expression for getter
            if (typeof expOrFn === 'function') {
                this.getter = expOrFn;
            } else {
                // 将需要观察的数据:string | Function | Object | Array等进行解析  如:a.b.c, 并返回访问该表达式的函数  
                this.getter = parsePath(expOrFn);  
                if (!this.getter) {
                    this.getter = function () { };
                    "development" !== 'production' && warn(
                        "Failed watching path: \"" + expOrFn + "\" " +
                        'Watcher only accepts simple dot-delimited paths. ' +
                        'For full control, use a function instead.',
                        vm
                    );
                }
            }
            // this.get()将访问需要观察的数据 
            this.value = this.lazy
                ? undefined
                : this.get(); 
        };
    
        /**
         * Evaluate the getter, and re-collect dependencies.
         */
        Watcher.prototype.get = function get() {
            //this为$watch方法中实例化的watcher
            pushTarget(this);讲this赋给Dep.target并缓存之前的watcher
            var value;
            var vm = this.vm;
            try {
                //访问需要观察的数据,在获取数据的getter中执行dep.depend();将$watch方法中实例化的watcher添加到对应数据下的dep中
                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
                if (this.deep) {// 如果是进行深度观察,在此依次访问对象下的属性,以便将该watcher实例添加到对应属性的dep中
                    traverse(value);
                }
                popTarget(); //将之前的watcher赋给Dep.target
                this.cleanupDeps();
            }
            return value
        };

     观察的对象改变时将执行该方法
    Watcher.prototype.run = function run() { /*....省略部分....*/     var value = this.get(); //重新获取info的值     var oldValue = this.value; //保存老的值     this.value = value;     this.cb.call(this.vm, value, oldValue); //执行watch的回调 /*....省略部分....*/   };

      

      以上代码在watcher实例化的时候执行  this.getter = parsePath(expOrFn); 返回一个访问该属性的函数,参数为被访问的对象  如vm.$watch("info",function(new, old){console.log("watch success")});, this.getter =function(obj){return obj.info};,在执行watcher的get方法中,将执行value = this.getter.call(vm, vm);,触发属性的get方法,添加该watcher至info属性对应的def对象中,如果需要深度监听,将执行traverse(value),依次访问info(假设info只对象)对象下的属性,如果info的属性还有是对象的属性,将进行递归访问,以达到info以及info下所有的属性的def对象都会添加该watcher实例。

         当我们执行vm.info="change"时,将出发info的set方法,执行dep.notify();出发info所依赖的watcher执行watcher的run方法,即实现监听

     举例

  • 相关阅读:
    ios -为什么用WKWebView加载相同的html文本,有时展示的内容却不一样。
    weex
    [Objective-C 面试简要笔记]
    [iOS 基于CoreBluetooth的蓝牙4.0通讯]
    [SVN Mac自带SVN结合新浪SAE进行代码管理]
    [SVN Mac的SVN使用]
    [iOS dispatch_once创建单例]
    [iOS UI设计笔记整理汇总]
    [iOS 视频流开发-获得视频帧处理]
    [iOS OpenCV错误解决]
  • 原文地址:https://www.cnblogs.com/riona/p/10000668.html
Copyright © 2011-2022 走看看