- 例子1
- new Vue做了啥?
- new Vue做了啥,源码解析
- initMixin函数 初始化 – 初始化Vue实例的配置
- initLifecycle函数 – 初始化生命周期钩子函数
- initEvents – 初始化事件系统
- 初始化渲染 initRender
- 初始化inject选项
例子1
<div id="app"><div class="home"><h1>{{title}}</h1></div>
</div>
new Vue({el: '#app',data: () => {return {title: 'Home Page'}}
})
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
new Vue()做了啥?
在Vue.js 2.x的源码中,new Vue()
的主要作用是创建一个Vue实例。当我们调用new Vue()
时,会经过以下几个步骤:
- 初始化Vue实例的配置:Vue会将我们传入的配置对象进行初始化,包括数据、计算属性、方法等。
- 初始化生命周期钩子函数:Vue会在初始化过程中执行一系列的生命周期钩子函数,例如
beforeCreate
、created
、beforeMount
、mounted
等。 - 初始化事件系统:Vue会为实例创建一个事件总线,用于处理事件的监听和触发。
- 解析模板:如果配置中指定了
template
选项,Vue会将模板解析成渲染函数,以便后续的渲染过程中使用。 - 初始化渲染:Vue会创建一个虚拟DOM,并将之前解析的渲染函数进行渲染,生成真实DOM,并将其挂载到页面上。
- 数据响应式处理:Vue会对实例中的数据进行响应式处理,即通过劫持数据的访问和修改,实现数据的双向绑定。
在这个过程中,Vue还会做很多其他的工作,例如处理指令、组件注册、依赖收集等,以确保整个应用的正常运行和响应式更新。
以上只是对new Vue()
的简单概述,具体的实现细节涉及到很多源码内容,在Vue.js的源码中可以详细了解其中的实现原理。
new Vue()做了啥,源码解析
vue源码版本vue2.5.2
new Vue()会执行_init方法,而_init方法在initMixin函数中定义。
src/core/instance/index.js文件中定义了Vue
function Vue (options) {this._init(options)
}initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)export default Vue
initMixin函数 初始化 – 初始化Vue实例的配置
- const vm: Component = this;定义vm等于Vue;
- vm._uid = uid++; vm存在_uid,自增
- vm._isVue = true;只有是经过vue实例化的。vm都存在_isVue属性为true值
- options && options._isComponent 当options存在_isComponent属性时,执行initInternalComponent(vm, options)
- 当组件options._isComponen不存在时,执行 mergeOptions( resolveConstructorOptions(vm.constructor), options || {},vm )
- m._renderProxy = vm;给vm定义一个_renderProxy 属性等于自身
- vm._self = vm;给vm定义一个_self 属性等于自身
- initLifecycle(vm);初始化生命周期相关实例属性
- initEvents(vm);初始化事件
- initRender(vm);定义$createElement() createElement的执行过程
- callHook(vm, ‘beforeCreate’);执行beforeCreate生命周期钩子
- initInjections(vm);
- initState(vm);将data代理至Vue._data data的代理
- initProvide(vm);初始化provide
- callHook(vm, ‘created’);执行created生命周期钩子
- vm. m o u n t ( v m . mount(vm. mount(vm.options.el)执行挂载
定义的 Vue.prototype._init
export function initMixin (Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uid,自增IDvm._uid = uid++vm._isVue = true// merge options 合并optionsif (options && options._isComponent) {initInternalComponent(vm, options)} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}vm._renderProxy = vm// expose real selfvm._self = vm// 初始化生命周期相关实例属性initLifecycle(vm)//初始化事件initEvents(vm)//定义$createElement() createElement的执行过程initRender(vm)// 执行beforeCreate生命周期钩子callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/props//将data代理至Vue._data data的代理initState(vm)// 初始化provideinitProvide(vm) // resolve provide after data/props// 执行created生命周期钩子callHook(vm, 'created')// 当options中存在el属性,则执行挂载if (vm.$options.el) {//挂载#app $mount执行过程vm.$mount(vm.$options.el)}}
}
initLifecycle函数 – 初始化生命周期钩子函数
initLifecycle 初始化生命周期相关变量即在vue实例上挂载一些属性并设置默认值,如
- p a r e n t , parent, parent,root,
- $children,
- $ref,vm._watcher,
- vm.__inactive,
- vm._directInactive,
- vm._isMounted,
- vm._isDestroyed,
- vm._isBeingDestroyed
export function initLifecycle (vm: Component) {const options = vm.$options// locate first non-abstract parentlet parent = options.parent//当前组件存在父级并且当前组件不是抽象组件if (parent && !options.abstract) {//通过while循环来向上循环,如果当前组件的父级是抽象组件并且也存在父级,那就继续向上查找当前组件父级的父级while (parent.$options.abstract && parent.$parent) {//更新parentparent = parent.$parent}//把该实例自身添加进找到的父级的$children属性中parent.$children.push(vm)}//直到找到第一个不是抽象类型的父级时,将其赋值vm.$parentvm.$parent = parent//设置实例根元素。判断如果当前实例存在父级,那么当前实例的根实例$root属性就是其父级的根实例$root属性,如果不存在,那么根实例$root属性就是它自己vm.$root = parent ? parent.$root : vmvm.$children = []vm.$refs = {}vm._watcher = nullvm._inactive = nullvm._directInactive = false//实例是否已挂载vm._isMounted = false//实例是否已销毁vm._isDestroyed = false//实例是否正准备销毁vm._isBeingDestroyed = false
}
合并options之后
initEvents – 初始化事件系统
initEvents 初始化事件。 初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件。
export function initEvents (vm: Component) {
//在vm上新增_events属性并将其赋值为空对象,用来存储事件。vm._events = Object.create(null)vm._hasHookEvent = false// init parent attached events//获取父组件注册的事件赋给listeners,const listeners = vm.$options._parentListeners//如果listeners不为空,则调用updateComponentListeners函数,将父组件向子组件注册的事件注册到子组件的实例中if (listeners) {updateComponentListeners(vm, listeners)}
}
初始化渲染 initRender
initRender函数 初始化渲染.。
export function initRender (vm: Component) {vm._vnode = null // the root of the child treevm._staticTrees = null // v-once cached treesconst options = vm.$optionsconst parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent treeconst renderContext = parentVnode && parentVnode.context//实例上插槽vm.$slots = resolveSlots(options._renderChildren, renderContext)vm.$scopedSlots = emptyObject//在实例上定义_c函数和$_createElement函数vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)const parentData = parentVnode && parentVnode.datadefineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)}
初始化inject选项
inject 选项中的每一个数据key都是由其上游父级组件提供的,所以我们应该把每一个数据key从当前组件起,不断的向上游父级组件中查找该数据key对应的值,直到找到为止。
如果在上游所有父级组件中没找到,那么就看在inject 选项是否为该数据key设置了默认值,如果设置了就使用默认值,如果没有设置,则抛出异常。
export function initInjections (vm: Component) {
//调用resolveInject把inject选项中的数据转化成键值对的形式赋给resultconst result = resolveInject(vm.$options.inject, vm)if (result) {toggleObserving(false)//遍历result中的每一对键值Object.keys(result).forEach(key => {//调用defineReactive函数将其添加当前实例上defineReactive(vm, key, result[key])})toggleObserving(true)}
}
注意,在把result中的键值添加到当前实例上之前,会先调用toggleObserving(false),而这个函数内部是把shouldObserve = false,这是为了告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式
resolveInject函数内部是如何把inject 选项中数据转换成键值对的。
export function resolveInject (inject: any, vm: Component): ?Object {if (inject) {// inject is :any because flow is not smart enough to figure out cached//创建一个空对象result,用来存储inject 选项中的数据key及其对应的值,作为最后的返回结果。const result = Object.create(null)const keys = hasSymbol? Reflect.ownKeys(inject).filter(key => {/* istanbul ignore next */return Object.getOwnPropertyDescriptor(inject, key).enumerable}): Object.keys(inject)for (let i = 0; i < keys.length; i++) {const key = keys[i]//provideKey就是上游父级组件提供的源属性const provideKey = inject[key].fromlet source = vm//while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中while (source) {if (source._provided && hasOwn(source._provided, provideKey)) {result[key] = source._provided[provideKey]break}source = source.$parent}if (!source) {//是否有default属性,如果有的话,则拿到这个默认值,官方文档示例中说了,默认值可以为一个工厂函数,所以当默认值是函数的时候,就去该函数的返回值,否则就取默认值本身。如果没有设置默认值,则抛出异常。if ('default' in inject[key]) {const provideDefault = inject[key].defaultresult[key] = typeof provideDefault === 'function'? provideDefault.call(vm): provideDefault} else if (process.env.NODE_ENV !== 'production') {warn(`Injection "${key}" not found`, vm)}}}return result}
}
官方文档中说inject 选项可以是一个字符串数组,也可以是一个对象,在上面的代码中只看见了处理当为对象的情况,那如果是字符串数组呢?怎么没有处理呢?
其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject
参考文档:
https://blog.csdn.net/weixin_40119412/article/details/128880336