提示:描述都寫在代碼的註釋中 在initMixin()的最後執行了以下操做:html
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
複製代碼
function query (el) {
if (typeof el === 'string') {
var selected = document.querySelector(el);
if (!selected) { // 若是存在 el 元素,就直接返回 selected,不然以下
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
);
return document.createElement('div') // 若是不存在el元素,就返回一個空div
}
return selected
} else { // 不然直接返回dom元素
return el
}
}
複製代碼
vm的原型上自己就定義了一個$mount(以下所示),而後經過重寫$mount方法,最後返回的時候,調用這個緩存$mount方法。vue
Vue.prototype.$mount = function ( // 這是最開始定義的$mount方法,在runtime-only版本中
el,
hydrating
) {
//var inBrowser = typeof window !== 'undefined';
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating) // 而後執行 mountComponent方法
};
複製代碼
直接調用的$mount代碼以下node
Vue.prototype.$mount = function() {
el = el && query(el); // 表示若是el存在就執行query(el)方法
if (el === document.body || el === document.documentElement) {
// Vue 不能掛載在 body、html 這樣的根節點上,由於它會替換掉這些元素
process.env.NODE_ENV !== 'production' && warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
// 若是有render方法,就直接 return 並調用原先原型原型上的 $mount 方法
// 若是沒有render方法,就判斷有沒有template
// 若是沒有template就會調用template = getOuterHTML(el)
// 總之,最終仍是會將template轉換成render函數
// 最後再調用 mountComponent 方法
if (!this.$options.render) {
if(this.$options.template){
if (template.charAt(0) === '#') { // 咱們這裏的 charAt(0) 是 '<'
template = idToTemplate(template);
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
}
}
// 而後開始編譯
if (template) {
// 經過compileToFunction 生成 render 函數,渲染vnode的時候會用到
var ref = compileToFunctions();
var render = ref.render;
var render = ref.render;
var staticRenderFns = ref.staticRenderFns; // 靜態 render函數
options.render = render; // 會在渲染 Vnode 的時候用到
options.staticRenderFns = staticRenderFns;
}
return mount.call(this, el, hydrating)
// 最後,調用原先原型上的 $mount 方法掛載。
// 並執行 mountComponent() 方法
// mountComponent 實在 core/instance/lifecycle 中
}
複製代碼
function mountComponent (
vm,
el,
hydrating
) {
debugger
vm.$el = el; // 對 el 進行緩存
if (!vm.$options.render) { // 若是沒有render函數,包括 template 沒有正確的轉換成render函數,就執行 if 語句
vm.$options.render = createEmptyVNode; // createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
// 若是使用了template而不是render函數可是使用的runtime-only版本,就報這個警告
// 若是使用了template 可是不是用的 compile 版本,也會報警告
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
);
} else {
// 若是沒有使用 template 或者 render 函數,就報這個警告
warn(
'Failed to mount component: template or render function not defined.',
vm
);
}
}
}
callHook(vm, 'beforeMount');
var updateComponent;
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
// mark 是 util 工具函數中的perf,這裏先不做深刻研究,主要研究主線。
// 性能埋點相關
// 提供程序的運行情況,
updateComponent = function () {
var name = vm._name;
var id = vm._uid;
var startTag = "vue-perf-start:" + id;
var endTag = "vue-perf-end:" + id;
mark(startTag);
var vnode = vm._render();
mark(endTag);
measure(("vue " + name + " render"), startTag, endTag);
mark(startTag);
vm._update(vnode, hydrating);
mark(endTag);
measure(("vue " + name + " patch"), startTag, endTag);
};
} else {
updateComponent = function () {
// vm._render() 方法渲染出來一個 VNode
// jydrating 跟服務端渲染相關,若是沒有啓用的話,其爲 false
// 當收集好了依賴以後,會經過 Watcher 的 this.getter(vm, vm) 來調用 updateComponent() 方法
vm._update(vm._render(), hydrating); // 而後執行 vm._render()方法
};
}
複製代碼
在這個方法裏有兩個方法須要調用:vm._render() and vm._update(),先調用 _render 方法生成一個vnode,而後將這個vnode傳入到 _update()方法中express
updateComponent = function () {
// vm._render() 方法渲染出來一個 VNode
// jydrating 跟服務端渲染相關,若是沒有啓用的話,其爲 false
// 當收集好了依賴以後,會經過 Watcher 的 this.getter(vm, vm) 來調用 updateComponent() 方法
vm._update(vm._render(), hydrating); // 而後執行 vm._render()方法
};
複製代碼
渲染watcher,Watcher 在這裏起到兩個做用,一個是初始化的時候會執行回調函數,另外一個是當 vm 實例中的監測的數據發生變化的時候執行回調函數緩存
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
bash
var Watcher = function Watcher (
vm,
expOrFn, // 是一個表達式仍是一個 fn
cb, // 回調
options, // 配置
isRenderWatcher // 是不是一個渲染watcher
) {
this.vm = vm;
if (isRenderWatcher) { // 若是是渲染 Watcher
vm._watcher = this; // this 表示 Vue實例, 其中包括了你定義了的數據,也有 $options,還有_data,均可以獲取到你定義的數據
}
vm._watchers.push(this); // 而後將Vue 實例push到 _watchers中, 在initState中 vm._watchers = []
// 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();
// expOrFn: updateComponent = function() { vm._update(vm._render(), hydrating); }
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: '';
// parse expression for getter
if (typeof expOrFn === 'function') { // 判斷expOrFn是不是一個函數
this.getter = expOrFn; // this => Watcher
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = function () {};
process.env.NODE_ENV !== 'production' && warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get(); // this表示Watcher,其原型上定義了get方法
};
複製代碼
在Watcher的構造函數中定義了getter函數:
this.getter = expOrFn
。這個expOrFn 是updateComponent方法,在Watcher.prototype.get()
方法中經過this.getter.call(vm, vm)
來調用updateComponent方法,而後執行vm._update(vm._render, hydrating)
。dom
Watcher.prototype.get = function get () {
// 依賴收集
pushTarget(this);
var value;
var vm = this.vm; // this => Watcher,裏面有當前的Vue實例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
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
複製代碼
到這裏,咱們知道updateComponent方法會完成整個渲染工做,在系列三中,將深刻分析 vm._render()方法以及vm._update()方法。函數