共计 17591 个字符,预计需要花费 44 分钟才能阅读完成。
初学 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 的源码:
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
,在讲挂载之前,肯定要讲 响应式零碎原理