初始化实例属性
- vue是通过initLifecycle函数来向实例中挂载属性的
export function initLifecycle(vm) {
const options - vm.$options;
//找出第一个非抽象的父类
let parent = options.parent;
if(parent && !options.abstract) {
while(parent.$options.abstract && parent.$parent) {// 如果父级是抽象类,直到遇到第一个非抽象类的父级时候,赋值给vm.$parent
parent = parent.$parent;
}
parent.#children.push(vm);
}
vm.$parent = parent;
vm.$root = parent? parent.$root: vm; // 表示的当前组件树的根vue实例
vm.children = []; // 表示包含当前实例的直接子组件
vm.$refs = {};
vm._watcher = null;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
初始化事件
- 根据上面的流程图所表示:vue是通过 initEvents 函数来执行初始化事件的
export function initEvents(vm) {
vm._events = Object.create(null);
// 初始化父组件附加的事件
const listrens = vm.$options.parentListeners;
if(listrens) {
updateComponentListenners(vm,listeners)
}
}
// updateComponentListenners 函数源码
let target;
function add(event,fn,once) {
if(once) {
target.$once(event,fn);
}else {
target.$on(event,fn);
}
}
function remove(event,fn) {
target.$off(event,fn);
}
export function updateComponentListenners(vm,listeners,oldListeners) {
target = vm;
updateListenners(vm,listeners|| {}, add, remove,fn);// 如果listenners对象中存在某个key,早oldListeners中不存在,那么说明这个事件是需要新增的事件
}
// updateListenners 函数
function isUndef(v){
return v === undefined || v === null
}
export function updateListenners(on,oldOn,add,remove,vm) {
let name ,cur,old, event;
for (name in on) {
cur = on[name];
old = oldOn[name];
event = normalizeEvent(name);
if(isUndef(cur)) { //判断该事件名对应的值是否undefined或者null,如果是,则在控制台触发警告
process.env.NODE_ENV !== 'production' && warn(`Invalid handler for event '${event.name}':got` + String(cur),vm);
} else if (isUndef(old)) { // 判断该事件名在oldOn中是否存在,如果不存在,则调用add注册事件
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur);
}
add(event.name, cur, event.once, event.capture, event.passive)
} else if (cur !== old) { // 如果都存在,但是他们并不相同,则将事件回调替换成on中的回调
old.fns = cur;
on[name] = old;
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
初始化状态
- 初始化状态包括props、methods、data、conputed、watch
graph TD
initState --> initProps
initState --> initMethods
initState --> initData
initState --> initComputed
initState --> initWatch
// initState源码
initState function initState(vm) {
vm._watchrs = [];
const opts = vm.$options;
if(opts.props) initProps(vm.opts.props); // 如果vm.$options中存在props属性,如果存在,则调用props
if (opts.methods) initMethods(vm,opts.methods);// 如果存在methods属性,则调用初始化methods
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);// 如果不存在.则直接使用observe函数观察空对象
}
if(opts.computed) initComputed(vm,opts.computed);
if(opts.wathch && opts.watch !== nativeWatch) {
initWathch(vm,opts.wathch);
}
}
1.初始化props
- 父组件提供数据,子组件通过props字段选择自己需要那些内容 规格化的props源码:
function normalizeProps(options,vm) {
const props = options.props;
if(!props) return; // 如果没有props属性,说明用户没有使用props接收任何数据,那么不需要规格化,直接返回
const res = {};
let i,val,name;
if(Array.isArray(porps)) { // 如果检测为数组,则通过while循环数组的每一项,将Array类型的props规格化成object类型
i = props.length;
while(i--) {
val = props[i];
if(typeof val === 'string') {
name = camelzie(val);
res[name]= {type: null};
}else if(process.env.NODE_ENV !== 'production') { // 在非生产环境下打印警告
warn('poprs must be strings when using array syntax');
}
}
} else if(isPlanObjcet(props)) { // 规格化的props的类型有可能是基础函数,也有可能是数组,
for(cosnt key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlanObject(val)?val: {type:val}
}
} else if(process.env.NODE_ENV !== 'production') {
warn(`Invalid value for option "props":expected an Array or an Object`,`+ but got ${toRawType(props)}.`,vm);
}
options.props =res; // 返回规格化的结果
}
2.初始化methods
- 遍历选项中的methods对象,.并将每个属性依次挂载到vm上即可
function initMethods (vm,methods) {
const props - vm.$options.props;
for (const key in methods) {
if(process.env.NODE_ENV !== 'production') {
if (methods[key] == null) { // 在非生产环境下,校验方法是否合法,并在控制台发出警告
warn(
`Method "${key}" has an undefined value in the compontent definition.` + `Did you reference the function correctly?`,vm);
}
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has alreay been defined as a props.`,vm);
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance methods .` +
`Avoid defining component methods that start with_or $.`,vm);
}
}
vm[key] = methosd[key] == null ? noop :bind(methods[key],vm)// 将方法挂载到vm[key]中
}
4.初始化computed
- 我们知道的计算属的结果会被缓存,只有在所依赖的响应式属性的返回值发生变化时候,才会重新去计算
- 看如下图关系
- 计算属性computed源码
const computedWatcherOptions = {lazy, true};
function initComputed(vm,computed) {
const watcher = vm._computedWatcher = Object.create(null); // 注意,创建出来的对象没有原型,他不存在_proto_属性
// 计算属性在ssr中,只是一个普通的getter方法
const isSSR = isServerRending();
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === 'function' ? userDef : userDef.get;
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(`Getter is missing for computed property "${key}"`);
}
// 在非ssr中,为计算属性创建内部观察器
if(!isSSR) {
watcher[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else if (process.env.NODE_ENV !== 'production') {
if(key in vm.$data) {
warn(`The ....“${key} in data `, vm);
} else if(vm.$options.props && key in vm.$options.props) {
warn(`The ....“${key} in prop `, vm);
}
}
}
}
// initComputed函数作用是初始化计算属性, vm是vue实例上下文(this)
初始watch
// initWatch接收2个参数,一个vm,一个watch对象,
function initWatch(vm,watch) {
for(const key in watch) {
const handler = watch[key];
if(Array.isArray(handler)) {
for(let i =0; i<handler.length;i++) {
createWatcher(vm,key,hnadler);
}
} else {
createWatcher(vm,key,handler);
}
}
}
//createWatcher函数重要负责处理其他类型的handler并调用vm.$watch划船机watcher观察表达式
function createWatcher(vm,expOrFn,handler,optons) {
if(isPlainObject(handler)) { // 如果包含了对象,设置一个特殊包含的对象,因此options的值设置为handler;并将handler对象的handler方法
options = handler;
handler = handler.handler;
}
if(type handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn,handler,optons);
}
// vm:vue执行上下文
// expOrFn: 表示式或计算属性函数
// handler watch对象的值
// options: 用于传递给vm.$watch的选项对象
钩子函数
- 和回调函数一个意思,当系统执行到某处的时候,检测是否有hook(钩子),有的话就执行回调
vue生命周期可以分为8个阶段:
-
beforeCreate 实例创建前:
new Vue()之后触发的第一个钩子,在当前阶段中data、methods、computed以及watch上的数据和方法均不能被访问。
-
created 实例创建完成:
在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果你非要想,可以通过vm.$nextTick来访问Dom。
-
beforeMount 挂载前:
在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated
-
mounted 挂载完成: 在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。也可以向后台发送请求,拿到返回数据
-
beforeUpdate 更新前:
在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染
-
updated 更新完成:
当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新
-
beforeDestory 销毁前: 当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
-
destoryed 销毁完成:
这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁
new Vue()调用的时候发生了什么
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)
}
exprot default Vue
//1. 在上面中可得知,首先进行了安全的检查,在非生产环境下,如果没有使用new 来调用vue,这会在控制台抛出错误警告我们:vue是构造函数,应该使用new关键字来调用
// 2.声明周期的初始化流程在this._init中实现
_init 方法的内部原理
Vue.prototype._init = function (options) {
vm.$options = mergeOptons(
resloveConstructorOptions(vm.construtor),
options||{},
vm
);
}
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm,'beforeCreate');
initInjections(vm);// 在data/props前初始化inject
initState(vm);
initProvide(vm);// 在data/props后初始化provide
callHook(vm,'created');
//如果用户在实例化vue时候传递了el选项,则自动开启模板编译阶段与挂载阶段
// 如果用户在传递el选项.这不进入下一个生命周期流程
// 用户在需要执行vm.$mount方法,手动开启模板编译阶段与挂载阶段
if(vm.$options.el){
vm.$mount(vm,$options.el)
}
钩子函数源码
1.beforeCreate与created
// src/core/instance/init
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
// 合并选项部分已省略
initLifecycle(vm)
// 主要就是给vm对象添加了 $parent、$root、$children 属性,以及一些其它的生命周期相关的标识
initEvents(vm) // 初始化事件相关的属性
initRender(vm) // vm 添加了一些虚拟 dom、slot 等相关的属性和方法
callHook(vm, 'beforeCreate') // 调用 beforeCreate 钩子
//下面 initInjections(vm) 和 initProvide(vm) 两个配套使用,用于将父组件 _provided 中定义的值,通过 inject 注入到子组件,且这些属性不会被观察
initInjections(vm)
initState(vm) // props、methods、data、watch、computed等数据初始化
initProvide(vm)
callHook(vm, 'created') // 调用 created 钩子
}
}
// src/core/instance/state
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
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 */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
// 1.可以看到beforeCreate钩子调用是在initState之前的,而从上面的第二段代码我们可以看出initState的作用是对props、methods、data、computed、watch等属性做初始化处理。
//2. 在beforeCreate钩子的时候没有对props、methods、data、computed、watch上的数据的访问权限。在created中才可以
2.beforeMounted与mounted
// mountComponent 核心就是先实例化一个渲染Watcher
// 在它的回调函数中会调用 updateComponent 方法
// 两个核心方法 vm._render(生成虚拟Dom) 和 vm._update(映射到真实Dom)
// src/core/instance/lifecycle
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
callHook(vm, 'beforeMount') // 调用 beforeMount 钩子
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
// 将虚拟 Dom 映射到真实 Dom 的函数。
// vm._update 之前会先调用 vm._render() 函数渲染 VNode
...
const vnode = vm._render()
...
vm._update(vnode, hydrating)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
// 先判断是否 mouted 完成 并且没有被 destroyed
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted') //调用 mounted 钩子
}
return vm
}
//在执行vm._render()函数渲染VNode之前,执行了 beforeMount钩子函数,在执行完 vm._update() 把VNode patch到真实Dom后,执行 mouted钩子。也就明白了为什么直到mounted阶段才名正言顺的拿到了Dom
beforeUpdate和updated
// src/core/instance/lifecycle
new Watcher(vm, updateComponent, noop, {
before () {
// 先判断是否 mouted 完成 并且没有被 destroyed
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate') // 调用 beforeUpdate 钩子
}
}
}, true /* isRenderWatcher */)
// src/core/observer/scheduler
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
// 只有满足当前 watcher 为 vm._watcher(也就是当前的渲染watcher)
// 以及组件已经 mounted 并且没有被 destroyed 才会执行 updated 钩子函数。
callHook(vm, 'updated') // 调用 updated 钩子
}
}
}
// src/instance/observer/watcher.js
export default class Watcher {
...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
// 在它的构造函数里会判断 isRenderWatcher,
// 接着把当前 watcher 的实例赋值给 vm._watcher
isRenderWatcher?: boolean
) {
// 还把当前 wathcer 实例 push 到 vm._watchers 中,
// vm._watcher 是专门用来监听 vm 上数据变化然后重新渲染的,
// 所以它是一个渲染相关的 watcher,因此在 callUpdatedHooks 函数中,
// 只有 vm._watcher 的回调执行完毕后,才会执行 updated 钩子函数
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
...
}
#beforeDestroy和destroyed
// src/core/instance/lifecycle.js
// 在 $destroy 的执行过程中,它会执行 vm.__patch__(vm._vnode, null)
// 触发它子组件的销毁钩子函数,这样一层层的递归调用,
// 所以 destroy 钩子函数执行顺序是先子后父,和 mounted 过程一样。
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy') // 调用 beforeDestroy 钩子
vm._isBeingDestroyed = true
// 一些销毁工作
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// 拆卸 watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
...
vm._isDestroyed = true
// 调用当前 rendered tree 上的 destroy 钩子
// 发现子组件,会先去销毁子组件
vm.__patch__(vm._vnode, null)
callHook(vm, 'destroyed') // 调用 destroyed 钩子
// 关闭所有实例侦听器。
vm.$off()
// 删除 __vue__ 引用
if (vm.$el) {
vm.$el.__vue__ = null
}
// 释放循环引用
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}