因为工作原因,很长时间没有更新文章了,抱歉抱歉,大家多多理解,谢谢支持!
上篇文章我们对Vue最开始是在Vue的原型上挂载了一些方法或属性,比如_init,watch等等,今天我会用一个实例开始我们今天的分析。
我们知道Vue是这样用的。
new Vue({
el: '#app',
data () {
return {
message: 123
}
},
methods: {}
})
我们接着转到源码文件。
......
function Vue (options) {
if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
// Vue是一个构造函数,所以应该用new来实例化,这也是为什么CDN生产环境中要new的原因
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
......
export default Vue
这里我们看到源码将传进来的参数在_init方法中运行。
文件地址:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
...
// 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 */
...
// 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 (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
我们看到一直到vm._self = vm这句代码为止,_init就做了3件事情
- 给每个组件加一个 uid,方便标识
- 加一个 _isVue 用于判断是否为vue组件。
- 合并 options
我们这里先不讨论 new Vue 和组件的参数处理,所以我们先分析 else 里边的逻辑。
可以看到,这里调用了一个 mergeOptions 方法来进行参数合并,接收 vue 构造函数上的参数,实例的参数,实例对象。
我们来看看这个方法都做了什么。
文件路径:src\core\instance\init.js
export function resolveConstructorOptions (Ctor: Class<Component>) {
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
}
我们可以打印 options 看看构造函数上的options都有一些什么。
{
components: {KeepAlive: {…}, Transition: {…}, TransitionGroup: {…}, runoob: ƒ},
directives: {model: {…}, show: {…}},
filters: {},
_base: ƒ Vue(options)
}
可以看到,这里就是放了一些指令,组件,以及筛选器相关的通用属性而已。不深入讨论,后边再来说,现在只要知道有这个就行。
接着往下走,因为当前 Ctor 是 Vue 的构造函数,所以不存在 super ,直接将 options 返回。
我们接着看 mergeOptions 这个函数,传入的是 Vue 的构造函数,子组件的 options,和 vue 实例。
文件路径:src\core\util\options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
...
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
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.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (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)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
这里大致看一下, mergeOptions 函数也做了三件事:
- 统一 props, inject, directive 的写法,因为这三个属性会有多种写法,就以 directive 为例,可以用 Vue.directive 注册全局指令,也可以用 directives: {} 注册局部组件,如果每种写法都做兼容,那么要考虑到的情况就有很多了,所以作者将不同的写法进行了格式统一,再来进行分析就好轻松了。
- 递归子组件的 options ,直至所有 options 都挂载了构造函数的属性和方法。
- 合并子组件和构造函数的字段。
截止到这里,字段合并的功能已经全部结束。返回到 initMixin 的 "vm._self = vm" 这一行,打印vm.$options看看
{
components: {},
data: ƒ mergedInstanceDataFn(),
directives: {},
filters: {},
methods: {},
_base: ƒ Vue(options)
}
我们看到,这里多了 components, directives, filters 这3个对象,我们大胆猜一下作者将为什么这三个字段加入到每个组件中。
我们首先回顾一个经典面试题:组件的data数据为什么必须要以函数返回的形式。
组件是可用的vue实例,每个组件可能被用在各种地方,但是不管怎么用,用了多少次,他们的数据、方法和属性应该是相互独立,互不影响的。
vue 的作者用工厂模式的方法给每个组件都挂载构造函数中的各种数据,vue 参数合并的函数就是实现了这样一个功能。