此階段主要是初始化Vue實例的屬性computed、provide、inject和watch、事件methods以及響應式數據data等 Vue的構造函數爲:vue
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
複製代碼
_init初始化代碼爲:node
Vue.prototype._init = function (options) {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
複製代碼
resolveConstructorOptions(vm.constructor)函數的做用是獲取當前當前實例中構造函數的options選項及其全部父級的構造函數的options,而後在_init中由initLifecycle函數初始化實例屬性:git
function initLifecycle (vm) {
var options = vm.$options;
// locate first non-abstract parent
// 找到第一個非抽象父類
var parent = options.parent;
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm);
}
vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;
vm.$children = [];
vm.$refs = {};
vm._watcher = null;
vm._inactive = null;
vm._directInactive = false;
vm._isMounted = false;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
複製代碼
在Vue實例上初始化一些屬性並設置默認值。 接着初始化事件,被初始化的事件就是父組件在模板中使用v-on監聽子組件中觸發的事件:github
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
// 父組件初始化時監聽的事件
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
複製代碼
_parentListeners是父組件傳遞到子組件,其格式以下:vuex
{
increment: function () {}
}
複製代碼
而後將父組件向子組件註冊的事件註冊到子組件數組
function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm);
target = undefined;
}
複製代碼
updateComponentListeners函數具體爲:promise
function updateListeners (
on,
oldOn,
add,
remove$$1,
createOnceHandler,
vm
) {
var name, def$$1, cur, old, event;
for (name in on) {
def$$1 = cur = on[name];
old = oldOn[name];
// 判斷是否使用修飾符
event = normalizeEvent(name);
if (isUndef(cur)) {
warn(
"Invalid handler for event \"" + (event.name) + "\": got " + String(cur),
vm
);
} else if (isUndef(old)) {
// 哪些事件在中oldOn不存在,則調用add註冊
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm);
}
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture);
}
add(event.name, cur, event.capture, event.passive, event.params);
} else if (cur !== old) {
// 若是on和oldOn不一致,則將事件回調替換成on中的回調,並把on中的回調引用指向oldOn中對應的事件
old.fns = cur;
on[name] = old;
}
}
// 刪除在on中不存在的事件
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove$$1(event.name, oldOn[name], event.capture);
}
}
}
複製代碼
add函數爲瀏覽器
function add (event, fn, once) {
if (once) {
target.$once(event, fn)
} else {
target.$on(event, fn)
}
}
複製代碼
將事件添加到vm._events = {} 中。 initRender函數主要是初始化一些變量屬性和函數緩存
vm._vnode = null; // the root of the child tree
vm._staticTrees = null; // v-once cached trees
var options = vm.$options;
var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree
var renderContext = parentVnode && parentVnode.context;
vm.$slots = resolveSlots(options._renderChildren, renderContext);
vm.$scopedSlots = emptyObject;
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
複製代碼
因data或者props中能依賴reject,須要將reject初始化在initState前,inject初始化爲:bash
function initInjections (vm) {
var result = resolveInject(vm.$options.inject, vm);
if (result) {
// 設置不要將內容轉換成響應式
toggleObserving(false);
Object.keys(result).forEach(function (key) {
/* istanbul ignore else */
{
defineReactive$$1(vm, key, result[key], function () {
warn(
"Avoid mutating an injected value directly since the changes will be " +
"overwritten whenever the provided component re-renders. " +
"injection being mutated: \"" + key + "\"",
vm
);
});
}
});
toggleObserving(true);
}
}
複製代碼
resolveInject函數的做用是經過配置的reject從當前組件自底向上,查找可用的內容
function resolveInject (inject, vm) {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
var result = Object.create(null);
var keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject);
// inject中的key值逐級查找,若是全部沒有可用值,則使用默認值
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// #6574 in case the inject object is observed...
// 跳過響應式的key
if (key === '__ob__') { continue }
var provideKey = inject[key].from;
var source = vm;
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break
}
source = source.$parent;
}
if (!source) {
if ('default' in inject[key]) {
var provideDefault = inject[key].default;
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault;
} else {
warn(("Injection \"" + key + "\" not found"), vm);
}
}
}
return result
}
}
複製代碼
而後初始化數據屬性initState,這個稍後介紹,接着初始化provide
initProvide(vm)
function initProvide (vm) {
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
複製代碼
其在vm.$options中的形式以下:
_provided: {foo: Window}
複製代碼
剛講到的在初始化reject後,provide前,初始化了data、copmputed、watch、props以及methods等數據方法,先講初始化methods:
initMethods(vm, opts.methods);
複製代碼
其中opts.methods爲
{
incremenTotal: ƒ incremenTotal(total)
}
複製代碼
首先會判斷props屬性中是否有跟methods的方法名同樣的,有的話則給出警告;再將方法綁定到實例上:
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
複製代碼
能夠經過vm.x訪問methods中的x了。data、watch以及props前面均有講到,接下來詳細說下computed的原理。
computed是一個惰性求值的觀察者,具備緩存性,只有當依賴變化後,第一次訪問computed屬性纔會計算新的值,計算屬性的返回值是否發生變化是經過dirty屬性來肯定,當dirty爲true時,須要從新計算返回值,爲false時不須要從新結算。 初始化computed代碼爲
var computedWatcherOptions = { lazy: true };
function initComputed (vm, computed) {
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
// 計算屬性在SSR環境中,只是一個普通的getter方法
var isSSR = isServerRendering();
// 遍歷computed屬性
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
if (!isSSR) {
// create internal watcher for the computed property.
// 在非SSR環境下,建立一個watcher觀察器,保存到vm._computedWatchers
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
// 若是計算屬性不在vm中則在vm上設置一個計算屬性
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}
複製代碼
若是computed屬性名與methods重名時,計算屬性會失效。defineComputed函數具體爲
function defineComputed (
target,
key,
userDef
) {
// shouldCache爲true表示非SSR環境下
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
// 處理函數
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
} else {
// 處理get對象
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
複製代碼
在瀏覽器端會執行createComputedGetter(key)函數,以下:
function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
複製代碼
從vm._computedWatchers中取出前面存儲的計算屬性的watcher實例,若是watcher存在,再判斷watcher.dirty,若是爲true,說明計算屬性發生變化,須要從新計算。隨後判斷Dep.target,若是存在,調用watcher.depend(),將計算屬性的watcher添加到相關聯的屬性依賴列表中去。當咱們修改計算屬性中的依賴時,由於組件watcher觀察了計算屬性中的依賴,故當依賴的屬性發生變化時,組件的watcher會獲得通知,而後從新渲染。watcher的相關代碼爲:
// 其中options爲var computedWatcherOptions = { lazy: true };
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
if (options) {
this.lazy = !!options.lazy; // true
this.sync = !!options.sync; // false
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
...
this.value = this.lazy
? undefined
: this.get();
}
Watcher.prototype.evaluate = function evaluate () {
// 從新計算下值
this.value = this.get();
this.dirty = false;
};
Watcher.prototype.depend = function depend () {
var i = this.deps.length;
// 遍歷this.deps依賴列表,將computed的watcher添加到相關的列表中
while (i--) {
this.deps[i].depend();
}
};
複製代碼
執行dep的depend方法能將組件的watcher添加到依賴列表,當這些狀態發生變化時,組件的watcher也會收到通知,並進行從新渲染。
該階段主要在created和beforeMount鉤子函數之間,這個階段解析模板,生成渲染函數,只存在完整版中.
在beforeMount和mounted鉤子函數之間,將生成的render函數生成VNode,並掛載到頁面,在掛載過程當中,Watcher會持續追蹤狀態變化
執行vm.$destroy方法,Vue.js會將自身從父組件中刪掉,取消實例上全部的依賴追蹤而且移除全部的事件監聽器
vm.$nextTick和Vue.nextTick方法是一致的,都是被抽象成了nextTick方法:
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
複製代碼
nextTick函數的具體實現方式以下:
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
// 用於沒有回調函數返回Promise,支持nextTick().then形式
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
複製代碼
callbacks數組用來存儲用戶註冊的回調,pending表示是否須要被添加入任務隊列,接着執行timerFunc函數:
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function () {
p.then(flushCallbacks);
// In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) { setTimeout(noop); }
};
isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = function () {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = function () {
setTimeout(flushCallbacks, 0);
};
}
複製代碼
timerFunc表示一個異步延遲包裝器,保延遲調用 flushCallbacks 函數。Vue2.6.*版本以微任務優先,其順序爲promise > MutationObserver > setImmediate > setTimeout。Vue2.5.*的總體優先級是:Promise > setImmediate > MessageChannel > setTimeout,flushCallbacks函數是遍歷執行回調函數
function flushCallbacks () {
pending = false;
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
copies[i]();
}
}
複製代碼
當$nextTick的回調函數改變了狀態值,會觸發Object.defineProperty中的set屬性,隨後執行
dep.notify();
// Dep類的notify方法
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort(function (a, b) { return a.id - b.id; }); } for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; 複製代碼
觸發watcher的update方法:
Watcher.prototype.update = function update () {
/* istanbul ignore else */
// computed須要更新值時
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
// 同步執行時
this.run();
} else {
// 加入到任務隊列中
queueWatcher(this);
}
};
複製代碼
queueWatcher函數主要執行nextTick(flushSchedulerQueue)
函數,表示下一個事件循環執行flushSchedulerQueue函數,該函數簡化爲:
function flushSchedulerQueue () {
flushing = true
let watcher, id
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
}
複製代碼
此時能更新視圖,從新渲染。
Vue的API中有個errorCapture鉤子函數的做用是捕獲來自子孫組件的錯誤,當返回false時,組織錯誤繼續傳播,其錯誤傳播機制以下:
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的錯誤信息,好比錯誤所在的生命週期鉤子
// 只在 2.2.0+ 可用
}
複製代碼
相關代碼以下:
function handleError (err, vm, info) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
// See: https://github.com/vuejs/vuex/issues/1505
pushTarget();
try {
if (vm) {
var cur = vm;
// 自底向上循環讀取父組件errorCaptured鉤子函數,並執行
while ((cur = cur.$parent)) {
var hooks = cur.$options.errorCaptured;
if (hooks) {
for (var i = 0; i < hooks.length; i++) {
try {
var capture = hooks[i].call(cur, err, vm, info) === false;
// 若是errorCaptured鉤子函數返回的值是false,則直接返回
if (capture) { return }
} catch (e) {
globalHandleError(e, cur, 'errorCaptured hook');
}
}
}
}
}
globalHandleError(err, vm, info);
} finally {
popTarget();
}
}
複製代碼
而後執行globalHandleError(err, vm, info)函數:
function globalHandleError (err, vm, info) {
if (config.errorHandler) {
try {
// 傳遞到全局的errorHandler函數處理
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
// if the user intentionally throws the original error in the handler,
// do not log it twice
if (e !== err) {
logError(e, null, 'config.errorHandler');
}
}
}
// 打印錯誤
logError(err, vm, info);
}
複製代碼