我们在使用Vue开发项目时,new Vue({ el: '#app', methods: {}, ...rest })。那么在这个过程中,发生了什么呢? 以web平台为例,vue构造函数有外到内引用顺序依次为:
- platforms/web/runtime/index.js(设置了Vue.config上的一些属性,挂载__patch__, $mount到vue的原型上)
- core/index.js(为vue挂载一些静态方法即全局api,Vue.extend, Vue.component等。定义$isServer, $ssrContext到vue的原型上)
- core/instance/index.js(定义Vue构造函数,依次调用initMixin, stateMixin, eventsMixin, lifecycleMixin, renderMixin)
接下来我们简要分析一下new Vue都做了什么操作。
在vue的构造函数中,只调用了原型上的_init方法,并传入options。在_init方法中,开始了vue实例化的过程。主要做了以下几件事:
- 合并配置
- 代理vm
- 初始化vm关于生命周期,父子关系,事件处理以及渲染相关的属性
- 调用钩子函数beforeCreate
- 处理options中的inject和provide配置项
- 处理options中的props, methods, data, computed, watch配置项。
- 调用钩子函数created
- 如果options.el存在,调用vm.$mount,$mount来自于Vue.prototype。
下面着重说一些第一步和第六步。第七步会单独作为一个章节来讲。
合并配置
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
function resolveConstructorOptions (Ctor: Class<Component>) {
// 乍一看代码很多,其实new vue时并没有走if分支,获取到Vue.options并返回。
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
mergeOptions合并就是在合并Vue.options和传入的options。下面来看一下mergeOptions:
function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
// 格式化child的props配置项为统一的格式:props: { propsA: { type: xx }, propsB: { type: xx }}
normalizeProps(child, vm)
// 格式化child的inject配置项为统一格式:inject: { injectA: { from: xx } }
normalizeInject(child, vm)
// 格式化child的directives为统一格式: directives: { bind: fn, updaet: fn }
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
// 只有合并过的options有_base属性。
if (!child._base) {
if (child.extends) {
// 处理options.extends的合并
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
// 处理child.mixins的合并
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 选择对应的合并策略。不同的key有不同的合并策略,如data, components,
// filters等。合并策略取自config.optionMergeStrategies中,用户可以自定义合并策略。否则,采用默认的合并策略。
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
执行完合并策略后,将合并后的options赋值给vm.$options以供后续使用。
处理配置项data, props, methods, computed, watch
对于props. 调用initProps(vm, opts.props)。
- 在initProps中,遍历options.props,将props的keys进行收集,放在vm.$options._propKeys上。
- 通过defineReactive对props中的值定义在vm._props上并设置成响应式属性,便于当props发生变化时触发对应的watcher更新视图。
- 将对vm[key]的访问代理到vm._props[key]上。
对于methods,调用initMethods(vm, opts.methods)
- 对methods的key和value进行检测。对于key,不能是props中的key,不能是vm上的以$或者_开头的属性或方法。对于value,必须为function
- vm[key] = value.bind(vm)
对于data,调用initData(vm)。
- 获取到data中的值
- 检查data中的key是否和methods或者props中的有重复,如果重复,给出警告
- 将对vm[key]的访问代理到vm._data[key]上
- 对获取的data进行遍历并定义为响应式属性,便于之后收集依赖watcher。
对于computed和watch的处理,可在Vue源码分析——computed和watch的实现及执行过程分析查看。
以上就是对Vue过程的大致分析,之后会针对某个过程进行单独分析。