初学vue,你得晓得咱们是从new Vue
开始的:
new Vue({ el: '#app', data: obj, ...})
那你感觉是不是很有意思,咱们new Vue
之后,就能够应用他那么多的性能,可见Vue是暴进去的一个一个性能类函数,咱们进入源码一探到底:
import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'//判断是不是服务端渲染import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'/** * 增加全局的API */initGlobalAPI(Vue)/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})/** * 服务端渲染须要 */Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})/** * vue版本号 这里的'__VERSION__'为占位符,公布版本时将被主动替换 */Vue.version = '__VERSION__'export default Vue
那么咱们看到咱们的外围Vue
来自'./instance/index'
那咱们再去一探到底,可想而知外面必然有一个Vue函数类
import { initMixin } from './init'import { stateMixin } from './state'import { renderMixin } from './render'import { eventsMixin } from './events'import { lifecycleMixin } from './lifecycle'import { warn } from '../util/index'// Vue构造函数必须应用new关键字实例化, 否则会抛出一个正告, 实例化Vue的时候会调用_init办法初始化// 这里options也是.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)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue
能够看到外面有一个function Vue
性能类,而且外面加载了initMixin
,stateMixin
等,这几个办法别离传入了Vue来初始化一些性能。
另外咱们能够在入口文件出看到initGlobalAPI
这个办法,那么咱们关上initGlobalAPI
所在的地位:./global-api/index
......export function initGlobalAPI (Vue: GlobalAPI) { // config // 提供获取配置项的办法, 不容许在这里设置配置项 const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object.defineProperty(Vue, 'config', configDef) // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. // 注册全局工具API, 只对Vue失效。 Vue.util = { warn, extend, mergeOptions, defineReactive } //定义全局属性 Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 初始化options Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. // 用_base属性来挂载Vue结构器 Vue.options._base = Vue extend(Vue.options.components, builtInComponents) //定义全局办法 initUse(Vue) // Vue.use initMixin(Vue) // Vue.mixin initExtend(Vue) // Vue.extend initAssetRegisters(Vue)}
可见暴露出多个办法给全局,而Vue.util是一些工具办法:
import config from '../config'import { initUse } from './use'import { initMixin } from './mixin'import { initExtend } from './extend'import { initAssetRegisters } from './assets'import { set, del } from '../observer/index'import { ASSET_TYPES } from 'shared/constants'import builtInComponents from '../components/index'import { warn, extend, nextTick, mergeOptions, defineReactive} from '../util/index'
那么咱们回到Vue性能函数类上:
import { initMixin } from './init'import { stateMixin } from './state'import { renderMixin } from './render'import { eventsMixin } from './events'import { lifecycleMixin } from './lifecycle'import { warn } from '../util/index'// Vue构造函数必须应用new关键字实例化, 否则会抛出一个正告, 实例化Vue的时候会调用_init办法初始化// 这里options也是.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)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue
咱们能够看到initMixin(Vue)
执行了,那么咱们去读一下init的源码:
参考Vue3源码视频解说:进入学习
import config from '../config'import { initProxy } from './proxy'import { initState } from './state'import { initRender } from './render'import { initEvents } from './events'import { mark, measure } from '../util/perf'import { initLifecycle, callHook } from './lifecycle'import { initProvide, initInjections } from './inject'import { extend, mergeOptions, formatComponentName } from '../util/index'let uid = 0// 向Vue.prototype增加_init办法, 用于new Vue时 初始化一些配置项// 次要的性能是合并配置项, 初始化生命周期, 事件, 组件, 指令等等export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid // 应用uid来辨别不同的vue实例, 以便前面的缓存性能应用 vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // 如果是vue实例,则不须要被observe vm._isVue = true // 1、解决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(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } //2、renderProxy if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self // 顺次初始化配置项, 并调用生命周期钩子 vm._self = vm initLifecycle(vm) // 事件监听初始化 initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props //初始化vm状态 prop/data/computed/watch实现初始化 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // 用于性能监控 /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } // 配置项里有el属性, 则会挂载到实在DOM上, 实现视图的渲染 if (vm.$options.el) { vm.$mount(vm.$options.el) } }}
浏览init.js源码之后,咱们能够看到其实就是这样一个程序:
- option参数
- renderProxy代理
- vm的生命周期相干变量初始化
- vm的事件监听
- 初始化vm的状态
- render & $mount
在Vue的原型上挂了一个_init办法,也就是说,咱们执行new Vue(options)
的时候就会执行这个办法,并且咱们传过来的options能够通过vm.$options拜访到,那么mergeOptions()
外部原理又是啥呢,而且咱们看到他接管到两个局部resolveConstructorOptions()
,options
并且交融在一起,那么咱们先钻研resolveConstructorOptions()
:
export function resolveConstructorOptions (Ctor: Class<Component>) { let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options}
那么问题又来了,let options = Ctor.options
中的Ctor
又是什么货色呢?咱们能够看到上面extend(Ctor.extendOptions, modifiedOptions)
那咱们去找extend
:
import { ASSET_TYPES } from 'shared/constants'import { defineComputed, proxy } from '../instance/state'import { extend, mergeOptions, validateComponentName } from '../util/index'export function initExtend (Vue: GlobalAPI) { /** * Each instance constructor, including Vue, has a unique * cid. This enables us to create wrapped "child * constructors" for prototypal inheritance and cache them. */ // 为每个构造函数调配一个惟一的cid, 以便用于缓存 Vue.cid = 0 let cid = 1 /** * Class inheritance */ // 生成Vue结构器的子类 Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} // 如果cid已存在, 则利用缓存, 间接返回缓存的结构器 const Super = this const SuperId = Super.cid const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } // 创立一个蕴含_init办法的子类, 并赋予惟一的cid const Sub = function VueComponent (options) { this._init(options) } Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ // 合并配置项 Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. // 将props和computed都代理到vm实例上 if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // allow further extension/mixin/plugin usage // 增加extend,mixin,use这些API Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // 将组件实例本身也挂载为一个属性, 用于递归组件 // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub }}
那我么找到了Vue.extend
,能够看进去这不就是实现了一个继承嘛。Sub
继承自super
,而后return
进来。
所以resolveConstructorOptions
就做两件事
Ctor.super
来判断该类是否是Vue的子类if (superOptions !== cachedSuperOptions)
来判断父类中的options
有没有发生变化
那么咱们晓得了resolveConstructorOptions
与new Vue(options)
,咱们的mergeOptions函数就是把他们合并的:
// 合并组件的配置项export function mergeOptions ( parent: Object, child: Object, vm?: Component): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } // 序列化props, inject, directive normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) const extendsFrom = child.extends if (extendsFrom) { parent = mergeOptions(parent, extendsFrom, vm) } // child的mixins退出parent中 if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } // 应用strat中的合并办法去顺次合并配置对象 const options = {} let key for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options}
那么咱们写的一些组件和个性全副放在vm.$options
外面了,那么下一步也就是renderProxy
咱们有趣味的童鞋,能够根据援用门路
去看看实现原理。
那么咱们initMixin
函数执行完前两步之后须要执行的几个函数别离为
// 顺次初始化配置项, 并调用生命周期钩子 vm._self = vm initLifecycle(vm) // 事件监听初始化 initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props //初始化vm状态 prop/data/computed/watch实现初始化 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
那咱们先来理解一下这几个函数别离干了啥:
1、initLifecycle
export function initLifecycle (vm: Component) { 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(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}
2、initEvents
export function initEvents (vm: Component) { vm._events = Object.create(null) vm._hasHookEvent = false // init parent attached events const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) }}
3、initRender
export function initRender (vm: Component) { vm._vnode = null // the root of the child tree vm._staticTrees = null // v-once cached trees const options = vm.$options const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree const renderContext = parentVnode && parentVnode.context vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) }}
4、callback
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`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) }}
那咱们在initRender
中能够发现初始化了一些性能,例如$lintener
,$createElement
,而在callback
外面却间接调用了beforeCreate钩子函数。
前面还有
5、initInjections
,resolveInject
,initProvide
export function initInjections (vm: Component) { const result = resolveInject(vm.$options.inject, vm) if (result) { toggleObserving(false) Object.keys(result).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(vm, key, result[key]) } }) toggleObserving(true) }}
export function resolveInject (inject: any, vm: Component): ?Object { if (inject) { // inject is :any because flow is not smart enough to figure out cached 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] const provideKey = inject[key].from let source = vm while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey] break } source = source.$parent } if (!source) { if ('default' in inject[key]) { const provideDefault = inject[key].default result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault } else if (process.env.NODE_ENV !== 'production') { warn(`Injection "${key}" not found`, vm) } } } return result }}
export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide }}
那么对于这三个函数是干嘛的?咱们先来理解一下:
provide/inject:
这对选项须要一起应用,以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效。如果你相熟 React,这与 React 的上下文个性很类似。
那就是相当于初始化依赖的关系,而initProvide
根本没有什么内容,就是将$options
里的provide
赋值到以后实例上
写到这里那么激动人心的时刻到了--initState
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) // 如果选项中不存在data,调用observe来观测一个空对象 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) // 这里须要额定判断一下opts.watch !== nativeWatch // 是因为在Firefox下, Object实例上会有一个自带的watch属性, 须要判断这个watch必须是vue提供的watch if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }}
能够看出initState
外面初始化了props
,data
,computed
,watch
,methods
那么咱们次要看一下initData
,initProps
的代码:
/** * data初始化, 和props基本一致 */function initData (vm: Component) { let data = vm.$options.data /** * data字段有两种可选类型 * 当data是function时, 间接执行这个function 并将返回值作为data * data也能够间接就是一个object, 但仅应该在root组件这样做, 因为间接应用data对象会导致多个雷同的组件持有同一个data对象的援用 * 而应用一个返回新对象的function就能够防止这个问题 * 详见 https://cn.vuejs.org/v2/style-guide/#%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE-%E5%BF%85%E8%A6%81 * 其实在initData之前的mergeOptions操作中曾经将data格式化为一个function * 然而在initData和mergeOptions两头还有一个生命周期钩子beforeCreate被调用 * 这里应用typeof再次判断data的类型是为了避免在beforeCreate中扭转了vm.$options.data */ data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // 避免开发者在写data选项时不按规矩返回一个对象 if (!isPlainObject(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 // 将data的每个字段都代理到vm实例上, 并判断是否与methods, props抵触, 反复key会报错 const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length // 查看是否有反复的key, 这里能够看出props优先级最高, 其次是data, 最初是methods // 真的呈现重名时,会依照优先级笼罩 while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) // isReserved用于判断一个key是否以$或者_结尾,vue不会在vm下面代理这些key } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data // 使data变为响应式 observe(data, true /* asRootData */)}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. const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted // 应用toggleObserving能够设置shouldObserve为参数的值, 这里设为false是阻止观察者响应更新, 直到props初始化完结 if (!isRoot) { toggleObserving(false) } // 遍历所有props选项, 并校验props for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(key) // 如果应用了保留属性, 这里会报错, 保留属性包含key,ref,slot,slot-scope,is if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } // isUpdatingChildComponent会在组件开始更新时置为true, 实现更新时置为false // 如果子组件中更改一个props时会导致父组件也从新渲染, 这里就会抛出正告 defineReactive(props, key, value, () => { if (vm.$parent && !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 { // 使props变为响应式属性 defineReactive(props, key, value) } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 将props都代理到vm实例上, 以便代码中能够通过this.propName去拜访到对应的props if (!(key in vm)) { proxy(vm, `_props`, key) } } // 初始化结束, 开启响应式更新 toggleObserving(true)}
上述代码实现了把props
,data
变为响应式,前面的computed
,watch
临时不讲,后续专门有一篇文章来写他们。
那么initState
之后咱们还会执行一个callback
函数,传入的是created
参数,调用钩子函数created
这个时候也就是页面曾经创立了,并且下一个生命周期为beforMount
,在讲挂载之前,肯定要讲响应式零碎原理