- 为当前实例添加方法;
initData
initData方法是用来处理传递进来的data参数,添加监听
代码语言:text复制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参数
代码语言:text复制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的实现:
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:
代码语言:javascript复制function createGetterInvoker(fn) {
return function computedGetter() {
return fn.call(this, this)
}
}
上面代码直接返回了一个函数,函数内部调用的是传递进来的fn函数,fn函数是从defineComputed传进来的,值为userDef或者userDef.get。
createComputedGetter
下面来看下createComputedGetter:
代码语言:scss复制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参数。
代码语言:scss复制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
代码语言:text复制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挂载到原型对象上的。
代码语言:javascript复制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的调用过程。