vue實例 初始化 完成之後,接下來就要進行 掛載。 html
vue實例掛載,即爲將vue實例對應的 template模板,渲染成 Dom節點。vue
經過原型方法 $mount方法 來掛載vue實例。 node
掛載vue實例時,經歷一下幾個重要步驟:express
// 掛載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節點。函數
在 掛載vue實例 時, 會爲 vue實例 構建一個 監聽者watcher。oop
// 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 方法時,會觸發響應式屬性的 getter 方法,將 watcher 添加到 dep對象的依賴列表中。
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樹。
響應式屬性 的原理: