# Vue源码解读(三)
# 一、Vue中computed和watch
两者区别:
- 计算属性computed是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
- 侦听器watch,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
计算属性computed和侦听器watch的初始化都在 src\core\instance\state.js
initState方法里面。
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // 初始化props
if (opts.methods) initMethods(vm, opts.methods) // 初始化methods
if (opts.data) {
initData(vm) // 初始化data
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed) // 初始化计算属性
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // 初始化侦听器
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
initState会调用initComputed来初始化计算属性和initWatch来初始化侦听器。先看computed的实现
# computed
computed的基本使用如下
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
2
3
4
5
6
7
8
9
10
现在我们从源码角度来看computed的实现,computed的初始化是调用initComputed
方法完成的。
- initComputed 初始化计算属性
具体做了两件事,为每个计算属性创建watcher和定义计算属性的getter和setter,核心代码如下
const computedWatcherOptions = { lazy: true }//是否延迟执行watcher的get方法,计算属性默认都延迟
function initComputed (vm: Component, computed: Object) {
// 创建computedWatchers数组来保存每个计算属性的watcher
const watchers = vm._computedWatchers = Object.create(null)
// 在SSR服务端渲染的时候,计算属性仅仅是getter
const isSSR = isServerRendering()
// 遍历这些计算属性,key就是计算属性的名字
for (const key in computed) {
// userDef是计算属性的value,有两种:一种是function,一种是包含get和set的Object
const userDef = computed[key]
// 得到getter
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
// 如果不是SSR,则为每个key创建watcher
watchers[key] = new Watcher(
vm, // vue实例
getter || noop, // getter或者空函数
noop, // 空函数
computedWatcherOptions // 延迟执行watcher的get方法
)
}
// 如果这些key不在vm实例上,则用defineComputed定义这些计算属性
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- defineComputed 定义计算属性的getter和setter。
// 通用的属性定义描述符
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop, // 空函数
set: noop
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering() // 如果不是服务端渲染,即客户端渲染,则需要使用缓存
// 如果计算属性的value是一个function
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key) // 如果是客户端渲染,则使用createComputedGetter创建getter
: createGetterInvoker(userDef) // 否则使用createGetterInvoker创建getter
sharedPropertyDefinition.set = noop
} else {
// 如果计算属性的value是带getter和setter的对象,默认只有getter,不过在需要时也可以提供一个 setter
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key) // 如果是客户端渲染,则使用createComputedGetter创建getter
: createGetterInvoker(userDef.get) // 否则使用createGetterInvoker创建getter
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
// 为每个计算属性定义getter和setter存取描述符
Object.defineProperty(target, key, sharedPropertyDefinition)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- createComputedGetter和createGetterInvoker
创建计算属性的getter。如果是客户端渲染,则使用createComputedGetter创建getter,否则使用createGetterInvoker创建getter。
// 客户端
function createComputedGetter (key) {
// 返回getter函数
return function computedGetter () {
// 拿到这个计算属性的watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 当第一次访问computed中的值时,会因为watcher的初始化工作,watcher.dirty = watcher.lazy的原因,从而调用evalute()方法
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
// 服务端渲染
function createGetterInvoker(fn) {
// 因为服务端渲染只有首屏才会用到,所以不需要做依赖收集,直接返回fn的执行结果即可,即计算属性的value
return function computedGetter () {
return fn.call(this, this)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
当第一次访问computed中的值时,会因为初始化 watcher.dirty = watcher.lazy
的原因,从而调用evalute()方法,evalute()方法很简单,就是调用了watcher实例中的get方法以及设置dirty = false,我们将这两个方法放在一起,evalute()定义在 src/core/observer/watcher.js
evaluate () {
this.value = this.get()
this.dirty = false
}
get () {
// 将当前watcher放入Dep.target对象,这个时候,Dep.target就是当前计算属性的watcher了
pushTarget(this)
let value
const vm = this.vm
try {
// 当调用用户传入的方法时,会触发watcher的getter操作,即用户自己声明的方法
value = this.getter.call(vm, vm)
} catch (e) {
} finally {
popTarget()
// 去除不相关代码
}
return value
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在get方法中中,第一行就调用了pushTarget方法,其作用就是将Dep.target设置为所传入的watcher,即所访问的computed中属性的watcher,然后调用了value = this.getter.call(vm, vm)
方法,想一想,调用这个方法会发生什么?
this.getter 在Watcher构建函数中提到,本质就是用户传入的方法(也是watcher的更新函数),也就是说,this.getter.call(vm, vm)
就会调用用户自己声明的方法,那么如果方法里面用到了 this.data中的值或者其他被用defineReactive包装过的对象,那么,访问this.data 或者其他被defineReactive包装过的属性,是不是就会访问被代理的该属性的get方法。defineReactive中get方法如下:
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 这个时候,Dep.target有值了
if (Dep.target) {
// computed的watcher依赖了this.data的dep
dep.depend()
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个时候我们走完了一个依赖收集流程,知道了computed是如何知道依赖了谁。最后根据this.data所代理的set方法中调用的notify,就可以改变this.data的值,去更新所有依赖this.data值的computed属性value了。
computed整体流程
1、给每个计算属性创建 watcher和getter setter,watcher的更新函数是用户传入的方法。
2、首次渲染,自动触发计算属性的getter,getter会自动调用 watcher的evaluate()
方法,该方法会将当前watcher放入Dep.target对象,并且执行用户传入的方法。
3、由于用户传入的方法里面依赖了this.data中的属性(即依赖属性)或者其他被用defineReactive包装过的对象,那么,访问依赖属性或者其他被defineReactive包装过的属性时,就会访问被代理的该属性的get方法。该属性的get方法会把当前computed watcher添加到该属性的Dep对象里面,这样就完成了该属性的依赖收集了。
4、this.data里面的属性值改变时,所代理的set方法中会调用dep的notify方法,通知该Dep中的所有watcher,依赖this.data值的computed属性就会自动更新了。
# watch
watch的基本使用如下:
1、最简单的用法,直接写一个监听处理函数
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer() // 异步请求数据
}
}
2
3
4
5
6
7
2、在所监听的数据后面直接加字符串形式的监听方法名
watch: {
question: 'handleChange'
}
2
3
3、复杂用法,传递一个监听对象,这个对象包含handler、immediate、deep属性。
使用watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
watch: {
question: {
// 监听处理函数
handler(newName, oldName) {
// ...
},
immediate: true, // 立即执行
deep: true // 深度watch
}
}
2
3
4
5
6
7
8
9
10
现在我们从源码角度来看watch的实现,watch的初始化是调用initWatch
方法完成的。
- initWatch,初始化Watch,调用
createWatcher
方法创建watcher。
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
// handler可能是多个监听方法名或者多个监听对象
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- createWatcher,会调用内部的
vm.$watch
方法创建Watcher。
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
// 如果是纯对象
if (isPlainObject(handler)) {
options = handler // 拿到options
handler = handler.handler // 拿到监听处理函数handler
}
// 如果是字符串,则说明是个方法名,则从vm里面取出这个方法
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vm.$watch
方法在src\core\instance\state.js
stateMixin方法中定义。
export function stateMixin (Vue: Class<Component>) {
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
// 如果cb是对象,即上面传进来的handler,则递归调用createWatcher直到cb是一个函数
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true // 标记为user watcher
const watcher = new Watcher(vm, expOrFn, cb, options) // 实例化一个watcher,cb是更新函数
if (options.immediate) {
try {
// 如果需要立即执行,则直接调用cb方法
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
// 返回移除这个watcher的方法
return function unwatchFn () {
watcher.teardown()
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
通过以上分析,我们知道了侦听器watch方法的实现原理,侦听属性 watch 最终会调用 $watch 方法,这个方法首先判断 cb ,如果是一个对象,则调用 createWatcher 方法,这是因为 $watch 方法是用户可以直接调用的,它可以传递一个对象,也可以传递函数。接着执行 const watcher = new Watcher(vm, expOrFn, cb, options)
实例化了一个 watcher。
这里需要注意一点,这是一个 user watcher,因为 options.user = true
。通过实例化 watcher 的方式,一旦我们 watch 的数据发送变化,它最终会执行 watcher 的 run 方法,执行回调函数 cb,并且如果我们设置了 immediate 为 true,则直接会执行回调函数 cb。最后返回了一个 unwatchFn 方法,它会调用 teardown 方法去移除这个 watcher。
# 二、vue-router
# 实现简易版vue-router
import Vue from "vue";
class VueRouter {
constructor(options) {
this.$options = options;
this.routeMap = {}; // 路由map,key是path,value是导入的组件
// 借助Vue构造函数实现路由响应式
this.app = new Vue({
data: {
current: "/"
}
});
}
init() {
this.bindEvents(); //初始化事件,监听url变化
this.createRouteMap(this.$options); //解析路由配置
this.initComponent(); // 实现两个组件
}
bindEvents() {
window.addEventListener("load", this.onHashChange.bind(this));
window.addEventListener("hashchange", this.onHashChange.bind(this));
}
onHashChange() {
// 拿到当前的hash值
this.app.current = window.location.hash.slice(1) || "/";
}
createRouteMap(options) {
options.routes.forEach(item => {
this.routeMap[item.path] = item.component;
});
}
initComponent() {
// 实现router-link组件
Vue.component("router-link", {
props: { to: String },
render(h) {
// h(tag, data, children)
return h("a", { attrs: { href: "#" + this.to } }, [
this.$slots.default
]);
}
});
// 实现router-view组件
Vue.component("router-view", {
render: h => {
const comp = this.routeMap[this.app.current];
return h(comp);
}
});
}
}
VueRouter.install = function(Vue) {
// 混入
Vue.mixin({
beforeCreate() {
// this是Vue实例
if (this.$options.router) {
// 仅在根组件执行一次
Vue.prototype.$router = this.$options.router;
this.$options.router.init();
}
}
});
};
Vue.use(VueRouter);
export default new VueRouter({
routes: [{ path: "/", component: Home }, { path: "/about", component: About }]
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 三、vuex
# 实现简易版vuex
import Vue from 'vue'
let Vue;
class Store {
constructor(options) {
this.state = new Vue({
data: options.state
});
this.mutations = options.mutations;
this.actions = options.actions;
options.getters && this.handleGetters(options.getters)
}
// 声明为箭头函数,确保this执向正确
commit = (type, arg) => {
this.mutations[type](this.state, arg);
};
dispatch(type, arg) {
this.actions[type]({
commit: this.commit,
state: this.state
}, arg);
}
handleGetters(getters) {
this.getters = {};
// 遍历getters所有key
Object.keys(getters).forEach(key => {
// 为this.getters定义若干属性,这些属性是只读的
// $store.getters.score
Object.defineProperty(this.getters, key, {
get: () => {
return getters[key](this.state);
}
})
})
}
}
function install(_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store;
}
}
});
}
const Vuex ={ Store, install }
export default Vuex;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
← Vue源码解读(二) Vue高级特性 →