Appearance
二、Vue的初始化过程
Vue的初始化过程
从入口文件里面导入的Vue可以看出其构造函数的文件是web/runtime/index.js
web/runtime/index.js
这里的代码都是与平台相关的.注册了全局的组件和指令,注册了$mount
代码结构
- 导入了一些成员
- 导入了环境判断inBrowser
- 导入平台相关的指令platformDirectives和组件platformComponents
- 给Vue.config中注册了一些方法,这些方法是与平台相关的特定的通用的方法
- 通过extend方法注册了与平台相关的全局的指令和组件
- 注册指令
- 注册组件
- 在Vue的原型对象上注册了一个patch函数
- 给vue实例增加了一个$mount的方法
- 与调试相关的代码,不关心
- 导出Vue
这个文件依然没有找到Vue的构造函数,我们可以看到其 Vue在core/index文件夹中.
core/index.js
在文件core/index.js中,给Vue初始化静态方法,注册与ssr相关的代码,版本
代码结构
- 导入成员
- 给Vue的构造函数增加一些静态方法initGlobalAPI(重点看,选中按F12,在静态成员里面有详细描述)
- 通过Object.defineProperty注册了一些成员,这些都是ssr,与服务端渲染相关的.暂时忽略
- Vue的版本
- 导出Vue
这个文件依然没有找到Vue的构造函数,我们可以看到其 Vue在instance/index.js文件中, 下面的 core/instance/index.js
core/instance/index.js
这里面才是真正的Vue构造函数的定义,创建了Vue的构造函数,设置了Vue实例的成员
代码结构
- 导入成员
- 定义Vue构造函数,接收一个参数options
- 判断开发环境下this若不是Vue的实例,说明没有用new Vue去调用构造函数,会报出警告
- 调用 _init() 方法
- 混入 vm 的 _init()方法, 初始化vm
- 混入 vm 的 $data/$props/$set/$delete/$watch 方法,这里是初始化Vue实例的一些方法和属性
- 初始化事件相关方法
- 初始化声明周期相关的混入方法
- 混入 render
为什么不用类创建而用构造函数创建Vue? 此处不用 class 的原因是因为方便后续给Vue实例混入实例成员,如果用了class再用原型,很不搭.
里面把每个功能模块拆分的很细,之后我们要看哪个功能模块,直接看对应的模块即可.
笔记总结
四个导出 Vue 的模块
- src/platforms/web/entry-runtime-with-compiler.js
- 主要增加了编译的功能
- web 平台相关的入口
- 重写了平台相关的 $mount() 方法, 让其内部可以编译模板,将template转换成render函数
- 注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数
- src/platforms/web/runtime/index.js
- web 平台相关
- 注册和平台相关的全局指令:v-model、v-show
- 注册和平台相关的全局组件: v-transition、v-transition-group
- 全局方法:
__patch__
:把虚拟 DOM 转换成真实 DOM- $mount:挂载方法
- src/core/index.js
- 与平台无关
- 设置了 Vue 的静态方法,调用了initGlobalAPI(Vue)
- src/core/instance/index.js
- 与平台无关
- 与实例相关
- 定义了构造函数,调用了 this._init(options) 方法
- 给 Vue 中混入了常用的实例成员
初始化之静态成员
重点看initGlobalAPI方法的内部实现,下面的 core/global-api/index.js
core/global-api/index.js
core/global-api/index.js ,给Vue的构造函数初始化静态方法
代码结构
- 导入成员
- 定义并导出了一个initGlobalAPI函数
- 初始化Vue.config对象
- Vue.util里面增加了一些方法
- 静态方法set/delete/nextTick的定义
- 静态方法observable,这个方法是让对象变成可响应的,设置响应式数据
- 初始化Vue.options中的一些属性
- components\directives\filters
- _base
- keep-alive
- 初始化了其他的方法
- 注册插件Vue.use()
- 混入Vue.mixin()
- 返回组件构造函数Vue.extend()
- Vue.directive()\Vue.component()\Vue.filter()
初始化Vue.config对象
js
// 初始化Vue.config静态成员,定义了config属性的描述符
const configDef = {}
// 定义get方法, 获取config
configDef.get = () => config
// 如果是开发环境,给config设置值的时候会触发set方法,触发警告不要给Vue.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属性
Object.defineProperty(Vue, 'config', configDef)
Vue.util增加方法
这些方法没有在文档中写,也不建议我们使用,这里不做说明
js
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
// Vue.util里面增加了一些方法
// 上面给了NOTE:这些方法不能被视作全局API的一部分,除非你已经意识到某些风险,否则不要去依赖他们
// 意思是我们在调用这些方法的时候可能会出现意外,所以要避免去调用这些方法,vue这么做是其内部要进行使用
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
静态方法set/delete/nextTick
分析完响应式再来看
js
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
静态方法Vue.observable
这个方法是让对象变成可响应的,设置响应式数据
js
// 静态方法observable
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
初始化Vue.options中的属性
js
// 先设置一个空对象,创建改对象的同时设置原型等于null,说明当前不需要原型,可以提高性能
Vue.options = Object.create(null)
/**
* 引用自 shared/constants.js
* export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
* 遍历数组的名字加s之后挂载到Vue.options下面,这三个属性对应的值都是一个空对象,它们的作用分别是存储全局的组件\指令和过滤器
* 我们通过Vue.component,Vue.directive,Vue.filter注册的全局组件\指令和过滤器都会存储到Vue.options对应的属性上
*/
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
_base记录当前构造函数
js
Vue.options._base = Vue
注册全局组件keep-alive
js
/**
* 注册全局组件keep-alive
* extend
* 在shared/util.js目录下,extend是实现了浅拷贝,第二个参数的成员拷贝给了第一个参数的成员
* export function extend (to: Object, _from: ?Object): Object {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
builtInComponents
名字可以看出来这是一个内置组件,在core/components/index.js目录下,导出的组件其实是KeepAlive
所以在还注册了内置组件keep-alive
*/
extend(Vue.options.components, builtInComponents)
定义 Vue.use 静态成员
js
// 注册 Vue.use() 用来注册插件
initUse(Vue)
这个方法的定义在同级的use.js中
js
// 定义并导出函数initUse,传入参数是Vue构造函数
export function initUse (Vue: GlobalAPI) {
// 定义了Vue.use的方法,接收了一个参数plugin(插件,可以是函数也可以是对象)
Vue.use = function (plugin: Function | Object) {
// 定义了一个常量,installedPlugins表示已经安装的插件
// 这个地方的this只是的Vue的构造函数
// 获取_installedPlugins属性如果有就返回,如果没有就初始化成一个空数组,这个里面记录了安装的插件.
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 如果注册的插件已经存在,则直接返回,如果没有注册,下面注册插件,注册插件其实就是调用传入的插件
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// 这里整体的功能是调用插件的方法并传递相应的参数
// additional parameters
// 这里对调用此方法的其余参数做处理
// use方法可以传一个参数,也可以传多个参数,如果传多个参数,那么第一个是插件,其余的都是调用时候传入的参数
// toArray方法将arguments转换成数组,后面的1是把第一个参数去掉
const args = toArray(arguments, 1)
// 将Vue构造函数插入到参数数组的第一项中
args.unshift(this)
// 如果plugin.install有值的话说明plugin是一个对象,直接调用其install方法
// 文档中说,如果要注册一个插件,其中必须要有一个install方法,这是对插件的要求
if (typeof plugin.install === 'function') {
// 使用apply方法改变其内部的this,第一个传plugin是plugin调用的方法,apply方法会将args数组展开,第一个参数是Vue,intall方法要求的第一个参数也是Vue
plugin.install.apply(plugin, args)
// 如果是函数那么直接调用这个函数
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
// 当注册好插件之后要将插件保存到已安装的插件数组中
installedPlugins.push(plugin)
// 返回Vue的构造函数
return this
}
}
定义 Vue.mixin 静态成员
js
// 注册 Vue.mixin() 实现混入
initMixin(Vue)
这个方法的定义在同级的mixin.js中
js
export function initMixin (Vue: GlobalAPI) {
// 传入参数
Vue.mixin = function (mixin: Object) {
// 这里的this指的是Vue构造函数
// 把mixin对象的成员拷贝到Vue.options中,所以mixin注册的是全局选项,官网上有文档
this.options = mergeOptions(this.options, mixin)
return this
}
}
定义 Vue.extend 静态成员
返回一个组件的构造函数,做自定义组件的话可能会用到
js
// 注册 Vue.extend() 基于传入的options返回一个组件的构造函数
initExtend(Vue)
这个方法的定义在同级的extend.js中,下面对一些核心代码进行注解
js
export function initExtend (Vue: GlobalAPI) {
...
/**
* Class inheritance
* 参数是选项,对象
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
// Super是this是Vue构造函数
const Super = this
const SuperId = Super.cid
...
// 核心代码
// 创建一个构造函数 VueComponent ,组件对应的构造函数
const Sub = function VueComponent (options) {
// 调用_init()初始化
this._init(options)
}
// 改变了构造函数的原型,让其继承自Vue,故所有的组件都继承自Vue
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
// 合并 options 选项
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// 把Super中的成员拷贝到VueComponent构造函数中来
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
// 注册Vue.component\Vue.filter\Vue.directive方法
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
...
// 最后返回组件的构造函数VueComponent
return Sub
}
}
定义 Vue.directive 等静态成员
Vue.directive()\Vue.component()\Vue.filter()这三个方法不是一个一个注册的,是一起注册的,因为其参数是一样的.
Vue文档的全局API中,第一个传id,第二个传定义(可以是对象也可以是函数)
js
// 注册 Vue.directive()\Vue.component()\Vue.filter()
initAssetRegisters(Vue)
这个方法的定义在同级的assets.js中
js
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
* 没有直接定义而是遍历数组,里面其实就是'component','directive','filter'
*/
ASSET_TYPES.forEach(type => {
// 给每个值分别设置一个方法
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// 没有传定义的话会找到之前options中定义的方法
// id就是组件名称 or 指令名称
if (!definition) {
return this.options[type + 's'][id]
} else {
...
/**
* 判断类型是否是组件,且定义是否是原始的Object,判断其转换成字符串是不是'[object Object]'
*/
if (type === 'component' && isPlainObject(definition)) {
// 如果设置了组件名称name就用name,如果没有就用id作为组件名称
definition.name = definition.name || id
// this.options._base就是Vue构造函数
// Vue.extend()就是把普通对象转换成了VueComponent构造函数
// 官方文档中也可以直接传一个Vue.extend构造函数,如果第二个参数definition是Vue.extend的构造函数,那么就直接执行return的最后一句话
definition = this.options._base.extend(definition)
}
// 如果是指令,且是函数的话,会把定义赋值给bind和update两个方法
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
// 所有的内容处理之后够会直接挂载到this.options下面去,通过这个注册的是全局的组件\指令等
this.options[type + 's'][id] = definition
return definition
}
}
})
}
初始化之实例成员
(笔记对应上面core/instance/index.js)
core/instance/index.js
这个里面定义了Vue构造函数,还初始化了很多实例成员,下面具体分析
这几个函数的名字有特点,后面都有Mixin,都传入了Vue构造函数,这几个方法类似的作用就是给Vue的原型对象上混入了相应的成员,也就是给Vue的实例上增加了相应的成员.
initMixmin
混入初始化方法 _init()
同级init.js中
js
// 初始化了Vue实例的成员,并且触发了beforeCreate和created钩子函数,触发$mount渲染整个页面
// 不同的初始化在不同的模块中,结构很清晰,这些代码不需要记住,用到哪些成员回头看即可
export function initMixin (Vue: Class<Component>) {
// 给 Vue 实例增加 _init()方法
Vue.prototype._init = function (options?: Object) {
// 定义一个vm常量指代Vue的实例
const vm: Component = this
// 定义uid,唯一标识
vm._uid = uid++
// 开发环境下的性能检测,略过
let startTag, endTag
...
// a flag to avoid this being observed
// 标识当前实例是Vue实例,之后做响应式数据的时候不对其进行处理
vm._isVue = true
// merge options
// 合并options,这两个相似的是把用户传入的options和构造函数中的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
)
}
/* istanbul ignore else */
// 渲染时候的代理对象,实际设置成了Vue实例
// 在渲染过程的时候会看到这个属性的使用
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化的函数
// 初始化和生命周期相关的内容
// $children/$parent/$root/$refs
initLifecycle(vm)
// 初始化当前组件的事件
initEvents(vm)
// 初始化render中所使用的h函数,初始化了几个属性$slots/$scopedSlots/_c/$createElement/$attrs/$listeners
initRender(vm)
// 触发声明周期的钩子函数beforeCreate
callHook(vm, 'beforeCreate')
// initInjections与initProvide是一对,实现依赖注入
initInjections(vm) // resolve injections before data/props
// 初始化 vm 的 _props/methods/_data/computed/watch
initState(vm)
// initProvide函数中,会把父组件提供的成员存储到_provided里面中
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)
}
// 挂载整个页面
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
initProxy
渲染时候的代理对象,实际设置成了Vue实例
js
initProxy = function initProxy (vm) {
// 首先判断该环境下是否有proxy对象
// 如果有代理对象就用new Proxy初始化,否则就把_renderProxy设置成Vue实例
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
initLifecycle
js
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
// 找到当前Vue实例的父组件,将其添加到父组件的$children里面去
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
// 挂载$parent\$root\$children\$refs
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
}
initEvents
js
export function initEvents (vm: Component) {
// 给vm添加了属性_events,这个属性是用来存储事件名称以及对应的处理函数,键就是事件名称,值就是事件处理函数
// 值是数组形式,因为一个事件名称对应多个事件处理函数,发布订阅模式中曾经模拟过这种形式
// 在下面的$on中可以看到其使用
// (vm._events[event] || (vm._events[event] = [])).push(fn)
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
// 获取父元素上附加的事件,注册到当前组件中
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
initRender
js
export function initRender (vm: Component) {
...
// 初始化$slots/$scopedSlots两个去插槽相关的属性
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// 重点方法 _c/$createElement
// 对手动传入template属性,其编译生成的 render 进行渲染的方法,其调用的也是createElement,最后一个参数不一样
// 当把template编译成render函数的时候,其内部会调用_c
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 对手动传入的 render 函数进行渲染的方法
// $createElement就是new Vue的时候传入render(h)的h函数,所用是把虚拟DOM转换成真实DOM
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
...
// 定义$attrs和$listeners属性,用defineReactive是定义响应式数据,这些数据是只读的,开发环境不允许赋值
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)
}
}
initInjections
js
// 依赖注入的实现原理
export function initInjections (vm: Component) {
// 将inject对象的所有属性,判断这些属性如果在vm._provided属性中存在就提取出来放到result
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
// 遍历属性将其注入到Vue实例并设置成响应式数据
Object.keys(result).forEach(key => {
/* istanbul ignore else */
// 生产环境下如果直接给inject环境赋值会发送警告
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)
}
}
其中resolveInject函数的如何实现的?
js
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
...
// 核心代码,重点看
// 先拿到inject里面所有的keys
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
// 这些keys是inject中的所有属性,判断keys是否在source._provided里面,source就是vm实例
for (let i = 0; i < keys.length; i++) {
...
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
// 如果这个属性在source._provided中,就放到result里面
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
...
}
// 最终返回result
return result
}
}
initState
js
export function initState (vm: Component) {
vm._watchers = []
// 获取了实例中的$options
// 判断props,methods,data,computed,watch这些属性,如果有就用init进行初始化
const opts = vm.$options
// initProps:把props数据转换成响应式数据并且注入到Vue实例中
if (opts.props) initProps(vm, opts.props)
// initMethods:初始化了选项中的methods,在注入之前判断了方法名称和值
if (opts.methods) initMethods(vm, opts.methods)
// 如果参数中有data就执行initData
// 如果参数中没有data就在vm初始化一个_data并赋值一个空对象,并且进行响应式处理
if (opts.data) {
//initData:初始化选项中的data,注入到Vue实例中,注入之前进行重名判断.并且将data进行响应式处理
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
// 初始化计算属性和侦听器,注入到Vue实例中
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps
js
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
// 定义了一个_props对象并存到props常量中
const props = vm._props = {}
const keys = vm.$options._propKeys = []
...
// 遍历propsOptions(vm.$options.props)的所有属性,将属性都通过defineReactive转换成 get\set 注入到props(vm._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') {
...
} else {
defineReactive(props, key, value)
}
// vm.$options.props判断是否在Vue实例中存在,如果不存在通过proxy方法把属性注入到Vue实例中
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
initMethods
js
function initMethods (vm: Component, methods: Object) {
// 获取选项中的$options.props,这里为什么要获取props是为了下面名称重复排查需要
const props = vm.$options.props
// 遍历methods中的所有属性(方法名称)
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
// 如果是开发环境判断methods值是否是function,如果不是function就会发送警告
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中存在,会警告此名称已经在props中存在,因为最终props和methods都要注入到Vue的实例上,所以他们不能有同名存在.
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
// 判断方法名称是否在vue中存在,并且判断该名称是否以_或者$开头
// 如果以下划线开头,那Vue认为这是一个私有属性,不建议这样命名
// 如果以$开头,公认为Vue提供的成员,也不建议这样命名
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
// 最后将methods值注入到Vue实例中来,先判断这个值是否是function,如果不是就直接返回一个noop空函数,如果是就返回该函数的bind方法,bind方法是改变this指向
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
initData
js
function initData (vm: Component) {
// 获取options中的data选项
let data = vm.$options.data
// 判断data是否是function,如果是就调用getData
// 当组件中初始化data的时候会设置成一个函数
// 如果是Vue实例中的data是一个对象,并没有传入就初始化一个空对象
data = vm._data = typeof data === 'function'
? getData(data, vm)
: 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中的所有属性,还获取了props和methods的所有属性,目的也是判断data中的成员是否与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') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 判断data的属性是否以_或者$开头,如果是不会注入到Vue的实例中
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
)
// 如果不是_或者$开头,就会把属性注入到Vue实例中,
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
// 把data转化成响应式对象
observe(data, true /* asRootData */)
}
initComputed
没有细讲,原理一样
initWatch
没有细讲,原理一样
initProvide
js
export function initProvide (vm: Component) {
// 找到$options.provide对象(也可能是函数),将这个成员存储到vm._provided中(如果是函数,就调用改变其this,如果是对象直接存储)
// 这个属性在initInject中会使用到
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
stateMixin
混入 $data/$props/$set/$delete/$watch 方法
同级state.js中
js
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
// 定义描述符,get方法返回实例对象的_data,_props
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
// 如果是开发环境下,不允许给$data和$props赋值
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
// 通过Object.defineProperty给原型上添加$data,$props
// 后面是两个对象的描述符,上面给描述符定义了get方法
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
// 在原型是挂载了$set和$delete,这个与Vue.set\Vue.delete是一模一样的
Vue.prototype.$set = set
Vue.prototype.$delete = del
//原型上挂载了$watch,监视数据的变化
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
...
}
}
eventsMixin
混入事件相关方法 $on$once$off$emit,这里的事件使用的是发布订阅模式
同级events.js
js
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
// 注册事件
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
// 判断事件是否是数组,如果是数组就遍历数组给多个事件注册处理函数
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
// 如果是字符串就调用_events,_events是一个对象,之前有定义 vm._events = Object.create(null)
} else {
// 对象属性就是事件的名称,根据对象属性找事件存储的内容,如果没有找到就初始化成数组
// 最终的结果将处理函数添加到数组中
(vm._events[event] || (vm._events[event] = [])).push(fn)
...
}
return vm
}
// 注册事件(一次性)
Vue.prototype.$once = function (event: string, fn: Function): Component {
...
}
// 注销事件
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
...
}
// 触发事件
Vue.prototype.$emit = function (event: string): Component {
...
}
}
lifecycleMixin
混入声明周期相关的方法 _update/$forceUpdate/$destory
同级lifecycle.js
js
export function lifecycleMixin (Vue: Class<Component>) {
// 更新,最最核心的是里面调用了patch
// patch的作用是把虚拟DOM转换成真实DOM挂载到$el中
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
// 判断是不是第一次调用,如果是就调用上面的__patch__方法,如果不是就调用下面的__patch__方法
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
...
}
// 强制更新,会调用_watcher的update
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
// 销毁Vue实例
Vue.prototype.$destroy = function () {
...
}
}
renderMixin
混入 _render, $nextTick方法,作用是调用用户传入的render
同级render.js
js
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
// 安装了渲染相关的一些帮助方法,将Vue原型传入
installRenderHelpers(Vue.prototype)
// 挂载$nextTick方法
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
// 挂载_render方法
Vue.prototype._render = function (): VNode {
const vm: Component = this
// 从options中获取了render,这个render是用户定义的render,或者是模板渲染的render
const { render, _parentVnode } = vm.$options
...
let vnode
try {
...
// 核心,在_render中调用了传入的渲染函数
// 通过call方法调用,第一个方法是改变其内部的this
// render(h),这个$createElement就是h,生成虚拟DOM
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
...
} finally {
...
}
...
return vnode
}
}