vue2源码-- 初始化阶段
Posted 在厕所喝茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码-- 初始化阶段相关的知识,希望对你有一定的参考价值。
目录
- 概述
- new vue()阶段
- initLifecycle 函数
- initEvents 函数
- initInjections 函数
- initState 函数
- initProps 函数
- initMethods 函数
- initData 函数
概述
初始化阶段所做的工作大致分为两部分:第一部分是new vue()
,也就是创建一个vue
实例;第二部分就是为创建好的vue
实例初始化一些事件、属性、响应式数据等
new vue()阶段
- 利用
instanceof
,检查当前实例是否通过new
关键字初始化的 - 调用原型链上面
_init
方法,进行初始化,这个方法是在initMixin
函数中添加上去的- 将
vue
实例赋值给vm
,并且吧用户传递的options
选项和当前构造函数的options
属性,以及父级构造函数的options
属性进行合并,得到一个新的属性,并挂载到vm.$options
上 - 通过调用一些初始化函数来
vue
实例初始化一些属性,事件,响应式数据等- initLifecycle,初始化组件实例关系属性:挂载一些属性, p a r e n t , parent, parent,root,$children 等
- initEvents,初始化自定义事件:将父组件向子组件注册的事件注册到子组件的实例中
- initRender,初始化渲染相关的: s l o t s , slots, slots,scopedSlots,createElement, a t t r s , attrs, attrs,listeners 等数据
- 调用
beforeCreate
生命周期函数 - initInjections,初始化 inject
- initState,初始化 props,methods,data,computed,watch
- initProvide,初始化 provide
- 调用
created
生命周期
- 所有初始化工作完成之后,最后,会判断用户是否传入了
el
选项,如果传入了,则调用$mounted
函数进入模板编译和挂载阶段。如果没有传入,则不进入下一个生命周期阶段,需要用户手动调用$mounted
函数才会进入到下一个生命周期阶段
- 将
源码:
// vue是一个函数,通过new去实例化
function Vue(options)
// 一定要通过new去实例化vue
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);
export function initMixin(Vue: Class<Component>)
// vue初始化的时候主要有:合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化data,props,computed,watcher等
Vue.prototype._init = function (options?: Object)
const vm: Component = this;
// 唯一标识
vm._uid = uid++;
// 标识是vue实例,避免被数据劫持
vm._isVue = true;
// merge options
if (options && options._isComponent)
// 组件的合并策略
initInternalComponent(vm, options);
else
// 用户传递的options,当前构造函数的options,父级构造函数的options
// 合并配置之后挂载到$options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || ,
vm
);
// 合并之后
// vm.$options =
// components: ,
// created: [
// function created()
// console.log('parent created')
//
// ],
// directives: ,
// filters: ,
// _base: function Vue(options)
// // ...
// ,
// el: "#app",
// render: function (h)
// //...
//
//
if (process.env.NODE_ENV !== "production")
// 这个initProxy是代理vm上面的数据,数据被设置之后,会做相应的检查,不符合要求的会报错警告
initProxy(vm);
else
vm._renderProxy = vm;
vm._self = vm;
// 初始化组件实例关系属性:挂载一些属性,$parent,$root,$children等
initLifecycle(vm);
// 初始化自定义事件:将父组件向子组件注册的事件添加到子组件实例的_events中
// 初始化事件函数initEvents实际上初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件。
initEvents(vm);
// 初始化渲染相关的:$slots,$scopedSlots,createElement,$attrs,$listeners等数据
initRender(vm);
// 调用生命周期函数
callHook(vm, "beforeCreate");
// 初始化inject
initInjections(vm); // resolve injections before data/props
// 初始化props,methods,data,computed,watch
initState(vm);
// 初始化provide
initProvide(vm); // resolve provide after data/props
// 调用生命周期
callHook(vm, "created");
// 如果传入了el参数
if (vm.$options.el)
// 挂载dom
vm.$mount(vm.$options.el);
;
initLifecycle 函数
这个函数主要是初始化一些属性包括以$
开头给外部使用的属性,以_
开头给内部使用的属性
查找当前组件的直接父组件(不能是抽象组件,会直接跳过抽象组件),并把自身实例添加到父组件的$children
中。添加$root
根节点组件
源码:
// 给vue实例挂载一些属性,并设置了默认值
export function initLifecycle(vm: Component)
const options = vm.$options;
// 挂载$parent属性
let 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;
initEvents 函数
该函数内部实际上会在实例上面新增一个_events
属性的空对象,用来存储事件,接着调用updateComponentListeners
函数,将父组件向子组件注册的事件注册到子组件实例的_events
对象中
模板编译解析的时候,会调用processAttrs
方法解析标签中的属性,如果是指令,会解析出属性的修饰符,然后如果判断出是事件指令,就会根据修饰符对事件名做处理,如果是浏览器原生事件,就添加到nativeEvents
属性中,如果是自定义事件,就会添加到events
属性中,并把回调函数的字符串保留到对应的事件中。最后在创建组件的时候,把自定义事件传给子组件,在子组件实例化的时候进行初始化;而浏览器原生事件是在父组件中处理。
换句话说:实例初始化阶段调用的初始化事件函数 initEvents 实际上初始化的是父组件在模板中使用 v-on 或@注册的监听子组件内触发的事件
源码:
// 父组件给子组件的注册事件中,把自定义事件传给子组件,在子组件实例化的时候进行初始化;而浏览器原生事件是在父组件中处理。
export function initEvents(vm: Component)
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
// 获取父组件v-on监听器事件
const listeners = vm.$options._parentListeners;
if (listeners)
// 将父组件向子组件注册的事件添加到子组件实例的_events中
updateComponentListeners(vm, listeners);
initInjections 函数
该函数主要是从当前组件起,不断向上游父级组件的_provided
属性中查找是否提供了该数据对应的值,直到找到为止。如果没有找到,就看是否提供了默认值,有就使用默认值,没有就抛出异常。最后把查找出来的key-val
键值对添加到当前实例上
源码:
export function initInjections(vm: Component)
// 获取规范格式后的inject对象
const result = resolveInject(vm.$options.inject, vm);
if (result)
// 把shouldObserve 置为false,
// 目的是为了告诉observe函数仅仅把键值对加到当前实例,不需要转化为响应式
// 这也响应了的文档的,provide 和 inject 绑定并不是可响应的
// 当然,如果传入了一个可监听的对象,那么该对象的属性还是可响应的
toggleObserving(false);
Object.keys(result).forEach((key) =>
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production")
defineReactive(vm, key, result[key], () =>
// 禁止修改inject的值,如果inject的是对象,修改对象里面的属性不会报错
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "$key"`,
vm
);
);
else
defineReactive(vm, key, result[key]);
);
// 恢复shouldObserve为true
toggleObserving(true);
initState 函数
该函数首先添加一个_watchers
的属性,用来存储当前实例上面的所有watcher
实例
注意:从 Vue 2.0 版本起,Vue 不再对所有数据都进行侦测,而是将侦测粒度提高到了组件层面,对每个组件进行侦测,所以在每个组件上新增了 vm._watchers 属性,用来存放这个组件内用到的所有状态的依赖,当其中一个状态发生变化时,就会通知到组件,然后由组件内部使用虚拟 DOM 进行数据比对,从而降低内存开销,提高性能
然后开始按顺序初始化props
,methods
,data
,computed
,watcher
源码:
export function initState(vm: Component)
// 给实例添加一个_watchers,用来存储当前实例的所有watcher实例
// vue2并不会对所有数据进行拦截检测,不然数据越多,绑定的依赖就越多,会造成内存开销很大
// 所以vue2将监测粒度上升到组件层面,所以新增了一个_watchers属性
// 当一个数据发生变化时,会通知到组件,由组件内部使用虚拟dom进行数据对比
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);
initProps 函数
- 检验 props 的值是否合法。其中布尔类型的比较特殊
1、父组件没有传入prop
属性,并且也没有默认属性,就设置为false
2、父组件传入了prop
属性,属性值为空字符串或者属性名和属性值相等,并且prop
属性不存在string
类型或者boolean
类型的优先级比string
类型的优先级高,就设置为true
- 如果
prop
属性不在this
上面,就将prop
属性代理到this
上面
源码:
function initProps(vm: Component, propsOptions: Object)
// 父组件传进来的props数据
const propsData = vm.$options.propsData || ;
// 所有设置了props变量的属性都会保存一份到_props中
const props = (vm._props = );
// 缓存props对象中的key,更新的时候只需要遍历keys就可以拿到所有props的key
const keys = (vm.$options._propKeys = []);
// 判断当前组件是否为根组件
const isRoot = !vm.$parent;
// 不是根组件不需要将数据转化为响应式
// 根组件的props代理发生在vue.extend中
if (!isRoot)
toggleObserving(false);
// 遍历props的键值对
for (const key in propsOptions)
// 将建名添加到keys中
keys.push(key);
// 校验值
const value = validateProp(key, propsOptions, propsData, vm);
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production")
// 检查key是否为html的保留属性
const hyphenatedKey = hyphenate(key);
if (
isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)
)
warn(
`"$hyphenatedKey" is a reserved attribute and cannot be used as component prop.`,
vm
);
// 将key-value添加到_props
defineReactive(props, key, value, () =>
// 禁止组件修改props值
if (!isRoot && !isUpdatingChildComponent)
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "$key"`,
vm
);
);
else
defineReactive(props, key, value);
// 如果key不在当前实例的vm上
if (!(key in vm))
// 将数据到this上面
proxy(vm, `_props`, key);
toggleObserving(true);
initMethods 函数
-
检查
methods
中的每一个方法必须是一个函数 -
检查 methods 中的方法是否跟 props 中的重名
-
如果实例中有对应的 key 值,并且以_或者$开头的,提示命名不规范
-
将方法代理到 this 上面
源码:
function initMethods(vm: Component, methods: Object)
const props = vm.$options.props;
for (const key in methods)
if (process.env.NODE_ENV !== "production")
if (typeof methods[key] !== "function")
// methods里面的必须是函数
warn(
`Method "$key" has type "$typeof methods[
key
]" in the component definition. ` +
`Did you reference the function correctly?`,
vm
);
if (props && hasOwn(props, key))
// 检查methods中的方法是否跟props中的重名
warn(`Method "$key" has already been defined as a prop.`, vm);
// isReserved函数是用来判断字符串是否以_或$开头。
if (key in vm && isReserved(key))
// 如果实例中有对应的key值,并且以_或者$开头的,提示命名不规范
warn(
`Method "$key" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
);
// 将方法代理到this上面
vm[key] =
typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
initData 函数
-
获取 data 对象,如果 data 不是一个对象,则报错警告
-
检查是否跟
methods
和props
有重名的 -
将不以_或者$开头的属性代理到 this 上面
-
将
data
对象转化为响应式对象
源码:
function initData(vm: Component)
let data = vm.$options.data;
// 获取data对象
data = vm._data = typeof data === "function" ? getData(data, vm) : data || ;
if (!isPlainObject(data))
// data必须是一个对象,否则警告
data = ;
process.env.NODE_ENV !== "production" &&
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
const keys = Object.keys(data);
const props = vm.$options.props;
const methods = vm.$options.methods;
let i = keys.length;
while (i--)
const key = keys[i];
if (process.env.NODE_ENV !== "production")
// 检查是否跟methods有重名的
if (methods && hasOwn(methods, key))
warn(
`Method "$key" has already been defined as a data property.`,
vm
);
if (props && hasOwn(props, key))
// 检查是否跟props有重名的
process.env.NODE_ENV !== "production" &&
warn(
`The data property "$key" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
);
else if (!isReserved(key))
// 将不以_或者$开头的属性代理到this上面
proxy(vm, `_data`, key)以上是关于vue2源码-- 初始化阶段的主要内容,如果未能解决你的问题,请参考以下文章