前言
本文是vue2.x源碼分析的第四篇随常,主要講解vue實例的初始化過程init*系列撼嗓!
先看調(diào)用形式
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // 在data/props之前處理注入,暫不清楚作用Unknown4.1程梦,本節(jié)不分析
initState(vm); //最主要的函數(shù)
initProvide(vm); // 在data/props之后處理provide,暫不清楚作用Unknown4.2郎逃,本節(jié)不分析
callHook(vm, 'created');
1、分析initLifecycle(vm),initEvents(vm),initRender(vm)
1.1褒翰、initLifecycle(vm)
function initLifecycle (vm) {
var options = vm.$options;
// 定位第一個非抽象的parent,記為Unknown4.1
var parent = options.parent;
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm);
}
//給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;
}
1.2匀泊、initEvents(vm)
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// 處理父元素中的events,Unknown4.2
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
1.3各聘、initRender(vm)
function initRender (vm) {
vm.$vnode = null; // the placeholder node in parent tree,在父樹中的位置
vm._vnode = null; // the root of the child tree伦吠,子樹的根
vm._staticTrees = null;//靜態(tài)樹
var parentVnode = vm.$options._parentVnode;
var renderContext = parentVnode && parentVnode.context;
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext);//處理slot
vm.$scopedSlots = emptyObject;
// 將createElement函數(shù)綁定到該實例妆兑,參數(shù)順序:tag, data, children, normalizationType, alwaysNormalize.
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };//內(nèi)部調(diào)用版
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };外部調(diào)用版
}
之后,beforeCreate生命周期函數(shù)被調(diào)用:callHook(vm, 'beforeCreate');
2毛仪、initState(vm)
function initState (vm) {
vm._watchers = []; //用于存放所用的watcher實例
var opts = vm.$options;
if (opts.props) {
initProps(vm, opts.props); //對props各項進(jìn)行驗證
}
if (opts.methods) {
initMethods(vm, opts.methods); //將methods中各項添加到vm上
}
if (opts.data) {
initData(vm); //對data進(jìn)行觀測
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) {
initComputed(vm, opts.computed); //將computed屬性添加到vm搁嗓,并定義響應(yīng)式
}
if (opts.watch) {
initWatch(vm, opts.watch); //對watch屬性進(jìn)行處理...
}
}
2.1、initProps(vm,propsOptions)
function initProps (vm, propsOptions) {
var propsData = vm.$options.propsData || {};
var props = vm._props = {};
// 對props中key進(jìn)行緩存以減少遍歷
var keys = vm.$options._propKeys = [];
var isRoot = !vm.$parent; //是否是根節(jié)點
// 根實例的props需要被轉(zhuǎn)換
observerState.shouldConvert = isRoot; //observeState={isSettingProps:false,shouldConvert:false}
var loop = function ( key ) {
keys.push(key);
var value = validateProp(key, propsOptions, propsData, vm);
{
if (isReservedProp[key]) { //判斷是否是保留的prop(不能是key,slot,ref)
warn(
("\"" + key + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
//將props的key定義為響應(yīng)式箱靴,這里的匿名函數(shù)是作為customSetter用的腺逛,defineReactive$$1函數(shù)后面會分析
defineReactive$$1(props, key, value, function () {
if (vm.$parent && !observerState.isSettingProps) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
/* 靜態(tài)props在Vue.extend()時已經(jīng)被代理,這里只需對實例上的props進(jìn)行代理衡怀,代理的目的很簡單棍矛,
就是使props中的屬性可以用vm.xxx訪問,而不必vm.props.xxx,實現(xiàn)很簡單抛杨,就是使用API:Object.defineProperty*/
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) loop( key );
observerState.shouldConvert = true;
}
主要分析下validateProp(key, propsOptions, propsData, vm)
//該函數(shù)就是對prop進(jìn)行驗證够委,如type、default怖现、required茁帽、validator
function validateProp (
key,
propOptions,
propsData,
vm
) {
var prop = propOptions[key];
var absent = !hasOwn(propsData, key);
var value = propsData[key];
// 處理布爾類型的prop
if (isType(Boolean, prop.type)) {
if (absent && !hasOwn(prop, 'default')) {
value = false;
} else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
value = true;
}
}
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key);//當(dāng)type為object時玉罐,default必須是函數(shù)
// 觀測value
var prevShouldConvert = observerState.shouldConvert;
observerState.shouldConvert = true;
observe(value);
observerState.shouldConvert = prevShouldConvert;
}
{
assertProp(prop, key, value, vm, absent);//檢查是否有required和validator
}
return value
}
2.2、initMethods(vm, opts.methods)
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
//遍歷methods,將methods中的方法掛在vm實例上潘拨,注意這里調(diào)用了bind函數(shù)吊输,故methods中所有方法的this都是vm對象
vm[key] = methods[key] == null ? noop : bind(methods[key], vm);
{
if (methods[key] == null) {
warn(
"method \"" + key + "\" has an undefined value in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
//檢測props中是否與methods中有同名屬性
if (props && hasOwn(props, key)) {
warn(
("method \"" + key + "\" has already been defined as a prop."),
vm
);
}
}
}
}
2.3、initData(vm)
function initData (vm) {
//這里的data通過策略合并對象變成了函數(shù)mergedInstanceDataFn
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm) //getData就是執(zhí)行mergedInstanceDataFn函數(shù)铁追,返回data對象
: data || {};
//當(dāng)data函數(shù)返回的不是對象時
if (!isPlainObject(data)) {
data = {};
"development" !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// 在實例vm上代理data中各項季蚂,作用同props的代理
var keys = Object.keys(data);
var props = vm.$options.props;
var i = keys.length;
while (i--) {
//data中屬性不能和props中同名
if (props && hasOwn(props, keys[i])) {
"development" !== 'production' && warn(
"The data property \"" + (keys[i]) + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(keys[i])) { //data中屬性不能以_或$開頭
proxy(vm, "_data", keys[i]);
}
}
// 對data進(jìn)行觀測
observe(data, true /* asRootData */);
}
這里主要分析observe(data, true)
//給value添加一個observer,保存在value.__ob__屬性上。
function observe (value, asRootData) {
//不對普通類型觀測
if (!isObject(value)) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue //當(dāng)有_isVue屬性時琅束,該value不會被觀測
) {
ob = new Observer(value); //主要函數(shù)扭屁,實例化一個Observer
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
來看看Observer(value)
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep(); //每個observer都實例化一個Dep,用于收集依賴
this.vmCount = 0;
def(value, '__ob__', this); //將__ob__放在value上狰闪,值為observer對象
if (Array.isArray(value)) { //當(dāng)value是數(shù)組
var augment = hasProto
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value); //當(dāng)value是對象
}
};
來看看walk(value)
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj); //這里__ob__不會出現(xiàn)在keys里
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i], obj[keys[i]]); //對obj中每一項進(jìn)行響應(yīng)式定義
}
};
來看看defineReactive$$1(obj, keys[i], obj[keys[i]])
function defineReactive$$1 (obj,key,val,customSetter) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// 引用預(yù)先定義的getter/setters
var getter = property && property.get;
var setter = property && property.set;
//val可能是對象疯搅,故繼續(xù)觀測
var childOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
//子對象也收集父對象的依賴
if (childOb) {
childOb.dep.depend();
}
//對數(shù)組的依賴處理
if (Array.isArray(value)) {
dependArray(value);
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if ("development" !== 'production' && customSetter) {
customSetter();//報錯用
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = observe(newVal); //設(shè)置新值后,對新值進(jìn)行觀測
dep.notify(); //觸發(fā)觀測器的回調(diào)或get函數(shù)
}
});
}
2.4埋泵、initComputed(vm, opts.computed)
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);//computed中watcher存放在vm._computedWatchers
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
{
if (getter === undefined) {
warn(
("No getter function has been defined for computed property \"" + key + "\"."),
vm
);
getter = noop;
}
}
// 對computed中每一個屬性創(chuàng)建Watcher.
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);
// 將computed中的屬性定義到vm上丽声,跟代理差不多,使vm.xx等價于vm.computed.xx
if (!(key in vm)) { //computed中屬性若和vm上同名會被vm上的覆蓋
defineComputed(vm, key, userDef);
}
}
}
來看看Watcher(vm, getter, noop, computedWatcherOptions)
var Watcher = function Watcher (vm,expOrFn,cb,options) {
this.vm = vm;
vm._watchers.push(this); //vm._watchers存放所有的watcher實例
// 處理options浴井,在initComputed中,options={lazy:true}磺浙,即lazy watchers撕氧;
// 在initWatch中伦泥,options={user:true}
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; //存放回調(diào)函數(shù)cb
this.id = ++uid$2; // 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();
this.expression = expOrFn.toString(); //存放傳入的expOrFn
if (typeof expOrFn === 'function') {
// 當(dāng)expOrFn是函數(shù),initComputed從這兒走
this.getter = expOrFn;
} else {
// 當(dāng)expOrFn是字符串不脯,initWatch從這兒走
this.getter = parsePath(expOrFn);//字符串若有分隔防楷,只能用'.'號域帐,不能用空格,'-'等
if (!this.getter) {
this.getter = function () {};
"development" !== '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 //initComputed中l(wèi)azy是true肖揣,故不調(diào)用get函數(shù);initWatcher中會調(diào)用get
? undefined
: this.get();
};
2.4龙优、initWatch(vm, opts.watch)
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
if (Array.isArray(handler)) { //當(dāng)handler是數(shù)組時彤断,分別調(diào)用createWatcher
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
來看看createWatcher(vm, key, handler)
function createWatcher (vm, key, handler) {
var options;
if (isPlainObject(handler)) { //當(dāng)handler是數(shù)組時應(yīng)該提供handler屬性
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') { //當(dāng)handler是字符串宰衙,從vm上找同名屬性
handler = vm[handler];
}
vm.$watch(key, handler, options); //主要函數(shù)
}
來看看vm.$watch(key,handler,options)
Vue.prototype.$watch = function (expOrFn,cb,options) {
var vm = this;
options = options || {};
options.user = true; //user表示是否是用戶自定義的watcher
var watcher = new Watcher(vm, expOrFn, cb, options); //回到2.3中再看一遍供炼,會發(fā)現(xiàn)最后調(diào)用get函數(shù)
if (options.immediate) {
cb.call(vm, watcher.value);
}
return function unwatchFn () { //返回解除watcher的函數(shù)
watcher.teardown();
}
};
來看看get函數(shù)
Watcher.prototype.get = function get () {
pushTarget(this);//內(nèi)部執(zhí)行了Dep.target = this袋哼,并將this存入棧targetStack;
var value;
var vm = this.vm;
if (this.user) {
try {
value = this.getter.call(vm, vm);//調(diào)用getter,在initWatch中即調(diào)用如下函數(shù):
// function (obj) { //這里的obj=vm
// for (var i = 0; i < segments.length; i++) { //segments是對觀測表達(dá)式進(jìn)行的分割
// if (!obj) { return }
// obj = obj[segments[i]]; //當(dāng)segments.length>1時涛贯,第一次從vm上找同名屬性返回弟翘,后面就從obj上找稀余,例如
// //當(dāng)觀測表達(dá)式是'article.title'滚躯,那么segments=['article','title']
// //先找vm.article,再在article上找title作為結(jié)果返回
// }
// return obj
// }
} catch (e) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
}
} else {
value = this.getter.call(vm, vm);
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) { //是否進(jìn)行深度觀測
traverse(value);
}
popTarget(); //從targetStack棧中彈出this掸掏,將Deep.target仍為this
this.cleanupDeps(); //暫不清楚丧凤,Unknown4.1
return value
};
當(dāng)initState調(diào)用完成后愿待,created生命周期函數(shù)被調(diào)用:callHook(vm, 'created');
3、小結(jié)
??由于還沒到渲染環(huán)節(jié)要出,故本節(jié)中定義的響應(yīng)式屬性還不能起作用患蹂,不太好理解砸紊,下一節(jié)將會看到
這些響應(yīng)式屬性是怎么起作用的