1. 程式人生 > >vue 函式配置項watch以及函式 $watch 原始碼分享

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
        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) {
                traverse(value);
            }
            popTarget(); //將之前的watcher賦給Dep.target
            this.cleanupDeps();
        }
        return value
    };