vue - 響應式原理梳理(二)

vue實例 初始化 完成之後,接下來就要進行 掛載html

vue實例掛載,即爲將vue實例對應的 template模板,渲染成 Dom節點vue

原型方法 - $mount

  經過原型方法 $mount方法 來掛載vue實例。 node

  掛載vue實例時,經歷一下幾個重要步驟:express

  • 生成render函數;
  • 生成vue實例的監聽器watcher;
  • 執行render函數,將vue實例的template模板轉化爲VNode節點樹;
  • 執行update函數,將VNode節點樹轉化爲dom節點樹;
// 掛載Vue實例
    Vue$3.prototype.$mount = function(el, hydrating) {
        // el爲dom元素對應的選擇器表達式,根據選擇器表達式,獲取dom元素
        el = el && query(el);

        ...
        
        // this->Vue實例,或者是組件實例對象
        var options = this.$options;
        
        // 解析模板,將模板轉換爲render渲染函數
        if(!options.render) {
            // 通常是組件實例的構造函數的options中會直接有template這個屬性
            var template = options.template;
            if(template) {
                if(typeof template === 'string') {
                    if(template.charAt(0) === '#') {
                        template = idToTemplate(template);
                        /* istanbul ignore if */
                        if("development" !== 'production' && !template) {
                            warn(
                                ("Template element not found or is empty: " + (options.template)),
                                this
                            );
                        }
                    }
                } else if(template.nodeType) {
                    template = template.innerHTML;
                } else {
                    {
                        warn('invalid template option:' + template, this);
                    }
                    return this
                }
            } else if(el) {
                // 獲取dom節點的outerHTML
                template = getOuterHTML(el);
            }
            // template,模板字符串
            if(template) {
                /* istanbul ignore if */
                if("development" !== 'production' && config.performance && mark) {
                    mark('compile');
                }
                
                // 將html模板字符串編譯爲渲染函數
                var ref = compileToFunctions(template, {
                    // 換行符
                    shouldDecodeNewlines : shouldDecodeNewlines,
                    // 分割符
                    delimiters : options.delimiters,
                    // 註釋
                    comments : options.comments
                }, this);
                // 獲取渲染函數
                var render = ref.render;
                // 獲取靜態渲染函數
                var staticRenderFns = ref.staticRenderFns;
                // 將渲染函數添加到Vue實例對象的配置項options中
                options.render = render;
                // 將靜態渲染函數添加到Vue實例對象的配置項options中
                options.staticRenderFns = staticRenderFns;

                /* istanbul ignore if */
                if("development" !== 'production' && config.performance && mark) {
                    mark('compile end');
                    measure(((this._name) + " compile"), 'compile', 'compile end');
                }
            }
        }
        return mountComponent.call(this, el, hydrating)
    };
    
    
    // 掛載vue實例
    function mountComponent(vm, el, hydrating) {
        vm.$el = el;
        
        ...
        
        updateComponent = function() {
            vm._update(vm._render(), hydrating);
        };

        // 給Vue實例或者是組件實例建立一個監聽器, 監聽updateComponent方法
        vm._watcher = new Watcher(vm, updateComponent, noop);
        
        ...
        
        return vm;
    }

watcher 對象在構建過程當中,會做爲觀察者模式中的 Observer,會被添加到 響應式屬性的dep對象的依賴列表 中。 app

  當響應式屬性發生變化時,會通知依賴列表中的watcher對象進行更新。 dom

  此時,watcher 對象執行 updateComponent 方法,從新渲染 dom節點函數

watcher

  在 掛載vue實例 時, 會爲 vue實例 構建一個 監聽者watcheroop

// vm => 當前監聽者對應的vue實例
 // expOfFn => 須要監聽的表達式
 // cb => 監聽回調方法
 // options => 選項,好比deep、immediate
 // vm.$watch('message', function(newValue) {...})
 
 var Watcher = function Watcher(vm, expOrFn, cb,  options) {
        this.vm = vm;

        vm._watchers.push(this);
        
        ...
        
        // 監聽器訂閱的Dep對象實例
        this.deps = [];
        this.newDeps = [];
        // 存儲監聽器已經訂閱的Dep對象實例的id,防止重複訂閱。
        // 每個Dep對象實例都有一個ID
        this.depIds = new _Set();
        this.newDepIds = new _Set();
        
        // 監聽器監聽的表達式
        this.expression = expOrFn.toString();
        // 初始化getter,getter用於收集依賴關係
        if(typeof expOrFn === 'function') {
            // expOfFn 爲 函數
            // 對應:給vue實例創建watcher
            this.getter = expOrFn;
        } else {
            // epOfFn 爲 字符串
            // 對應:在vue實例中創建監聽
            // 好比 vm.$watch('message', function() {...})
            this.getter = parsePath(expOrFn);
        }

        this.value = this.lazy
            ? undefined
            : this.get();
    };
    
    // 執行watcher的getter方法,用於收集響應式屬性dep對象 和 watcher的依賴關係
    // 在vue實例掛載過程當中, getter = updateComponent
    Watcher.prototype.get = function get() {
        // 將Dep.target設置爲當前Watcher對象實例
        pushTarget(this);
        var value;
        var vm = this.vm;
        
        ...
        
        value = this.getter.call(vm, vm);
        
        ...
        
        return value
    };
    
    // watcher 更新
    Watcher.prototype.update = function update() {
        ...
        
        this.get()
        
        ...
    };
    
    // 將watcher添加到dep屬性的依賴列表中
    Watcher.prototype.addDep = function addDep(dep) {
        var id = dep.id;
        if(!this.newDepIds.has(id)) {
            this.newDepIds.add(id);
            this.newDeps.push(dep);
            if(!this.depIds.has(id)) {
                dep.addSub(this);
            }
        }
    };

  在掛載vue實例時,watcher對象會在構建過程當中會執行 updateComponent 方法。this

  執行 updateComponent 方法分爲兩個過程:spa

  • 執行 render 方法,返回 VNode節點樹
  • 執行 update 方法,將 VNode節點樹 渲染爲 dom節點樹

  執行 render 方法時,會觸發響應式屬性的 getter 方法,將 watcher 添加到 dep對象的依賴列表中

render

render 方法是 vue實例 在掛載時由 template 編譯成的一個 渲染函數

tempalte:
    <div id="app">
        {{message}}
    </div>
    
render:
    // 執行render, 須要讀取響應式屬性message,觸發message的getter方法
    (function anonymous() {
        with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(message))])}
    })
    // _s, 將this.message轉化爲字符串
    // _v, 生成文本節點對應的VNode
    // _c, 生成'div'元素節點對應的Vnode

render 函數返回 VNode節點 , 用於 渲染Dom節點

  在執行過程當中,若是須要讀取 響應式屬性,則會觸發 響應式屬性getter

  在 getter 方法中, 將 watcher 對象添加到 響應式屬性dep對象的依賴列表 中。

修改響應式屬性

修改 響應式屬性時,會觸發響應式屬性的 setter 方法。此時,響應式屬性的 dep對象執行 notify 方法,遍歷本身的 依賴列表subs, 逐個通知subs中的 watcher 去更新。

watcher 對象執行 update 方法,從新調用 render 方法生成 Vnode節點樹,而後 對比新舊Vnode節點樹 的不一樣,更新dom樹

總結

響應式屬性 的原理:

clipboard.png

相關文章
相關標籤/搜索