上一篇:Vue2.x 源码学习准备
这一篇文章及后面的几篇文章主要看看 Vue 是什么,以及 Vue 是怎么初始化的,出于篇幅考虑,这里先看一下 initMixin
混入。
带着问题开始:我们的代码在执行 import Vue from "vue"
的时候,这个 Vue 是从哪里来的呢?
Vue 的入口
这里我们从 Vue 的入口开始抽丝剥茧:
1、在
main.js
引入的 Vue 是在入口文件src/platforms/web/entry-runtimes.js
里面暴露出来的;
2、入口文件里面的 Vue 是从src/platforms/web/runtime/index.js
里面引入的;
3、src/platforms/web/runtime/index.js
里面的 Vue 则是从src/core/index.js
里面引入的;
4、src/core/index.js
里面的 Vue 又是从src/core/instance/index.js
里面引入的;
这里的每一个文件都会对 Vue 这个对象做一些扩展,这里就不做展开,后面慢慢介绍;
在 src/core/instance/index.js
文件中
//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)
}
//往vue的原型上混入一些自定义的原型方法
initMixin(Vue) // 定义 _init 方法
stateMixin(Vue) // 定义数据相关方法 $set、$delete、$watch
eventsMixin(Vue) // 定义事件相关方法 $on, $once, $off, $emit
lifecycleMixin(Vue) // 定义 _update、$forceUpdate(强制更新)以及生命周期方法 $destroy
renderMixin(Vue) // 定义方法 $nextTick、_render(将render函数转为vnode)
export default Vue
这里 warn 信息直接告诉我们 Vue 的本质:Vue是一个构造函数,应该用“new”关键字调用 ;然后在 Vue 构造函数里面执行 this._init(options)
方法,传入初始化参数;代码最后面执行几个方法往 Vue 原型上混入一些自定义的原型方法,下面会分别说明一下。
Vue 初始化过程思维导图:
这篇主要看一下
initMixin
这个混入以及涉及到的initProxy(vm)、initLifecycle(vm)、initEvents(vm)、initRender(vm)、initInjections(vm)、initState(vm)、initProvide(vm)
方法;
文章目录
一、 初始化混入 initMixin(Vue)
在 src/core/instance/init.js
文件里面,主要为 Vue 原型绑定了 _init 方法;
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 给每个组件初始化时添加唯一标识,依次递增
vm._uid = uid++
let startTag, endTag
// mark是window的performance属性的相关方法,通过传入的相关数值,记录性能
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// 初始化后 vm 实例标记为true,其他场景会用到,例如 observer观察者
// 所有Vue的实例都被标记为_isVue,避免实例被set 或者 del
vm._isVue = true
// 合并选项
//_isComponent在 Vue 构建组件的时候才会有这个属性
if (options && options._isComponent) {
// 每一个子组件初始化都走这里,只是做一些性能优化,为vm.$options添加一些属性
initInternalComponent(vm, options)
} else {
// 初始化根实例
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
//代理初始化,不同环境不一样的初始化方法
//将 vm 实例上的属性代理到 vm._renderProxy
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm) // 初始化组件实例相关属性 $parent、$children、$root、$refs 等
initEvents(vm) // 初始化自定义事件
initRender(vm) // 挂载可以将 render函数转为vnode 的方法
callHook(vm, 'beforeCreate') //调用 beforeCreate 钩子函数
initInjections(vm) // 初始化inject
initState(vm) // 初始化 data/props等
initProvide(vm) // 初始化 provide
callHook(vm, 'created') //调用 created 钩子函数
// window的performance属性的相关方法,通过传入的相关数值,记录性能
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
//配置项有 el,自动调用 $mount 方法挂载,挂载的⽬标就 是把模板渲染成最终的 DOM
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
这一部分主要做了如下操作:
1、缓存当前的上下文到 vm 变量中;
2、为 vm 添加唯一标识;
3、打标记用来测试性能;
4、用 _isVue 值标识当前实例;
5、合并选项,区分组件实例和非组件实例;
6、初始化代理;
7、下面就是一些组件、事件、render、inject、data/props、provide 的初始化;
8、再次打标记用来测试性能;
9、el 挂载。
1、initInternalComponent
//合并组件实例的参数
// 优化内部组件实例化,因为动态选项合并非常慢,而且内部组件选项不需要特殊处理。
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
//这样做是因为它比动态枚举更快
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
用Object.create
这个函数,把组件构造函数的 options 挂载到 vm.$options
的__proto__
上,指定vm.$options
的原型;通过传入的参数 options 为 vm.$options
添加一些属性,把组件依赖父组件的 props 、listeners、children、tag 等属性挂载到 vm.$options
,方便子组件调用。
2、resolveConstructorOptions
区分 Vue 构造器和 Vue.extend
拓展器,Ctor.super
是 Vue.extend
里面定义的属性。如果是构造器则直接返回参数,如果是拓展器则执行内部代码。
//合并父构造函数的参数
export function resolveConstructorOptions (Ctor: Class<Component>) {
//构造函数上的option
let options = Ctor.options
//有super属性则说明Ctor是Vue.extend构建的子类,super指向父级构造器
if (Ctor.super) {
//递归获取父级上最新的 options(可能不止一个父级,所以要递归)
const superOptions = resolveConstructorOptions(Ctor.super)
//extend时父级默认的options
const cachedSuperOptions = Ctor.superOptions
//有可能会被 Vue.mixin 混入一些新的属性,所以这里要判断父类是否变了,变了则赋值更新
if (superOptions !== cachedSuperOptions) {
// options入参变了,修改默认参数
Ctor.superOptions = superOptions
//检查是否有任何后期修改/附加的选项(mixin)
const modifiedOptions = resolveModifiedOptions(Ctor)
// 更新基本扩展选项,一般在混入新的 options会用到
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// 选项合并,将合并结果赋值为 Ctor.options
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
//获取到执行extend之后出现变化的options项
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
//自身的options
const latest = Ctor.options
//执行Vue.extend时封装的options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
执行 extend 时,会出现 Vue.mixin 来对父级和子级混入一些参数,这个时候需要判断拓展器执行前后父级和子级的参数是否发生了变化,变了则更新最新的参数;最后返回合并后的的构造函数的options。
3、mergeOptions
在 src/core/util/options.js
文件中,这个方法的目的是合并构造函数 options 和传入的 options 这两个对象。
//合并实例参数和入参
export function mergeOptions (
parent: Object, //构造器参数
child: Object, //实例化时传入参数
vm?: Component //实例本身
): Object {
//检查组件名称是否合法,不合法则发出警告信息。
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
//如果child是function类型的话,我们取其options属性作为child
if (typeof child === 'function') {
child = child.options
}
//分别是把options中的props,inject,directives属性转换成对象的形式
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
//只有合并选项具有_base属性
//当传入的options里有mixin或者extends属性时,再次调用mergeOptions方法合并mixins和extends里的内容到实例的构造函数options上
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
//核心合并策略strats、defaultStrat
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
这里合并 options 的时候会针对 props、inject、directives 分别进行合并;当传入的 options 里有 mixin 或者 extends 属性时会再次调用 mergeOptions 方法合并 mixins 和 extends 里的内容到实例的构造函数 options 上。
然后就是这个方法的核心部分,分别循环 parent 和 child ,循环 child 时额外判断当前属性不在 parent 属性里,然后调用 mergeField 方法通过 strats 和 defaultStrat 合并策略来合并 options。
下面我们来看看合并策略:
//default
//child存在则用child,不存在用parent
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
//strats
//component、directive、filter
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
//child
if (childVal) {
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
//watch
strats.watch = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
//child不存在
if (!childVal) return Object.create(parentVal || null)
if (process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret = {}
extend(ret, parentVal)
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
ret[key] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child]
}
return ret
}
//props methods inject computed
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (childVal && process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
//钩子函数
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
//child不存在返回parent、child存在parent不存在则返回child数组、都存在则通过cancat合并,child覆盖parent
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
return res
? dedupeHooks(res)
: res
}
//转成数组
function dedupeHooks (hooks) {
const res = []
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i])
}
}
return res
}
//provide data
strats.provide = mergeDataOrFn
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
//data不是function报错
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
defaultStrat 的逻辑是,如果 child 上该属性值存在时,就取 child 上的该属性值,如果不存在,则取 parent 上的该属性值。
strats 又细分几种策略:
1、el、propsData :直接走的 defaultStrat 策略;
2、component、directive、filter:首先缓存 parent ;如果 child 有则合并,以 child为准;
3、watch:child 属性不存在直接返回 parent;child 属性存在则判断是不是对象;parent 属性不存在则直接返回 child 属性;都存在则合并;
4、props、methods、inject、computed:如果 child 属性存在则判断是否是对象,parent 上没有该属性则直接返回 child 上属性;如果 child 和 parent 都有则合并,以child 的值为准;
5、钩子函数:child 上不存在而 parent 上存在则返回 parent 上属性;child 和 parent 上都有则返回 concat 后的属性(同名child覆盖parent);child 有而 parent 上没有则返回 child 属性(这个属性必须是数组,如果不是则转成数组);
6、data 、provide:会区分合并的是不是 Vue 实例;是Vue实例,options 有 data 属性则调用 mergeData 合并 child 和 parent,没有则走 defaultStrat 策略;不是 Vue 实例,没有 child 则返回 parent,没有 parent 则返回 child,两个都有则调用 mergeData 合并 child 和 parent;
注意:出现相同配置项时,就是会以 child 属性为准。
这里就把所有业务逻辑和组件的一些特性全部都转化放到 vm.$options
里面了,后面用到的时候只需要从 vm.$options
里面取值就可以了。
二、initProxy
在 src/core/instance/proxy
文件里面
//初始化代理
let initProxy
initProxy = function initProxy (vm) {
// 判断当前环境 Proxy 是否可用
if (hasProxy) {
// 确定要使用哪个代理处理程序
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
//判断对象是否具有某个属性,
const hasHandler = {
has (target, key) {
//是否存在与目标中
const has = key in target
//判断key名是否存在于allowedGlobals函数中生成的对象中 || (key名是string类型 && 首字母是_)
const isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
//访问了一个没有定义在实例对象上(或原型链上)的属性 && isAllowed为false提示警告
if (!has && !isAllowed) {
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
//返回一个布尔值
return has || !isAllowed
}
}
const getHandler = {
get (target, key) {
//key名是字符串类型 && key不存在于目标中提示警告
if (typeof key === 'string' && !(key in target)) {
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
return target[key]
}
}
export { initProxy }
当前环境 Proxy 不可用则 Vue 实例的 _renderProxy 属性指向 Vue 实例本身;Proxy 可用,如果实例的 options 上面存在 render 然后 render 上有 _withStripped 属性则调用 getHandler
否则调用 hasHandler
,这俩方法的作用的声明代理 vm 的指定行为;然后调用 new Proxy(target, handler)
getHandler :针对读取代理对象的属性时进行操作,属性不是字符串或者不存在则报错,然后返回属性值;
hasHandler:开发过程中错误调用 vm 属性时,起提示作用;
注意:options.render._withStripped
这个只有在严格模式下不支持 with 时,手动设置为 true 才启用,所以一般都是使用 hasHandler
三、initLifecycle
//初始化组件实例相关属性
export function initLifecycle (vm: Component) {
//合并后的属性
const options = vm.$options
// 找到第一个非抽象父节点
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
//让每一个子组件的$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
}
找到第一个非抽象父节点,将当前 vm 实例作为子节点放进找到的父节点中;然后是一些私有属性的初始化;
四、initEvents
//初始化事件
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// 初始化附加的父事件
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
主要是将父组件里面自定义的事件注册到子组件的事件中心里;注册时会调用 vdom 里面的 updateListeners 方法,主要是比较新旧事件列表来新增和移除以及修饰符的处理;
//比较 listeners 和 oldListeners 然后利用add , remove两个函数对事件列表进行更新
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
// 循环当前事件
for (name in on) {
def = cur = on[name]
old = oldOn[name]
//判断绑定的事件中使用了修饰符
event = normalizeEvent(name)
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
//判断给定变量是否是未定义,当变量值为 null时,也会认为其是未定义
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
//如果没有当前事件的处理器,则调用创建一个函数调用器
if (isUndef(cur.fns)) {
创建函数调用器
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
//循环老事件
for (name in oldOn) {
//判断当前事件在事件系统是否注册
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
//先去掉前缀返回,最后又返回带前缀的事件
const normalizeEvent = cached((name: string): {
name: string,
once: boolean,
capture: boolean,
passive: boolean,
handler?: Function,
params?: Array<any>
} => {
const passive = name.charAt(0) === '&'
name = passive ? name.slice(1) : name
const once = name.charAt(0) === '~' // Prefixed last, checked first
name = once ? name.slice(1) : name
const capture = name.charAt(0) === '!'
name = capture ? name.slice(1) : name
return {
name,
once,
capture,
passive
}
})
比较 listeners 和 oldListeners 然后利用add , remove两个函数对事件列表进行更新
五、initRender
//挂载可以将 render函数转为vnode 的方法
export function initRender (vm: Component) {
vm._vnode = null
vm._staticTrees = null
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
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
//在实例对象上定义响应性属性attrs、listeners
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)
}
}
主要作用是挂载 vm._c
和 vm.$createElement
方法,两个方法都是将 render 函数转为 vnode ,他们的区别是 vm._c 是通过编译器将 template 转成 render ,vm.$createElement
转换用户自定义的 render 函数;然后在实例对象上定义响应性属性 attrs、listeners;
我们常用的 vue 入口模式:entry-runtimes
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app');
这里 render 里面的参数 h 就是 vm.$createElement
,将 App 数据转化为 vnode;
六、initInjections(vm)、initProvide(vm)
在 src/core/instance/inject.js
文件里面
export function initInjections (vm: Component) {
//获取 inject 选项对应的依赖
const result = resolveInject(vm.$options.inject, vm)
if (result) {
//关闭响应式绑定
toggleObserving(false)
//遍历每一项属性
Object.keys(result).forEach(key => {
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)
}
}
//遍历inject的key,如果provide中有key与inject的from属性同名,则将这个数据给result,如果没有,检测是否有默认值,将默认值给result,最后返回
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// 创建空对象
const result = Object.create(null)
//如果支持hasSymbol,支持就用Reflect,否则用Object
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
//遍历inject属性
for (let i = 0; i < keys.length; i++) {
// 获取每个属性的值
const key = keys[i]
if (key === '__ob__') continue
//provide的ky是否等于inject的from
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
}
}
先遍历每一项,然后在遍历每一项的父级是否提供该项的依赖,有就返回到 result ,没有就继续找;获取到 result 之后会调用 toggleObserving
来关闭响应式绑定属性,具体实现在 defineReactive
里面,通过传参 true 和 false 来决定是否将绑定的属性设置为响应式数据;这里刻意在绑定 inject 上属性时关闭响应式绑定。
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
从 vm 实例的 options 里面取出 provide ,如果 provide 是 function 则绑定 this 给 vm._provided
私有属性,不是 function 则直接赋值给 vm._provided
私有属性;这样子组件就可以访问父组件提供的依赖了。
这里要额外的说说 inject 和 provide 这两个 API,他们俩是搭配使用的,provide 在父组件提供依赖绑定到 vm 实例的 _provided 属性上,这样可以全局访问这些绑定的依赖,inject 则在子组件通过入参在自己的父级链上获取到对应的依赖。
这里有一个问题:在初始化 inject 的时候会去父级上去找 provide ,但是 provide 的初始化在 inject 后面,这样会不会有问题呢?
答案是没有问题;这两个 API 主要是处理父子组件之间的传值,在初始化的时候,首先会初始化父组件,然后才会初始化子组件,所以这个时候子组件是可以拿到父组件里面的 provide 的;(父beforeCreate -> 父created -> 父beforeMount ->子beforeCreate -> 子created ->子beforeMount -> 子mounted-> 父mounted)
七、 initState
在 src/core/instance/state
文件里面
//初始化会被使用到的状态
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)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initState 方法里面会分布初始化 props、methods、data、computed、watch ;其中初始化 data 的时候如果 data 不存在则会在 vm 实例创建一个默认的 _data 属性;
1、initProps(vm, propsOptions)
//检测子组件接收的值是否符合规则, 以及让对应的值可以用this直接访问;
function initProps (vm: Component, propsOptions: Object) { //propsOptions 是props的规则
const propsData = vm.$options.propsData || {} //props具体的值
const props = vm._props = {} //存放props
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent //是否是根节点
// 不是根组件则关闭响应式绑定
if (!isRoot) {
toggleObserving(false)
}
//遍历props规则
for (const key in propsOptions) {
keys.push(key)
//通过validateProp 方法验证规则并得到相应的值
const value = validateProp(key, propsOptions, propsData, vm)
if (process.env.NODE_ENV !== 'production') {
//用连字符连接驼峰字符串
const hyphenatedKey = hyphenate(key)
//判断是否为 vue 保留属性,是则抛出警告信息
if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) {
warn(`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,vm)
}
//将值绑定到vm._props
defineReactive(props, key, value, () => {
if (!isRoot && !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 {
//将值绑定到vm._props
defineReactive(props, key, value)
}
//通过代理让我们可以通过this直接访问
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
1、非根组件上的 props 不做响应式绑定,所以父子组件传值时都是父往子传递,而不能直接通过 props 将子组件的值传递给父组件;
2、然后 props 会校验传递的值是否符合 props 的规则,同时 Vue 的保留字和关键字不可以用于 props 属性,获取 props 值;
3、通过 defineReactive
方法将props的 key 和 value 绑定到 vm._props
上,这样我们可以直接通过 this._props.name
类获取到对应的 props 的 name 属性;
4、然后通过代理 proxy 让我们可以直接通过 this.name
来访问 name 属性;proxy:定义一个对象值的get方法, 读取时让其返回另外一个值
2、initMethods(vm, opts.methods)
//将methods内的方法挂载到this下
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
//是不是方法
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
//key是不是在props中已经定义过了
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
//key是不是已经存在或者命名不规范
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
//绑定到 this 上
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
1、循环 methods,先判断是不是 function ;
2、在判断名称是不是在 props 中已经定义过了;
3、然后在判断是否已经存在同时对命名规范进行校验(避免以_或$开头);
4、最后将method绑定到 this下;
3、initData(vm)
//挂载到 this下
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
//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
)
}
// 实例上的代理数据 props 和 methods
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') {
//判断是否和methods重名
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
//判断是否和props重名
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
)
} else if (!isReserved(key)) { // key不能以_或$开头
//代理
proxy(vm, `_data`, key)
}
}
// 响应式数据
observe(data, true /* asRootData */)
}
1、拿到 data ,如果是 function 格式就执行然后将结果赋值给 vm._data
属性,不是则返回 data;
2、 然后遍历data 内的每一项, 不能和 methods 以及 props 内的key 重名, 然后使用 proxy 做一层代理;
3、最后会执行一个方法 observe(data, true)
来递归让 data 内的每一项数据都变成响应式的;
4、initComputed(vm, opts.computed)
//空函数,什么都不做,用于初始化一些值为函数的变量
export function noop () {}
const computedWatcherOptions = { lazy: true } //缓存
//初始化computed
function initComputed (vm: Component, computed: Object) {
// 创建一个空对象 watchers 没有原型链方法
const watchers = vm._computedWatchers = Object.create(null)
//是否是服务端渲染
const isSSR = isServerRendering()
// 循环computed
for (const key in computed) {
const userDef = computed[key]
//计算属性可能是一个function,也有可能设置了get以及set的对象。
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) {
// 为computed属性创建内部监视器 Watcher,保存在vm实例的_computedWatchers中
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
//当前key没有重复
if (!(key in vm)) {
//将computed绑定到 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)
}
}
}
}
1、函数⾸先创建 vm._computedWatchers
为⼀个空对象,然后遍历获取 computed 循环每一项,判断每一项的 key 是否是 function 或者 有没有 get 方法,没有则抛出警告;
2、接下来会为每一个 key 创建一个 Watcher ,这个 Watcher 就是 computed watcher
;
3、然后判断当前 key 是否已经存在于 vm 实例上,没有则调用defineComputed
方法,然后会判断 key 是否和 data、props 重名,重名则抛出警告;
这里绑定时会调用 defineComputed
方法:
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop,
};
//将计算属性绑定到vm上
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
//!isServerRendering()不是是服务端渲染
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
//不是服务端渲染`createComputedGetter(key)`,是`createGetterInvoker(userDef)`
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
//当userDef是function时只设置getter,不需要设置setter,这里就设置为空函数
//当需要设置setter时,计算属性需要是一个对象
sharedPropertyDefinition.set = noop
} else {
//get不存在直接赋值空函数,存在则查看是否有缓存,有则走`createComputedGetter(key)`,没有走`createGetterInvoker(userDef.get)`
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
//set为空则报错
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
)
}
}
//响应式绑定computed的值
Object.defineProperty(target, key, sharedPropertyDefinition)
}
//这里目的是返回get函数
function createComputedGetter (key) {
return function computedGetter () {
//得到当前key对应的watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
//在计算属性中的依赖发生改变的时候dirty会变成true,在get的时候重新计算计算属性的输出值*
if (watcher.dirty) {
//主动触发一遍watcher.get,重新估算wathcer
watcher.evaluate()
}
//如果此时存在订阅者
if (Dep.target) {
//为watcher添加依赖
watcher.depend()
}
//返回watcher的值
return watcher.value
}
}
}
//返回get函数
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
将传进来的 userDef 判断是函数还是对象,分别定义;然后使用 Object.defineProperty
挂载到 vm 上;
computed 有一个重要特性是会收集依赖并缓存 get 结果,用来节省性能,只有依赖更新了才会重新获取get;这里在 createComputedGetter
方法中都有体现;
5、initWatch(vm, opts.watch)
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
//watch的每一项是不是数组
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
//handler存在,只有handler是纯正的对象才会返回true
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
//字符串,从组件实例上获取handler
if (typeof handler === 'string') {
handler = vm[handler]
}
//创建监听器
return vm.$watch(expOrFn, handler, options)
}
1、获取 watch 循环遍历每一项,然后调取 createWatcher
方法;
2、 createWatcher
方法中 key 区分对象和字符串分别处理,然后为每一个属性创建监听器;
总结
先合并 options => 初始化代理 => 初始化组件实例相关属性,确定组件(Vue实例)的父子关系 => 初始化事件,将父组件自定义事件传递给子组件 => 绑定将 render 函数转化为 vnode 的方法 => 调用 beforeCreate 生命周期钩子 => 初始化inject, 让子组件可以访问到对应的依赖 => 将组件定义的状态(props, methods, data, computed, watch)挂载到this下 => 初始化provide 为子组件提供依赖 => 调用 created生命周期钩子 => 执行 $mount 挂载 el。
下一篇:Vue2.x 源码-初始化:stateMixin(Vue)、eventsMixin(Vue)、lifecycleMixin(Vue)、renderMixin(Vue)
这里是我个人的见解,如有疑问或者发现说错的,欢迎留言讨论!!!