学习目标
本篇主要学习若川源码共读系列的《为什么 Vue2 this 能够直接获取到 data 和 methods》,掌握vue的初始化过程。
学习资料
准备环境调试
lxchuan12.gitee.io/vue-this/#_…
初始化过程
Vue构造函数
// vue 构造函数
function Vue (options) {
// 判断是不是用了 new 关键词调用构造函数,不是的话控制台报出警告
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
// 初始化
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
_init 初始化函数
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++;
var startTag, endTag;
// if 语句块,在计算覆盖率的时候会被忽略。
/* istanbul ignore if */
if (config.performance && mark) {
startTag = "vue-perf-start:" + (vm._uid);
endTag = "vue-perf-end:" + (vm._uid);
mark(startTag);
}
// a flag to avoid this being observed
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 {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* istanbul ignore else */
{
initProxy(vm);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
/* istanbul ignore if */
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(("vue " + (vm._name) + " init"), startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
initLifecycle
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;
}
initRender
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$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
}, true);
defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
}
}
callhook
// 回调生命周期钩子函数
function callHook (vm, hook) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget();
var handlers = vm.$options[hook];
var info = hook + " hook";
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info);
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
popTarget();
}
initState
// 初始化所有state props methods data computed watch
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
// props 初始化比data早 所以data 中字段可以用props赋值
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
// data 比 computed watch 初始化早 computed watch 可以依赖、监听data中的变量
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
initMethods
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
{
// 判断methods中value 是否是方法
if (typeof methods[key] !== 'function') {
warn(
"Method "" + key + "" has type "" + (typeof methods[key]) + "" in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
// 判断methods中方法名是否和props中属性重名
if (props && hasOwn(props, key)) {
warn(
("Method "" + key + "" has already been defined as a prop."),
vm
);
}
// 判断是否是私有属性
if ((key in vm) && isReserved(key)) {
warn(
"Method "" + key + "" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
// methods 中 某个value 不是方法时,指向空函数,是方法时,改变this指向,指向vm
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
initData
function initData (vm) {
var data = vm.$options.data;
// 判断data是否是函数 如果是函数 使用call改变this指向,data是return的{},data不是函数时,赋值为$options.data 或 {}
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
// 判断data是否是纯对象
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// proxy data on instance
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn(
("Method "" + key + "" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
warn(
"The data property "" + key + "" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
// 将_data挂载到vm
proxy(vm, "_data", key);
}
}
// observe data
observe(data, true /* asRootData */);
}
function getData (data, vm) {
// #7573 disable dep collection when invoking data getters
pushTarget();
try {
// call 立即执行 接受不定量参数
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();
}
}
proxy
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
// 把每个key 挂载到vm实例上
Object.defineProperty(target, key, sharedPropertyDefinition);
}
observe
function observe (value, asRootData) {
// 对象才添加__ob__
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
// value 已经有__ob__属性 return
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 为value 添加 __ob__
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
Observer
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
// 数组调用observeArray方法 遍历为每一项调用observe方法
this.observeArray(value);
} else {
this.walk(value);
}
};
// 遍历每个属性并将它们转换为getter/setter
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
/**
* Observe a list of Array items.
*/
Observer.prototype.observeArray = function observeArray(items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
Dep
var uid = 0;
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
// Dep是订阅者Watcher对应的数据依赖
var Dep = function Dep () {
// 每个dep对象有一个唯一的id
this.id = uid++;
//subs用于存放依赖
this.subs = [];
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
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();
}
};
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null;
def
/**
* Define a property.
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
defineReactive$$1
/**
* Define a reactive property on an Object. 在对象上定义一个响应式对象
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
// 为vm添加get set
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();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
invokeWithErrorHandling
// 调用生命周期钩子函数 及 错误处理
function invokeWithErrorHandling (
handler,
context,
args,
vm,
info
) {
var res;
try {
// 有参数 使用apply改变this指向并立即执行 无参数 使用call改变this
res = args ? handler.apply(context, args) : handler.call(context);
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true;
}
} catch (e) {
handleError(e, vm, info);
}
return res
}
总结
vue初始化时先初始化生命周期、event事件、render函数,执行beforeCreate回调 ,接着初始化数据相关的方法,执行created回调。this.xxx()可以直接访问到methods里的方法是因为,initMethods事 执行了 vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) ,methods的方法都会指向vm也就是this,this.xxx可以访问到data中数据是因为初始化data时 ,将data中每个属性挂载到了vm实例的_data属性中,访问this.xxx 就是在访问Object.defineprototy代理后的this._data.xxx。