|
@@ -2033,7 +2033,7 @@ export function initState (vm: Component) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-如上高亮代码所示,在这个 `if` 条件语句块中,调用 `initWatch` 函数,这个函数用来初始化 `watch` 选项,至于判断条件我们就不多讲了,前面讲解中我们已经讲解过类似的判断条件。至于 `initWatch` 函数,它就定义在 `createWatcher` 函数的上方,如下是其全部代码:
|
|
|
+如上高亮代码所示,在这个 `if` 条件语句块中,调用 `initWatch` 函数,这个函数用来初始化 `watch` 选项,至于判断条件我们就不多讲了,前面的讲解中我们已经讲解过类似的判断条件。至于 `initWatch` 函数,它就定义在 `createWatcher` 函数的上方,如下是其全部代码:
|
|
|
|
|
|
```js
|
|
|
function initWatch (vm: Component, watch: Object) {
|
|
@@ -2226,7 +2226,7 @@ if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-这段代码是对参数 `val` 的检查,后面我们统一称 `val` 为**被观察属性的值**,我们知道既然是深度观测,所以被观察属性的值要么是一个对象要么是一个数组,并且该值不能是冻结的,同时也不应该是 `VNode` 实例(这是Vue单独做的限制)。只有当被观察属性的值满足这些条件时,才会对其进行深度观测,只要有一项不满足 `_traverse` 就会 `return` 结束执行。所以上面这段 `if` 语句可以理解为是在检测被观察属性的值能否进行深度观测,一旦能够深度观测将会继续执行之后的代码,如下:
|
|
|
+这段代码是对参数 `val` 的检查,后面我们统一称 `val` 为 **被观察属性的值**,我们知道既然是深度观测,所以被观察属性的值要么是一个对象要么是一个数组,并且该值不能是冻结的,同时也不应该是 `VNode` 实例(这是Vue单独做的限制)。只有当被观察属性的值满足这些条件时,才会对其进行深度观测,只要有一项不满足 `_traverse` 就会 `return` 结束执行。所以上面这段 `if` 语句可以理解为是在检测被观察属性的值能否进行深度观测,一旦能够深度观测将会继续执行之后的代码,如下:
|
|
|
|
|
|
```js
|
|
|
if (isA) {
|
|
@@ -2253,7 +2253,7 @@ if (val.__ob__) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-这段代码的作用不容忽视,它解决了循环引用导致死循环的问题,为了更好的说明问题我们举个例子,如下:
|
|
|
+这段代码的作用不容忽视,它解决了循环引用导致死循环的问题,为了更好地说明问题我们举个例子,如下:
|
|
|
|
|
|
```js
|
|
|
const obj1 = {}
|
|
@@ -2475,7 +2475,7 @@ if (!isSSR) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-只有在非服务端渲染时才会执行 `if` 语句块内的代码,因为服务端渲染中计算属性的实现本质上和使用 `methods` 选项差不多。这里我们着重讲解非服务端渲染的实现,这时 `if` 语句块内的代码会被执行,可以看到在 `if` 语句块内创建了一个观察者实例对象,我们称之为**计算属性的观察者**,同时会把计算属性的观察者添加到 `watchers` 常量对象中,键值是对应计算属性的名字,注意由于 `watchers` 常量与 `vm._computedWatchers` 属性具有相同的引用,所以对 `watchers` 常量的修改相当于对 `vm._computedWatchers` 属性的修改,现在你应该知道了,`vm._computedWatchers` 对象是用来存储计算属性观察者的。
|
|
|
+只有在非服务端渲染时才会执行 `if` 语句块内的代码,因为服务端渲染中计算属性的实现本质上和使用 `methods` 选项差不多。这里我们着重讲解非服务端渲染的实现,这时 `if` 语句块内的代码会被执行,可以看到在 `if` 语句块内创建了一个观察者实例对象,我们称之为 **计算属性的观察者**,同时会把计算属性的观察者添加到 `watchers` 常量对象中,键值是对应计算属性的名字,注意由于 `watchers` 常量与 `vm._computedWatchers` 属性具有相同的引用,所以对 `watchers` 常量的修改相当于对 `vm._computedWatchers` 属性的修改,现在你应该知道了,`vm._computedWatchers` 对象是用来存储计算属性观察者的。
|
|
|
|
|
|
另外有几点需要注意,首先创建计算属性观察者时所传递的第二个参数是 `getter` 函数,也就是说计算属性观察者的求值对象是 `getter` 函数。传递的第四个参数是 `computedWatcherOptions` 常量,它是一个对象,定义在 `initComputed` 函数的上方:
|
|
|
|
|
@@ -2711,7 +2711,7 @@ depend () {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-此时我们已经知道了 `this.dep` 属性是一个 `Dep` 实例对象,所以 `this.dep.depend()` 这句代码的作用就是用来收集依赖。那么它收集到的东西是什么呢?这就要看 `Dep.target` 属性的值是什么了,我们回想一下整个过程:首先渲染函数的执行会读取计算属性 `compA` 的值,从而触发计算属性 `compA` 的 `get` 拦截器函数,最终调用了 `this.dep.depend()` 方法收集依赖。这个过程中的关键一步就是渲染函数的执行,我们知道在渲染函数执行之前 `Dep.target` 的值必然是**渲染函数的观察者对象**。所以计算属性观察者对象的 `this.dep` 属性中所收集的就是渲染函数的观察者对象。
|
|
|
+此时我们已经知道了 `this.dep` 属性是一个 `Dep` 实例对象,所以 `this.dep.depend()` 这句代码的作用就是用来收集依赖。那么它收集到的东西是什么呢?这就要看 `Dep.target` 属性的值是什么了,我们回想一下整个过程:首先渲染函数的执行会读取计算属性 `compA` 的值,从而触发计算属性 `compA` 的 `get` 拦截器函数,最终调用了 `this.dep.depend()` 方法收集依赖。这个过程中的关键一步就是渲染函数的执行,我们知道在渲染函数执行之前 `Dep.target` 的值必然是 **渲染函数的观察者对象**。所以计算属性观察者对象的 `this.dep` 属性中所收集的就是渲染函数的观察者对象。
|
|
|
|
|
|
记得此时计算属性观察者对象的 `this.dep` 中所收集的是渲染函数观察者对象,假设我们把渲染函数观察者对象称为 `renderWatcher`,那么:
|
|
|
|
|
@@ -2802,7 +2802,7 @@ compA () {
|
|
|
|
|
|
大家想一想这个函数的执行会发生什么事情?我们知道数据对象的 `a` 属性是响应式的,所以如上函数的执行将会触发属性 `a` 的 `get` 拦截器函数。所以这会导致属性 `a` 将会收集到一个依赖,这个依赖实际上就是计算属性的观察者对象。
|
|
|
|
|
|
-现在思路大概明朗了,如果计算属性 `compA` 依赖了数据对象的 `a` 属性,那么属性 `a` 将收集计算属性 `compA` 的**计算属性观察者对象**,而**计算属性观察者对象**将收集**渲染函数观察者对象**,整个路线是这样的:
|
|
|
+现在思路大概明朗了,如果计算属性 `compA` 依赖了数据对象的 `a` 属性,那么属性 `a` 将收集计算属性 `compA` 的 **计算属性观察者对象**,而 **计算属性观察者对象** 将收集 **渲染函数观察者对象**,整个路线是这样的:
|
|
|
|
|
|

|
|
|
|
|
@@ -2845,7 +2845,7 @@ update () {
|
|
|
|
|
|
## 同步执行观察者
|
|
|
|
|
|
-通常情况下当数据状态发生改变时,所有 `Watcher` 都为异步执行,这么做的目的是处于对性能的考虑。但在某些场景下我们仍需要同步执行的观察者,我们可以使用 `sync` 选项定义同步执行的观察者,如下:
|
|
|
+通常情况下当数据状态发生改变时,所有 `Watcher` 都为异步执行,这么做的目的是出于对性能的考虑。但在某些场景下我们仍需要同步执行的观察者,我们可以使用 `sync` 选项定义同步执行的观察者,如下:
|
|
|
|
|
|
```js
|
|
|
new Vue({
|
|
@@ -2860,7 +2860,7 @@ new Vue({
|
|
|
|
|
|
如上代码所示,我们在定义一个观察者时使用 `sync` 选项,并将其设置为 `true`,此时当数据状态发生变化时该观察者将以同步的方式执行。这么做当然没有问题,因为我们仅仅定义了一个观察者而已。
|
|
|
|
|
|
-`Vue` 官方推出了 [vue-test-utils](https://github.com/vuejs/vue-test-utils) 测试工具库,这个库的一个特点是,当你使用它去辅助测试 `Vue` 单文件组件时,数据变更将会以同步的方式触发组件变更,这对于测试而言会提供很大帮助。大家思考一下 [vue-test-utils](https://github.com/vuejs/vue-test-utils) 库是如何实现这个功能的?我们知道开发者在开发组件的时候基本不太可能手动的指定一个观察者为同步的,所以 [vue-test-utils](https://github.com/vuejs/vue-test-utils) 库需要有能力拿到组件的定义并人为的把组件中定义的所有观察者都转换为同步的,这是一个繁琐并容易引起 `bug` 的工作,为了解决这个问题,`Vue` 提供了 `Vue.config.async` 全局配置,它的默认值为 `true`,我们可以在 `src/core/config.js` 文件中看到这样一句代码,如下:
|
|
|
+`Vue` 官方推出了 [vue-test-utils](https://github.com/vuejs/vue-test-utils) 测试工具库,这个库的一个特点是,当你使用它去辅助测试 `Vue` 单文件组件时,数据变更将会以同步的方式触发组件变更,这对于测试而言会提供很大帮助。大家思考一下 [vue-test-utils](https://github.com/vuejs/vue-test-utils) 库是如何实现这个功能的?我们知道开发者在开发组件的时候基本不太可能手动地指定一个观察者为同步的,所以 [vue-test-utils](https://github.com/vuejs/vue-test-utils) 库需要有能力拿到组件的定义并人为地把组件中定义的所有观察者都转换为同步的,这是一个繁琐并容易引起 `bug` 的工作,为了解决这个问题,`Vue` 提供了 `Vue.config.async` 全局配置,它的默认值为 `true`,我们可以在 `src/core/config.js` 文件中看到这样一句代码,如下:
|
|
|
|
|
|
```js {8}
|
|
|
export default ({
|
|
@@ -2897,7 +2897,7 @@ export function queueWatcher (watcher: Watcher) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-如上高亮代码所示,在非生产环境下如何 `!config.async` 为真,则说明开发者配置了 `Vue.config.async = false`,这意味着所有观察者需要同步执行,所以只需要把原本通过 `nextTick` 包装的 `flushSchedulerQueue` 函数单独拿出来执行即可。另外通过如上高亮的代码我们也能够明白一件事儿,那就是 `Vue.config.async` 这个配置项只会在非生产环境生效。
|
|
|
+如上高亮代码所示,在非生产环境下如果 `!config.async` 为真,则说明开发者配置了 `Vue.config.async = false`,这意味着所有观察者需要同步执行,所以只需要把原本通过 `nextTick` 包装的 `flushSchedulerQueue` 函数单独拿出来执行即可。另外通过如上高亮的代码我们也能够明白一件事儿,那就是 `Vue.config.async` 这个配置项只会在非生产环境生效。
|
|
|
|
|
|
为了实现同步执行的观察者,除了把 `flushSchedulerQueue` 函数从 `nextTick` 中提取出来之外,还需要做一件事儿,我们打开 `src/core/observer/dep.js` 文件,找到 `notify` 方法,如下:
|
|
|
|
|
@@ -2917,6 +2917,4 @@ notify () {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-在异步执行观察者的时候,当数据状态方式改变时,会通过如上 `notify` 函数通知变化,从而把执行所有观察者的 `update` 方法,在 `update` 方法内会将所有即将被执行的观察者都添加到观察者队列中,并在 `flushSchedulerQueue` 函数内对观察者回调的执行顺序进行排序。但是当同步执行的观察者时,由于 `flushSchedulerQueue` 函数是立即执行的,它不会等待所有观察者入队之后再去执行,这就没有办法保证观察者回调的正确更新顺序,这时就需要如上高亮的代码,其实现方式是在执行观察者对象的 `update` 更新方法之前就对观察者进行排序,从而保证正确的更新顺序。
|
|
|
-
|
|
|
-
|
|
|
+在异步执行观察者的时候,当数据状态方式改变时,会通过如上 `notify` 函数通知变化,从而执行所有观察者的 `update` 方法,在 `update` 方法内会将所有即将被执行的观察者都添加到观察者队列中,并在 `flushSchedulerQueue` 函数内对观察者回调的执行顺序进行排序。但是当同步执行的观察者时,由于 `flushSchedulerQueue` 函数是立即执行的,它不会等待所有观察者入队之后再去执行,这就没有办法保证观察者回调的正确更新顺序,这时就需要如上高亮的代码,其实现方式是在执行观察者对象的 `update` 更新方法之前就对观察者进行排序,从而保证正确的更新顺序。
|