Vue2.x源码:new Vue()做了啥?
vue源码版本vue2.5.2
new Vue()
做了啥?
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.prototype._init
。
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid,自增ID
vm._uid = uid++
vm._isVue = true
// merge options 合并options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
// expose real self
vm._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)
// 初始化provide
initProvide(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实例上挂载一些属性并设置默认值,如$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 parent
let parent = options.parent
//当前组件存在父级并且当前组件不是抽象组件
if (parent && !options.abstract) {
//通过while循环来向上循环,如果当前组件的父级是抽象组件并且也存在父级,那就继续向上查找当前组件父级的父级
while (parent.$options.abstract && parent.$parent) {
//更新parent
parent = parent.$parent
}
//把该实例自身添加进找到的父级的$children属性中
parent.$children.push(vm)
}
//直到找到第一个不是抽象类型的父级时,将其赋值vm.$parent
vm.$parent = parent
//设置实例根元素。判断如果当前实例存在父级,那么当前实例的根实例$root属性就是其父级的根实例$root属性,如果不存在,那么根实例$root属性就是它自己
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
初始化事件
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 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
//在实例上定义_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.data
defineReactive(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选项中的数据转化成键值对的形式赋给result
const 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].from
let 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].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
}
}
官方文档中说inject 选项可以是一个字符串数组,也可以是一个对象,在上面的代码中只看见了处理当为对象的情况,那如果是字符串数组呢?怎么没有处理呢?
其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject
初始化initState
从Vue 2.0版本起,Vue不再对所有数据都进行侦测,而是将侦测粒度提高到了组件层面,对每个组件进行侦测,所以在每个组件上新增了vm._watchers属性,用来存放这个组件内用到的所有状态的依赖,当其中一个状态发生变化时,就会通知到组件,然后由组件内部使用虚拟DOM进行数据比对,从而降低内存开销,提高性能。
export function initState (vm: Component) {
//实例上新增了一个属性_watchers,用来存储当前实例中所有的watcher实例,无论是使用vm.$watch注册的watcher实例还是使用watch选项注册的watcher实例,都会被保存到该属性中。
vm._watchers = []
const opts = vm.$options
//先判断实例中是否有props选项,如果有,就调用props选项初始化函数initProps去初始化props选项;
if (opts.props) initProps(vm, opts.props)
//先判断实例中是否有props选项,如果有,就调用props选项初始化函数initProps去初始化props选项;
if (opts.methods) initMethods(vm, opts.methods)
//接着再判断实例中是否有data选项,如果有,就调用data选项初始化函数initData去初始化data选项;如果没有,就把data当作空对象并将其转换成响应式
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
//接着再判断实例中是否有computed选项,如果有,就调用computed选项初始化函数initComputed去初始化computed选项
if (opts.computed) initComputed(vm, opts.computed)
//最后判断实例中是否有watch选项,如果有,就调用watch选项初始化函数initWatch去初始化watch选项
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
开发中有注意到我们在data中可以使用props,在watch中可以观察data和props,之所以可以这样做,就是因为在初始化的时候遵循了这种顺序,先初始化props,接着初始化data,最后初始化watch。
props处理initProps
在此之前,在合并options时已将props规范化
function initProps (vm: Component, propsOptions: Object) {
//propsData,父组件传入的真实props数据
const propsData = vm.$options.propsData || {}
//props,指向vm._props的指针,所有设置到props变量中的属性都会保存到vm._props中
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
//指向vm.$options._propKeys的指针,缓存props对象中的key,将来更新props时只需遍历vm.$options._propKeys数组即可得到所有props的key
const keys = vm.$options._propKeys = []
//当前组件是否为根组件
const isRoot = !vm.$parent
// root instance props should be converted
//如果不是,那么不需要将props数组转换为响应式的,toggleObserving(false)用来控制是否将数据转换成响应式
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
//调用validateProp函数校验父组件传入的props数据类型是否匹配并获取到传入的值value
const value = validateProp(key, propsOptions, propsData, vm)
//将键和值通过defineReactive函数添加到props(即vm._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.
//判断这个key在当前实例vm中是否存在,如果不存在,则调用proxy函数在vm上设置一个以key为属性的代码,当使用vm[key]访问数据时,其实访问的是vm._props[key]
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
methods处理initMethods
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
//如果methods中某个方法只有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
)
}
//如果methods中某个方法名与props中某个属性名重复了,就抛出异常:提示用户方法名重复了
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
//判断如果methods中某个方法名如果在实例vm中已经存在并且方法名是以_或$开头的,就抛出异常:提示用户方法名命名不规范
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
//将method绑定到实例vm上,然后通过this.xxx来访问methods选项中的xxx方法了
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
data处理initData
function initData (vm: Component) {
//获取到用户传入的data选项,赋给变量data,同时将变量data作为指针指向vm._data,然后判断data是不是一个函数,如果是就调用getData函数获取其返回值,将其保存到vm._data中。如果不是,就将其本身保存到vm._data中
let data = 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
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') {
//判断data对象中是否存在某一项的key与methods中某个属性名重复,如果存在重复,就抛出警告:提示用户属性名重复。
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
//判断是否存在某一项的key与prop中某个属性名重复,如果存在重复,就抛出警告:提示用户属性名重复
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
)
//调用proxy函数将data对象中key不以_或$开头的属性代理到实例vm上,这样,我们就可以通过this.xxx来访问data选项中的xxx数据了
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
//调用observe函数将data中的数据转化成响应式
observe(data, true /* asRootData */)
}
computed处理initComputed
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
//义了一个变量watchers并将其赋值为空对象,同时将其作为指针指向vm._computedWatchers
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
//遍历computed选项中的每一项属性,首先获取到每一项的属性值,记作userDef
const userDef = computed[key]
//判断userDef是不是一个函数,如果是函数,则该函数默认为取值器getter,将其赋值给变量getter;如果不是函数,则说明是一个对象,则取对象中的get属性作为取值器赋给变量getter
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
)
}
if (!isSSR) {
// create internal watcher for the computed property.
//判断如果不是在服务端渲染环境下,则创建一个watcher实例,并将当前循环到的的属性名作为键,创建的watcher实例作为值存入watchers对象中
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上
if (!(key in vm)) {
//不存在,调用defineComputed函数为实例vm上设置计算属性
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
//存在,则在非生产环境下抛出警告
if (key in vm.$data) {
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)
}
}
}
}
defineComputed函数 该函数接受3个参数,分别是:target、key和userDef。其作用是为target上定义一个属性key,并且属性key的getter和setter根据userDef的值来设置
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
//义了变量sharedPropertyDefinition,它是一个默认的属性描述符。
//定义了变量shouldCache,用于标识计算属性是否应该有缓存。该变量的值是当前环境是否为非服务端渲染环境,如果是非服务端渲染环境则该变量为true。也就是说,只有在非服务端渲染环境下计算属性才应该有缓存
const shouldCache = !isServerRendering()
//判断如果userDef是一个函数,则该函数默认为取值器getter
if (typeof userDef === 'function') {
//判断如果userDef是一个函数,则该函数默认为取值器getter,此处在非服务端渲染环境下并没有直接使用userDef作为getter,而是调用createComputedGetter函数创建了一个getter,这是因为userDef只是一个普通的getter,它并没有缓存功能,所以我们需要额外创建一个具有缓存功能的getter,而在服务端渲染环境下可以直接使用userDef作为getter,因为在服务端渲染环境下计算属性不需要缓存。由于用户没有设置setter函数,所以将sharedPropertyDefinition.set设置为noop
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
//如果userDef不是一个函数,那么就将它当作对象处理。在设置sharedPropertyDefinition.get的时候先判断userDef.get是否存在,如果不存在,则将其设置为noop,如果存在,则同上面一样,在非服务端渲染环境下并且用户没有明确的将userDef.cache设置为false时调用createComputedGetter函数创建一个getter赋给sharedPropertyDefinition.get。然后设置sharedPropertyDefinition.set为userDef.set函数
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
//如果用户没有设置setter的话,那么就给setter一个默认函数,这是为了防止用户在没有设置setter的情况下修改计算属性,从而为其抛出警告
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
//调用Object.defineProperty方法将属性key绑定到target上,其中的属性描述符就是上面设置的sharedPropertyDefinition
Object.defineProperty(target, key, sharedPropertyDefinition)
}
createComputedGetter
该函数是一个高阶函数,其内部返回了一个computedGetter函数,所以其实是将computedGetter函数赋给了sharedPropertyDefinition.get。当获取计算属性的值时会执行属性的getter,而属性的getter就是 sharedPropertyDefinition.get,也就是说最终执行的 computedGetter函数
function createComputedGetter (key) {
return function computedGetter () {
//储在当前实例上_computedWatchers属性中key所对应的watcher实例
const watcher = this._computedWatchers && this._computedWatchers[key]
//如果watcher存在,则调用watcher实例上的depend方法和evaluate方法,并且将evaluate方法的返回值作为计算属性的计算结果返回
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
initwatch处理
watch的几种写法
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// methods选项中的方法名
b: 'someMethod',
// 深度侦听,该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: 'someMethod',
immediate: true
},
// 调用多个回调
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
}
],
// 侦听表达式
'e.f': function (val, oldVal) { /* ... */ }
}
initWatch函数
function initWatch (vm: Component, watch: Object) {
//在函数内部会遍历watch选项
for (const key in watch) {
//拿到每一项的key和对应的值handler
const handler = watch[key]
//判断handler是否为数组,如果是数组则循环该数组并将数组中的每一项依次调用createWatcher函数来创建watcher
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
//不是数组,则直接调用createWatcher函数来创建watcher
} else {
createWatcher(vm, key, handler)
}
}
}
createWatcher 函数
function createWatcher (
vm: Component,//当前实例
keyOrFn: string | Function,//被侦听的属性表达式
handler: any,//watch选项中每一项的值
options?: Object//用于传递给vm.$watch的选项对象
) {
//判断handle是一个对象时,handler = handler.handler
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
//如果handle是一个字符串,则直接在实例上找
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(keyOrFn, handler, options)
}
分开解析-如果是一个对象
if (isPlainObject(handler)) {
//将handler对象整体记作options
options = handler
//把handler对象中的handler属性作为真正的回调函数记作handler
handler = handler.handler
}
则为以下情况
watch:{
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: 'someMethod',
immediate: true
},
}
分开解析-如果handler为一个字符串
if (typeof handler === 'string') {
handler = vm[handler]
}
则为以下情况
watch:{
// methods选项中的方法名
b: 'someMethod',
}
Vue.prototype.$watch
//src\core\instance\state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
//判断回调函数是否是一个对象
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
初始化provide
var Parent = {
provide: {
foo: 'bar'
},
// ...
}
const Provider = {
provide () {
return {
[s]: 'foo'
}
}
}
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}