游戏推广员怎么做,seo诊断分析报告,简单的网页设计源代码,雅安城市建设网站目录
1. 前言
2. new Vue()都干了什么
3 . 合并属性
4. callHook函数如何触发钩子函数
5. 总结 1. 前言
上篇文章中介绍了Vue实例的生命周期大致分为4个阶段#xff0c;那么首先我们先从第一个阶段——初始化阶段开始入手分析。从生命周期流程图中我们可以看到#xff…目录
1. 前言
2. new Vue()都干了什么
3 . 合并属性
4. callHook函数如何触发钩子函数
5. 总结 1. 前言
上篇文章中介绍了Vue实例的生命周期大致分为4个阶段那么首先我们先从第一个阶段——初始化阶段开始入手分析。从生命周期流程图中我们可以看到初始化阶段所做的工作也可大致分为两部分第一部分是new Vue()也就是创建一个Vue实例第二部分是为创建好的Vue实例初始化一些事件、属性、响应式数据等。接下来我们就从源码角度来深入分析一下初始化阶段所做的工作及其内部原理。
2. new Vue()都干了什么
初始化阶段所做的第一件事就是new Vue()创建一个Vue实例那么new Vue()的内部都干了什么呢 我们知道new 关键字在 JS中表示从一个类中实例化出一个对象来由此可见 Vue 实际上是一个类。所以new Vue()实际上是执行了Vue类的构造函数那么我们来看一下Vue类是如何定义的Vue类的定义是在源码的src/core/instance/index.js 中如下
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)
}可以看到Vue类的定义非常简单其构造函数核心就一行代码
this._init(options)调用原型上的_init(options)方法并把用户所写的选项options传入。那这个_init方法是从哪来的呢在Vue类定义的下面还有几行代码其中之一就是
initMixin(Vue)这一行代码执行了initMixin函数那initMixin函数又是从哪儿来的呢该函数定义位于源码的src/core/instance/init.js 中如下
export function initMixin (Vue) {Vue.prototype._init function (options) {const vm thisvm.$options mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)vm._self vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, beforeCreate)initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, created)if (vm.$options.el) {vm.$mount(vm.$options.el)}}
}可以看到在initMixin函数内部就只干了一件事那就是给Vue类的原型上绑定_init方法同时_init方法的定义也在该函数内部。现在我们知道了new Vue()会执行Vue类的构造函数构造函数内部会执行_init方法所以new Vue()所干的事情其实就是_init方法所干的事情那么我们着重来分析下_init方法都干了哪些事情。
首先把Vue实例赋值给变量vm并且把用户传递的options选项与当前构造函数的options属性及其父级构造函数的options属性进行合并关于属性如何合并的问题下面会介绍得到一个新的options选项赋值给$options属性并将$options属性挂载到Vue实例上如下
vm.$options mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm
)接着通过调用一些初始化函数来为Vue实例初始化一些属性事件响应式数据等如下
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染
callHook(vm, beforeCreate) // 调用生命周期钩子函数
initInjections(vm) //初始化injections
initState(vm) // 初始化props,methods,data,computed,watch
initProvide(vm) // 初始化 provide
callHook(vm, created) // 调用生命周期钩子函数可以看到除了调用初始化函数来进行相关数据的初始化之外还在合适的时机调用了callHook函数来触发生命周期的钩子关于callHook函数是如何触发生命周期的钩子会在下面介绍我们先继续往下看
if (vm.$options.el) {vm.$mount(vm.$options.el)
}在所有的初始化工作都完成以后最后会判断用户是否传入了el选项如果传入了则调用$mount函数进入模板编译与挂载阶段如果没有传入el选项则不进入下一个生命周期阶段需要用户手动执行vm.$mount方法才进入下一个生命周期阶段。
以上就是new Vue()所做的所有事情可以看到整个初始化阶段都是在new Vue()里完成的关于new Vue()里调用的一些初始化函数具体是如何进行初始化的我们将在接下来的几篇文章里逐一介绍。下面我们先来看看上文中遗留的属性合并及callHook函数是如何触发生命周期的钩子的问题。
3 . 合并属性
在上文中_init方法里首先会调用mergeOptions函数来进行属性合并如下
vm.$options mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm
)它实际上就是把 resolveConstructorOptions(vm.constructor) 的返回值和 options 做合并resolveConstructorOptions 的实现先不考虑可简单理解为返回 vm.constructor.options相当于 Vue.options那么这个 Vue.options又是什么呢其实在 initGlobalAPI(Vue) 的时候定义了这个值代码在 src/core/global-api/index.js 中
export function initGlobalAPI (Vue: GlobalAPI) {// ...Vue.options Object.create(null)ASSET_TYPES.forEach(type {Vue.options[type s] Object.create(null)})extend(Vue.options.components, builtInComponents)// ...
}首先通过 Vue.options Object.create(null) 创建一个空对象然后遍历 ASSET_TYPESASSET_TYPES 的定义在 src/shared/constants.js 中
export const ASSET_TYPES [component,directive,filter
]所以上面遍历 ASSET_TYPES 后的代码相当于
Vue.options.components {}
Vue.options.directives {}
Vue.options.filters {}最后通过 extend(Vue.options.components, builtInComponents) 把一些内置组件扩展到 Vue.options.components 上Vue 的内置组件目前 有keep-alive、transition 和transition-group 组件这也就是为什么我们在其它组件中使用这些组件不需要注册的原因。
那么回到 mergeOptions 这个函数它的定义在 src/core/util/options.js 中
/*** Merge two option objects into a new one.* Core utility used in both instantiation and inheritance.*/
export function mergeOptions (parent: Object,child: Object,vm?: Component
): Object {if (typeof child function) {child child.options}const extendsFrom child.extendsif (extendsFrom) {parent mergeOptions(parent, extendsFrom, 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 keyfor (key in parent) {mergeField(key)}for (key in child) {if (!hasOwn(parent, key)) {mergeField(key)}}function mergeField (key) {const strat strats[key] || defaultStratoptions[key] strat(parent[key], child[key], vm, key)}return options
}可以看出mergeOptions函数的 主要功能是把 parent 和 child 这两个对象根据一些合并策略合并成一个新对象并返回。首先递归把 extends 和 mixins 合并到 parent 上 const extendsFrom child.extendsif (extendsFrom) {parent mergeOptions(parent, extendsFrom, vm)}if (child.mixins) {for (let i 0, l child.mixins.length; i l; i) {parent mergeOptions(parent, child.mixins[i], vm)}}然后创建一个空对象options遍历 parent把parent中的每一项通过调用 mergeField函数合并到空对象options里
const options {}
let key
for (key in parent) {mergeField(key)
}接着再遍历 child把存在于child里但又不在 parent中 的属性继续调用 mergeField函数合并到空对象options里
for (key in child) {if (!hasOwn(parent, key)) {mergeField(key)}
}最后options就是最终合并后得到的结果将其返回。
这里值得一提的是 mergeField 函数它不是简单的把属性从一个对象里复制到另外一个对象里而是根据被合并的不同的选项有着不同的合并策略。例如对于data有data的合并策略即该文件中的strats.data函数对于watch有watch的合并策略即该文件中的strats.watch函数等等。这就是设计模式中非常典型的策略模式。
关于这些合并策略都很简单我们不一一展开介绍仅介绍生命周期钩子函数的合并策略因为我们后面会用到。生命周期钩子函数的合并策略如下
/*** Hooks and props are merged as arrays.*/
function mergeHook (parentVal,childVal): {return childVal? parentVal? parentVal.concat(childVal): Array.isArray(childVal)? childVal: [childVal]: parentVal
}LIFECYCLE_HOOKS.forEach(hook {strats[hook] mergeHook
})这其中的 LIFECYCLE_HOOKS 的定义在 src/shared/constants.js 中
export const LIFECYCLE_HOOKS [beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed,activated,deactivated,errorCaptured
]这里定义了所有钩子函数名称所以对于钩子函数的合并策略都是 mergeHook 函数。mergeHook 函数的实现用了一个多层嵌套的三元运算符如果嵌套太深不好理解的话我们可以将其展开如下
function mergeHook (parentVal,childVal): {if (childVal) {if (parentVal) {return parentVal.concat(childVal)} else {if (Array.isArray(childVal)) {return childVal} else {return [childVal]}}} else {return parentVal}
}从展开后的代码中可以看到它的合并策略是这样子的如果 childVal不存在就返回 parentVal否则再判断是否存在 parentVal如果存在就把 childVal 添加到 parentVal 后返回新数组否则返回 childVal 的数组。所以回到 mergeOptions 函数一旦 parent 和 child 都定义了相同的钩子函数那么它们会把 2 个钩子函数合并成一个数组。
那么问题来了为什么要把相同的钩子函数转换成数组呢这是因为Vue允许用户使用Vue.mixin方法关于该方法会在后面章节中介绍向实例混入自定义行为Vue的一些插件通常都是这么做的。所以当Vue.mixin和用户在实例化Vue时如果设置了同一个钩子函数那么在触发钩子函数时就需要同时触发这个两个函数所以转换成数组就是为了能在同一个生命周期钩子列表中保存多个钩子函数。
4. callHook函数如何触发钩子函数
关于callHook函数如何触发钩子函数的问题我们只需看一下该函数的实现源码即可该函数的源码位于src/core/instance/lifecycle.js 中如下
export function callHook (vm: Component, hook: string) {const handlers vm.$options[hook]if (handlers) {for (let i 0, j handlers.length; i j; i) {try {handlers[i].call(vm)} catch (e) {handleError(e, vm, ${hook} hook)}}}
}可以看到callHook函数逻辑非常简单。首先从实例的$options中获取到需要触发的钩子名称所对应的钩子函数数组handlers我们说过每个生命周期钩子名称都对应了一个钩子函数数组。然后遍历该数组将数组中的每个钩子函数都执行一遍。
5. 总结
本篇文章介绍了生命周期第一个阶段——初始化阶段中所做的第一件事new Vue()。
首先分析了new Vue()时其内部都干了些什么。其主要逻辑就是合并配置调用一些初始化函数触发生命周期钩子函数调用$mount开启下一个阶段。
接着就合并属性进行了详细介绍知道了对于不同的选项有着不同的合并策略并挑出钩子函数的合并策略进行了分析。
最后分析了callHook函数的源码知道了callHook函数如何触发钩子函数的。
接下来后面几篇文章将对调用的这些初始化函数进行逐个分析。