这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战
在前文已对Vue
初始化流程中initData
、initProps
进行梳理,接下来继续看看其他选项初始化工作做了哪些事情,首先回顾一下initState
初始化过程的整个过程
export function initState(vm) {
const opts = vm.$options; // 获取用户传递的所有选项
// 初始化 props
if (opts.props) initProps(vm, opts.props);
// 初始化 methods
if (opts.methods) initMethods(vm, opts.methods);
// 初始化 data
if (opts.data) {
initData(vm);
}
// 初始化 computed
if (opts.computed) initComputed(vm, opts.computed);
// 初始化 watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
initMethods
在初始化methods
过程非常简单
const initMethods = (vm, methods) => {
for (const key in methods) {
vm[key] = typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
}
};
核心在于判断用于在methods
的配置是否为function
类型,若是则通过bind
改变函数的this
为当前的实例
需要注意的是noop
function noop () {}
noop
属于hack
行为,只是为了兼容flow
而已,不会在编译的过程中留下无用的代码
源码中当然不会这么简单,在开发环境多了一些健壮性、容错处理,代码主要是一些控制台输出警告,避免直接贴代码不利于阅读,总结三点:
- 判断是否配置的是否为
function
类型:typeof methods[key] !== 'function'
- 判断和
props
是否存在key
命名冲突:props && hasOwn(props, key)
- 命名规范校验:不建议命名以
$
或者_
开始
initComputed
计算属性在开发过程中非常常用,熟悉Vue
的小伙伴都可熟练的使用,在介绍源码之前先介绍两个关于computed
小点
-
计算属性具备缓存特性,其本质是一种
Watcher
,和render watcher
、user watcher
差异在于其是lazy wacther
,根据其内部使用的响应式数据进行缓存 -
定义计算属性的两种方式
Vue.component('only-read', {
computed: {
// 仅读取方式
getName() {
return this.name
},
// 可读取、设置
getAge: {
get() {
return this.age
},
set(newVal) {
this.age = newVal
}
}
}
})
接下来看一下initComputed
具体实现
const computedWatcherOptions = { lazy: true };
const initComputed = (vm, computed) => {
const watchers = Object.create(null);
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === "function" ? userDef : userDef.get
// 计算属性本质就是一个 Watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
if (!(key in vm)) {
// 初始化计算属性
defineComputed(vm, key, userDef);
}
}
};
initComputed
函数主要就是将用户传递的计算属性遍历,依次增加到watchers
中并调用defineComputed
初始化,内部节省了一些非生产环境的健壮性、容错判断:检测定义的computed
是否与data
、props
、methods
存在key
的冲突
计算属性支持两种定义方式,在defineComputed
中需要对两者分别处理,最终转换为一种格式进行数据劫持
const defineComputed = (target, key, userDef) => {
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);
};
两种定义方式最终都转换为get
、set
的形式,便于数据劫持处理