共计 7745 个字符,预计需要花费 20 分钟才能阅读完成。
后面咱们讲到了_init 函数的执行流程,简略回顾下:
- 初始化生命周期 -initLifecycle
- 初始化事件 -initEvents
- 初始化渲染函数 -initRender
- 调用钩子函数 -beforeCreate
- 初始化依赖注入 -initInjections
- 初始化状态信息 -initState
- 初始化依赖提供 -initProvide
- 调用钩子函数 -created
一共通过下面 8 步,init 函数执行实现,开始 mount 渲染。
初始化状态信息
本章咱们次要解说 initState 函数的处理过程,咱们先看下 init 的主函数
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)
}
}
看下面代码,先申明了一个_watchers 的空数组;而后顺次判断传递进来的 options 是否蕴含系列参数;顺次执行 initProps、initMethods、initData、initComputed、initWatch。
initProps
initProps 函数次要是解决传进来的 props 对象,然而这个 props 对象是在上一篇文章中讲到的 normalizeProps 函数解决后的对象,不是传递进来的原对象。来看下 initProps 的代码:
function initProps(vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
if (!isRoot) {toggleObserving(false)
}
for (const key in propsOptions) {keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
defineReactive(props, key, value)
if (!(key in vm)) {proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
下面代码解读:
- 第一步获取了 propsData;
- 第二步给以后实例增加了_props 属性,新增了一个 props 援用,指向了_props 属性;
- 第三步给以后实例减少了_propKeys 属性,新增了一个 keys 的援用,指向了_propKeys 属性;
- 第四步判断了是否须要进行监听;
- 遍历 normalizeProps 函数解决后的对象 propsOptions;
-
- 存储 key
-
- 校验 props 格局
-
- 为以后 key 定义响应式的属性:defineReactive
-
- 把以后 key 的拜访形式进步到实例下面:proxy,即能够 vm.name 来拜访 vm._props.name
function proxy(target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter(val) {this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
- 关上监听。
initMethods
initMethods 办法是用来解决传递进来的 methods 参数,把 methods 绑定到以后实例下面
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)
}
if (props && hasOwn(props, key)) {warn(`Method "${key}" has already been defined as a prop.`, 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 $.`)
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
下面代码解读:
- 第一步获取了 props;
- 第二步遍历 methods;
-
- 判断以后 method 是否是函数,不是函数则在开发环境下报警
-
- 判断 props 是否曾经有了以后 method 的 key,如有则在开发环境下报警
-
- 判断以后 method 是否曾经在 vm 下面了,并且以 $ 或_结尾,如是,则在开发环境下报警
-
- 为以后实例增加办法;
参考 Vue3 源码视频解说:进入学习
initData
initData 办法是用来解决传递进来的 data 参数,增加监听
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
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)
}
}
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)) {
// 实现代理,能够 this.massage 来进行拜访 this._data.message
proxy(vm, `_data`, key)
}
}
observe(data, true /* asRootData */)
}
下面代码解读:
- 第一步获取传递进来的 data,判断 data 是否为函数,是函数则执行函数获取以后对象,否则间接读取以后对象;
- 第二步,获取上一步的 data 所有的 key,赋值给 keys;
- 第三步获取 props;
- 第四步获取 methods;
- 第五步,循环 keys;
-
- 判断是否和 methods 外面是否反复,反复则开发环境进行报警
-
- 判断是否和 props 外面是否反复,反复则开发环境进行报警
-
- 判断如不是以_或 $ 结尾的 key,则进行代理解决,把以后 key 的拜访形式进步到实例下面:proxy,即能够 vm.name 来拜访 vm._datas.name
- 对以后 data 对象进行 observe 解决,临时先不必关注 observe,前面会讲到是做什么的。
initComputed
initComputed 是用来解决传进来的 computed 参数
function initComputed(vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{lazy: true}
)
}
if (!(key in vm)) {defineComputed(vm, key, userDef)
}
}
}
initComputed 办法解读:
- 第一步为实例减少了_computedWatchers 属性, 申明援用 watchers;
- 获取是否是服务端渲染 -isSSR;
- 遍历 computed;
-
- 获取用户定义的内容 -userDef
-
- 依据用户定义的内容来获取以后属性 key 的 getter 函数
-
- 为以后 key 减少 Watcher,临时不必关注 Watcher 前面会讲到
-
- 调用 defineComputed,参数为以后实例,以后属性 key 和 userDef
上面来看下 defineComputed 的实现:
- 调用 defineComputed,参数为以后实例,以后属性 key 和 userDef
function defineComputed(target: any, key: string, userDef: Object | Function) {const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
defineComputed
defineComputed 办法解读:
- 判断是否须要应用 cache,非 server 端渲染,应用 cache,即浏览器状况下都是 true;
- 分状况探讨:
-
- userDef 为函数时,调用 createComputedGetter 函数生成 get 函数,set 函数为空函数
-
- userDef 不为函数时,get 函数为 createComputedGetter 或者 createGetterInvoker 生成的函数;
- 调用 Object.defineProperty 为以后实例增加定义属性;
createGetterInvoker
上面来看下 createGetterInvoker:
function createGetterInvoker(fn) {return function computedGetter() {return fn.call(this, this)
}
}
下面代码间接返回了一个函数,函数外部调用的是传递进来的 fn 函数,fn 函数是从 defineComputed 传进来的,值为 userDef 或者 userDef.get。
createComputedGetter
上面来看下 createComputedGetter:
function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {if (watcher.dirty) {watcher.evaluate()
}
if (Dep.target) {watcher.depend()
}
return watcher.value
}
}
}
下面代码返回了一个 computedGetter 的函数,函数外部剖析:
- 获取了在 initComputed 函数外面申明的_computedWatchers,
- watcher 必定是有值的,dirty 属性的值在此处也相当于 lazy 属性,因为创立 watcher 的时候传的是 true,所以此处也是 true;
- 执行 watcher.evaluate,该办法会获取以后 watcher 的 value,并且把 dirty 属性变为 false;
- 判断 Dep.target, 而后调用 watcher 的收集依赖;
- 返回 watcher.value;
initWatch
initWatch 是用来解决传进来的 watch 参数。
function initWatch(vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key]
if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])
}
} else {createWatcher(vm, key, handler)
}
}
}
initWatch 函数解读:
遍历 watch,依据 key 获取 handler,handler 为数组遍历执行 createWatcher,不为数组间接执行 createWatcher;
来看下 createWatcher:
createWatcher
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
createWatcher 代码解读:
- 判断 handler 是否为对象,如果为对象,则把以后 handler 作为 options,options.handler 作为 handler;
- 判断 handler 是否为字符串,字符串的话,则间接获取实例的 handler 办法;
- 调用 $watch 返回;
综上剖析,watch 的传参能够分为以下几种:
watch: {telephone: function (newValue, oldValue) {console.log('telephone')
},
name: 'printName',
message: ['printName', 'printValue'],
address: [{handler: function (newValue, oldValue) {console.log('address')
}
}]
},
methods: {printName(newValue, oldValue) {console.log('printName')
},
printValue(newValue, oldValue) {console.log('printValue')
}
}
- 第一种:间接传办法;
- 第二种:传递办法的字符串名称;
- 第三种:传递办法的字符串名称数组;
- 第四种:传递一个蕴含 handler 属性的对象数组;
接下来咱们看下 $watch 办法的实现
$watch
当初咱们来看下 watch 的实现,watch 是 Vue 原型上的办法,主流程篇简略提了一下,流程图下面看到 $watch 是在 statesMixin 函数外面给 Vue 挂载到原型对象上的。
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) {
try {cb.call(vm, watcher.value)
} catch (error) {handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn() {watcher.teardown()
}
}
下面代码就是 $watch 函数的实现,咱们一步步来看下。
- 参数,蕴含 3 个,第一个就是须要 watch 的 key,比方下面例子代码的 name;第二个就是回调函数,当 name 属性扭转的时候会调用此回调函数;第三个参数为 options,顾名思义,就是配置信息;
- 第一步:实例 vm 的申明;
- 第二步:判断 cb 是否为对象,如果是则调用下面的 createWatcher;
- 第三步:options 检测是否有值,无值则赋值为空对象;
- 第四步:设置 options.user 为 true,即这是用户所定义和调用触发的;
- 第五步:创立 Watcher;
- 第六步:如果是立刻调用,则调用 cb,即回调函数;
- 返回一个函数,此函数为 watcher 的销毁函数。
下面就是整个 initWatch 的调用过程。