Vue源码分析之Vue实例领头化精解_vue,Vue源码学习之开头化模块init

这一节关键记录一下:Vue 的早先化进度

我们看看了VUE分了成都百货上千模块stateMixinlifecycleMixin,通过动用Mixin情势,都以使用了JavaScript原型世袭的准则,在Vue的原型上面扩张属性和议程。大家三番六回跟着this._金沙网址,init走,那个一点击进去就清楚了是走入了init.js文件是在initMixin函数里面给Vue原型增多的_init方法。首先来从微观察看这一个init文件,能够观看首假诺导出了两个函数:initMixin和resolveConstructorOptions,具体功能我们一步步来谈谈。咋的一看那么些文件,只怕有一点童鞋会看不通晓函数参数括号里面写的是哪些鬼,这几个实乃行使了flow的档案的次序检查,具体flow的利用这里就不介绍了,风野趣的请移步:

Vue官方网址的生命周期图示表

我们以后来看率先个函数initMixin,Vue实例在起头化的时候就调用了那些函数,

重在说一下 new Vue(卡塔尔(قطر‎后的开始化阶段,约等于created以前产生了怎么着。

let uid = 0export function initMixin (Vue: Class) { Vue.prototype._init = function  { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ 【**注:istanbul 是代码覆盖率检测工具,此注释为代码测试用**】 if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-init:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark } // 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 } else { vm.$options = mergeOptions( resolveConstructorOptions, options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle initRender callHook initInjections // resolve injections before data/props initState // resolve provide after data/props callHook /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName mark measure(`${vm._name} init`, startTag, endTag) } if  { vm.$mount } }}

initLifecycle 阶段

Vue源码分析之Vue实例领头化精解_vue,Vue源码学习之开头化模块init。大家针对宏观简化原则,这一个函数里面后边有七个if判定工作大家能够先不细化探究,大约第二个是用performance做品质监测,第一个联合option,第多少个是做代办拦截,是ES6新特点,可参照阮一峰大神关于proxy的牵线【

export function initLifecycle  { const options = vm.$options // locate first non-abstract parent let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push // 自己把自己添加到父级的$children数组中 } 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}
initLifecycle //生命周期变量初始化initEvents //事件监听初始化initRender //初始化渲染callHook //回调钩子beforeCreateinitInjections //初始化注入initState // prop/data/computed/method/watch状态初始化initProvide // resolve provide after data/propscallHook //回调钩子created/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName mark measure(`${vm._name} init`, startTag, endTag)}if  { vm.$mount}

接下去是init伊夫nts 阶段

这里来三个插曲start

// v-on如果写在平台标签上如:div,则会将v-on上注册的事件注册到浏览器事件中// v-on如果写在组件标签上,则会将v-on注册的事件注册到子组件的事件系统// 子组件在初始化的时候,有可能接收到父组件向子组件注册的事件。// 子组件自身模板注册的事件,只要在渲染的时候才会根据虚拟DOM的对比结果// 来确定是注册事件还是解绑事件// 这里初始化的事件是指父组件在模板中使用v-on注册的事件添加到子组件的事件系统也就是vue的事件系统。export function initEvents  { vm._events = Object.create // 初始化 vm._hasHookEvent = false // init parent attached events 初初始化腹肌组件添加的事件 const listeners = vm.$options._parentListeners if  { updateComponentListeners }}export function updateComponentListeners ( vm: Component, listeners: Object, oldListeners: ?Object) { target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm) target = undefined}

V2.1.8及从前的版本】这里相比有利明白在生命周期created之后再做render,那么在created以前就不也许获得DOM。那也是在有个别源码剖析文章里面比较轻巧见到的深入分析,也是不错的

initjections 阶段

initLifecyclecallHookinitStatecallHookinitRender 
export function initInjections  { // 自下而上读取inject const result = resolveInject(vm.$options.inject, vm) if  { // 设置为false 避免defineReactive函数把数据转换为响应式 toggleObserving Object.keys.forEach(key => { defineReactive }) // 再次更改回来 toggleObserving }}export function resolveInject (inject: any, vm: Component): ?Object { if  { // inject is :any because flow is not smart enough to figure out cached const result = Object.create // 如果浏览器支持Symbol,则使用Reflect.ownkyes const keys = hasSymbol ? Reflect.ownKeys : Object.keys for (let i = 0; i < keys.length; i++) { const key = keys[i] // #6574 in case the inject object is observed... if  continue const provideKey = inject[key].from let source = vm // 当provided注入内容的时候就是把内容注入到当前实例的_provided中 // 刚开始的时候 source就是实本身,挡在source._provided中找不到对应的值 // 就会把source设置为父实例 // Vue实例化的第一步就是规格化用户传入的数据,所以inject不管时数组还是对象 // 最后都会变成对象 while  { if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey] break } source = source.$parent } // 处理默认值的情况 if  { if ('default' in inject[key]) { const provideDefault = inject[key].default result[key] = typeof provideDefault === 'function' ? provideDefault.call : provideDefault } else if (process.env.NODE_ENV !== 'production') { warn(`Injection "${key}" not found`, vm) } } } return result }}

v2.1.9及之后的本子】但到这里一齐先就懵逼了比较久render提到beforeCreate早先去了,这岂不是DOM在beforeCreate在此之前就会获得到了?显明不对了,请留神render尽管提前了,不过前边多了贰个if那一个if里面才拿走DOM的关键,这一个if在2.1.8版本在此以前是在render函数里面包车型地铁,在2.1.9事后被建议来,然后render函数提前了,至于怎么提前暂未通晓,此处只是踩了二个看此外源码深入分析差别版本带给的坑!

initState 阶段

initLifecycleinitRendercallHookinitStatecallHookif  { vm.$mount}

在 Vue 中,大家日常会用到 props 、methods 、 watch 、computed 、data
。那个情状在接受前都亟待开始化。而起头化的历程便是在 initState
阶段达成。

1.initLifecycle

因为 injects 是在 initState 以前产生,所以能够在 State 中运用 injects 。

function initLifecycle  { const options = vm.$options // locate first non-abstract parent let parent = options.parent //我理解为父实例或者父组件 if (parent && !options.abstract) { //例子中没有parent,断点代码的时候自动跳过 while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push } 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}
export function initState  { vm._watchers = [] // 获取到经过初始化的用户传进来的options const opts = vm.$options if  initProps if  initMethods if  { initData } else { observe(vm._data = {}, true /* asRootData */) } if  initComputed if (opts.watch && opts.watch !== nativeWatch) { initWatch }}

function initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. // 缓存props的key值 const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted // 如果不是跟组件则没必要转换成响应式数据 if  { // 控制是否转换成响应式数据 toggleObserving } for (const key in propsOptions) { keys.push // 获取props的值 const value = validateProp(key, propsOptions, propsData, vm) defineReactive // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 把props代理到Vue实例上来,可以直接通过this.props访问 if  { proxy } } toggleObserving}

那一个函数主若是有父实例的图景下管理vm.$parent和vm.$children那俩个实例属性,笔者这里未有就跳过,其余的正是新添了一些实例属性

initMethods

2.initEvents

function initMethods (vm: Component, methods: Object) { const props = vm.$options.props for  { if (process.env.NODE_ENV !== 'production') { // 如果key不是一个函数 报错 if (typeof methods[key] !== 'function') { warn( `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm ) } // 如果props中存在同名的属性 报错 if (props && hasOwn { warn( `Method "${key}" has already been defined as a prop.`, vm ) } // isReserved判断是否以$或_开头 if  && isReserved { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.` ) } } // 把methods的方法绑定到Vue实例上 vm[key] = typeof methods[key] !== 'function' ? noop : bind }}

function initData  { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData : data || {} // isPlainObject监测data是不是对象 if  { 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 const props = vm.$options.props const methods = vm.$options.methods let i = keys.length // 循环data while  { const key = keys[i] if (process.env.NODE_ENV !== 'production') { // 如果在methods中存在和key同名的属性 则报错 if (methods && hasOwn { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } // 如果在props中存在和key同名的属性 则报错 if (props && hasOwn { 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判断是否以$或_开头 // 代理data,使得可以直接通过this.key访问this._data.key proxy } } // observe data // 把data转换为响应式数据 observe(data, true /* asRootData */)}
function initEvents  { vm._events = Object.create vm._hasHookEvent = false // init parent attached events const listeners = vm.$options._parentListeners if  { updateComponentListeners }}

initComputed

又新扩展八个属性,前面那三个if条件里面是有父组件的平地风波时最初化,推断正是props和events父亲和儿子组件通讯的事件源委。

const computedWatcherOptions = { lazy: true }function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create // computed properties are just getters during SSR // 判断是不是服务端渲染 const isSSR = isServerRendering() 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}".`, vm ) } // 如果不是ssr,则创建Watcher实例 if  { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. // 如果vm不存在key的同名属性 if  { defineComputed } else if (process.env.NODE_ENV !== 'production') { if  { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } }}sharedPropertyDefinition = { enumerable: true, cnfigurable: true, get: noop, set: noop}export function defineComputed ( target: any, key: string, userDef: Object | Function) { // 如果是服务端渲染,则computed不会有缓存,因为数据响应式的过程在服务器是多余的 const shouldCache = !isServerRendering() // createComputedGetter返回计算属性的getter // createGetterInvoker返回userDef的getter if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter : createGetterInvoker sharedPropertyDefinition.set = noop } else { // 当userDef为一个对象时 sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter : createGetterInvoker : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } // 在tearget上定义一个属性, 属性名为key, 属性描述符为sharedPropertyDefinition Object.defineProperty(target, key, sharedPropertyDefinition)}function createComputedGetter  { return function computedGetter () { // 查找是否存在key的Watcher const watcher = this._computedWatchers && this._computedWatchers[key] if  { // 如果dirty为true,则重新计算,否则返回缓存 if  { watcher.evaluate { watcher.depend() } return watcher.value } }}function createGetterInvoker { return function computedGetter () { return fn.call }}

function initWatch (vm: Component, watch: Object) { for  { const handler = watch[key] // 处理数组类型 if (Array.isArray { for (let i = 0; i < handler.length; i++) { createWatcher } } else { createWatcher } }}function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object) { // isPlainObject检查是否是对象 if (isPlainObject { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } // 最后调用$watch return vm.$watch(expOrFn, handler, options)}

3.initRender

initProvide阶段

function initRender  { vm._vnode = null // the root of the child tree vm._staticTrees = null const parentVnode = vm.$vnode = vm.$options._parentVnode const renderContext = parentVnode && parentVnode.context vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext) vm.$scopedSlots = emptyObject vm._c =  => createElement(vm, a, b, c, d, false) vm.$createElement =  => createElement const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, '$attrs', parentData && parentData.attrs, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) defineReactive(vm, '$listeners', vm.$options._parentListeners, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { defineReactive(vm, '$attrs', parentData && parentData.attrs, null, true) defineReactive(vm, '$listeners', vm.$options._parentListeners, null, true) }}
export function initProvide  { const provide = vm.$options.provide if  { // 把provided存到_provided上 vm._provided = typeof provide === 'function' ? provide.call : provide }}

此函数也是开始化了节点属性音信,绑定createElement函数到实例,接下去调用beforeCreate回调钩子;——TODO1:后续专项论题深入分析VUE渲染逻辑

到此地 Vue 的开始化就截至了,接下去正是触发生命周期函数 created 。

4.initInjections

计算一下:new Vue(卡塔尔(قطر‎ 执行之后,Vue 步向初步化阶段。

function initInjections  { const result = resolveInject(vm.$options.inject, vm) if  { observerState.shouldConvert = false Object.keys.forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { 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 } }) observerState.shouldConvert = true }}

规格化 $options ,也正是客商自定义的多寡 initLifecycle 注入生命周期
initEvents 开端化事件,注意:这里的平地风波是值在父组件在子组件上定义的事件
initRender initjections 早先化 jetction initProps 初始化props initState
满含props 、methods 、data 、computed 、watch initProvided 初步化
provide

此函数也是当有inject属性时做拍卖,源码例子无inject断点跑近年来跳过

总结

5.initState

如上正是那篇小说的全部内容了,希望本文的原委对大家的上学大概干活有所自然的参阅学习价值,感谢我们对台本之家的支撑。

function initState  { vm._watchers = [] const opts = vm.$options if  initProps if  initMethods if  { initData } else { observe(vm._data = {}, true /* asRootData */) } if  initComputed if (opts.watch && opts.watch !== nativeWatch) { initWatch }}

能够看见此处是对options传入的props/methods/data/computed/watch属性做早先化————TODO2:深入分析各类属性的伊始化

6.initProvide

function initProvide  { const provide = vm.$options.provide if  { vm._provided = typeof provide === 'function' ? provide.call : provide }}

那一个函数跟4.initInjections在同二个inject.js中,也是在传诵参数有provide属性时做管理,一时半刻跳过,然后就到了created回调钩子,最终的vm.$mount接入TODO1;

今日initMixin到此结束,以上即是本文的全部内容,希望对大家的学习抱有助于,也指望咱们多多关照脚本之家。

发表评论

电子邮件地址不会被公开。 必填项已用*标注