上一篇文章我們了解了Vue是怎么一步步變得豐富起來的镣典,從而形成我們的3大框架之一,接下來我們當(dāng)然是要了解這個(gè)Vue是用來干嘛的唾琼?怎么發(fā)生作用的兄春?本質(zhì)上Vue就是一個(gè)構(gòu)造函數(shù),所以我們通過new Vue來探究父叙,從這里也可以看出神郊,我們用Vue框架搭建的項(xiàng)目,本質(zhì)上是一個(gè)Vue實(shí)例趾唱。
new Vue
我們引入vue涌乳,然后new Vue
創(chuàng)建應(yīng)用,那么甜癞,要探究發(fā)生了什么夕晓,我們要先找到Vue構(gòu)造函數(shù)定義的地方,上一篇我們知道豐富Vue的文件有4個(gè)悠咱,它正是在文件src\core\instance\index.js
定義的蒸辆。
如果你還無法確定征炼,可以通過debugger來查看,像這樣:
一般我們像這樣創(chuàng)建vue實(shí)例,new Vue時(shí)傳入不同的選項(xiàng)躬贡,如el, data, mounted等谆奥,我們的思路之一可以是Vue都是怎么處理我們傳入的這些選項(xiàng)的。
var vm = new Vue({
el: '#el',
data: {
...
},
mounted() {
...
},
methods: {
....
}
.......
})
接下來我們看Vue構(gòu)造函數(shù)
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
非常簡(jiǎn)單是不是拂玻,只要符合條件酸些,就執(zhí)行_init
方法,還記得這個(gè)_init
文件怎么來的嗎?上一篇我們已經(jīng)講過檐蚜,通過執(zhí)行initMixin(Vue)
魄懂,接下來我們看看_init發(fā)生了什么?
this._init(options)
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
// 設(shè)置uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
// 性能相關(guān)的操作
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag) // 這里調(diào)用window.performance.mark:從navigetionStart事件發(fā)生時(shí)刻到記錄時(shí)刻間隔的毫秒數(shù)
}
// a flag to avoid this being observed
// 添加_isVue標(biāo)記
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
// 合并options闯第,設(shè)置$options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
// _self執(zhí)行自身
vm._self = vm
// 初始化生命周期
initLifecycle(vm)
// 初始化事件中心
initEvents(vm)
// 初始化渲染
initRender(vm)
callHook(vm, 'beforeCreate')
// 初始化 data市栗、props、computed咳短、watcher 等等
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
// 調(diào)用鉤子函數(shù)create
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag) // 跟上面的性能操作相對(duì)應(yīng)
}
if (vm.$options.el) {
// 如果存在選擇器填帽,則執(zhí)行掛載方法
vm.$mount(vm.$options.el)
}
}
}
好了,讓我們來總結(jié)如下:
讓我們來看看用顏色圈出的操作具體是什么意思咙好?
1. vm._uid
2. vm._isVue = true
3. vm.$options
調(diào)用mergeOptions合并構(gòu)造函數(shù)的options盲赊,如Vue.options,返回的結(jié)果賦給vm.$options
4. initProxy(vm)
設(shè)置vm._renderProxy = new Proxy(vm, handlers)
, vm._renderProxy是干嘛用的敷扫,暫時(shí)不理會(huì)哀蘑。
5. vm._self = vm
6. initLifecycle(vm)
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);
}
// 掛載上它的父節(jié)點(diǎn),子節(jié)點(diǎn)葵第,根節(jié)點(diǎn)
vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;
// 對(duì)以下屬性初始化
vm.$children = [];
vm.$refs = {};
vm._watcher = null;
vm._inactive = null;
vm._directInactive = false;
vm._isMounted = false;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
7. initEvents(vm)
function initEvents (vm) {
// 初始化vm._events, vm._hasHookEvent
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
// 如果listeners存在绘迁,則進(jìn)行更新
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
其實(shí)它后面是對(duì)新舊listeners進(jìn)行更新,這里暫時(shí)不討論卒密。
8. initRender(vm)
這一步就跟渲染有關(guān)了缀台,例如虛擬DOM,創(chuàng)建元素的方法哮奇,$slots
等膛腐,另外,還設(shè)置了$attrs
和$listeners
,并將它們?cè)O(shè)置為響應(yīng)式
function initRender (vm) {
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;
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
var parentData = parentVnode && parentVnode.data;
/* istanbul ignore else */
{
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
}, true);
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
}
}
9. callHook(vm, 'beforeCreate')
調(diào)用生命周期方法beforeCreate,在這里可以看到beforeCreate之前都做了哪些操作
10. initInjections(vm)
function initInjections (vm) {
var result = resolveInject(vm.$options.inject, vm);
if (result) {
toggleObserving(false);
Object.keys(result).forEach(function (key) {
/* istanbul ignore else */
{
defineReactive(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);
}
}
這里對(duì)我們傳入的選項(xiàng)inject進(jìn)行處理鼎俘。
11. initState(vm)
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); // 使用bind方法把methods里面的方法和vm綁定在一起哲身,于是vm上有了這個(gè)方法,我們就可以用this.xxx()調(diào)用}
if (opts.data) {
initData(vm); // 1. 將data里面的數(shù)據(jù)定義在vm上贸伐,于是我們可以使用this.xxx訪問屬性勘天;2. observe(data, true)將遞歸遍歷data里面的轉(zhuǎn)為可響應(yīng)式數(shù)據(jù),遞歸遍歷到的數(shù)據(jù)如果是對(duì)象或數(shù)組,會(huì)被打上_ob_標(biāo)記
} else {
// 這里將數(shù)據(jù)可響應(yīng)化
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
從代碼可知脯丝,這一步初始化vm.watchers = []
商膊,初始化選項(xiàng)computed,watch宠进,props,methods, data并將data里面的數(shù)據(jù)轉(zhuǎn)為響應(yīng)式的晕拆,主要處理數(shù)據(jù)相關(guān)的
12. initProvide(vm)
function initProvide (vm) {
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
非常明顯,處理我們傳入的provide選項(xiàng)材蹬。
13. callHook(vm, 'created')
調(diào)用生命周期鉤子函數(shù)created, 說明這時(shí)已經(jīng)初始化好數(shù)據(jù)潦匈,但是并沒有把模板正在掛載在元素上,下一步$mount才開始掛載赚导。
14. vm.$mount(vm.$options.el)
如果我們有傳入el
選項(xiàng),則會(huì)Vue會(huì)自動(dòng)幫我們調(diào)用$mount
方法赤惊,否則需要我們手動(dòng)去調(diào)用vm.$mount()
吼旧。
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
var options = this.$options;
// resolve template/el and convert to render function
if (!options.render) {
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if ( !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) {
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if ( config.performance && mark) {
mark('compile');
}
// 將模板轉(zhuǎn)render函數(shù)
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if ( config.performance && mark) {
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating)
};
很明顯,這里重寫了$mount
未舟,這個(gè)在上一篇中我們已經(jīng)提及到了圈暗,具體$mount
做了什么?由于是重點(diǎn)內(nèi)容裕膀,我們將在下一篇繼續(xù)探討员串。