|
@@ -1197,9 +1197,128 @@ dep.notify()
|
|
|
|
|
|
至此 `set` 函数我们就讲解完毕了。
|
|
|
|
|
|
-###### 保证程序行为的一致性
|
|
|
+###### 保证定义响应式数据行为的一致性
|
|
|
|
|
|
+本节我们主要讲解 `defineReactive` 函数中一段代码,即:
|
|
|
|
|
|
+```js
|
|
|
+if ((!getter || setter) && arguments.length === 2) {
|
|
|
+ val = obj[key]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+在之前的讲解中,我们没有详细的讲解如上代码所示的这段 `if` 语句块。该 `if` 语句有两个条件:
|
|
|
+
|
|
|
+* 第一:`(!getter || setter)`
|
|
|
+* 第二:`arguments.length === 2`
|
|
|
+
|
|
|
+并且这两个条件要同时满足才能会根据 `key` 去对象 `obj` 上取值:`val = obj[key]`,否则就不会触发取值的动作,触发不了取值的动作就意味着 `val` 的值为 `undefined`,这会导致 `if` 语句块后面的那句深度观测的代码无效,即不会深度观测:
|
|
|
+
|
|
|
+```js
|
|
|
+// val 是 undefined,不会深度观测
|
|
|
+let childOb = !shallow && observe(val)
|
|
|
+```
|
|
|
+
|
|
|
+对于第二个条件,很好理解,当传递参数的数量为 `2` 时,说明没有传递第三个参数 `val`,那么当然需要通过执行 `val = obj[key]` 去获取属性值。比较难理解的是第一个条件,即 `(!getter || setter)`,要理解这个问题你需要知道 `Vue` 代码的变更,以及为什么变更。其实在最初并没有上面这段 `if` 语句块,在 `walk` 函数中是这样调用 `defineReactive` 函数的:
|
|
|
+
|
|
|
+```js
|
|
|
+walk (obj: Object) {
|
|
|
+ const keys = Object.keys(obj)
|
|
|
+ for (let i = 0; i < keys.length; i++) {
|
|
|
+ // 这里传递了第三个参数
|
|
|
+ defineReactive(obj, keys[i], obj[keys[i]])
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+可以发现在调用 `defineReactive` 函数的时候传递了第三个参数,即属性值。这是最初的实现,后来变成了如下这样:
|
|
|
+
|
|
|
+```js
|
|
|
+walk (obj: Object) {
|
|
|
+ const keys = Object.keys(obj)
|
|
|
+ for (let i = 0; i < keys.length; i++) {
|
|
|
+ // 在 walk 函数中调用 defineReactive 函数时暂时不获取属性值
|
|
|
+ defineReactive(obj, keys[i])
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ================= 分割线 =================
|
|
|
+
|
|
|
+// 在 defineReactive 函数内获取属性值
|
|
|
+if (!getter && arguments.length === 2) {
|
|
|
+ val = obj[key]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+在 `walk` 函数中调用 `defineReactive` 函数时去掉了第三个参数,而是在 `defineReactive` 函数体内增加了一段 `if` 分支语句,当发现调用 `defineReactive` 函数时传递了两个参数,同时只有在属性没有 `get` 函数的情况下才会通过 `val = obj[key]` 取值。
|
|
|
+
|
|
|
+为什么要这么做呢?具体可以查看这个 [issue](https://github.com/vuejs/vue/pull/7302)。简单的说就是当属性原本存在 `get` 拦截器函数时,在初始化的时候不要触发 `get` 函数,只有当真正的获取该属性的值的时候,在通过调用缓存下来的属性原本的 `getter` 函数取值即可。所以看到这里我们能够发现,如果数据对象的某个属性原本就拥有自己的 `get` 函数,那么这个属性就不会被深度观测,因为当属性原本存在 `getter` 时,是不会触发取值动作的,即 `val = obj[key]` 不会执行,所以 `val` 是 `undefined`,这就导致在后面深度观测的语句中传递给 `observe` 函数的参数是 `undefined`。
|
|
|
+
|
|
|
+举个例子,如下:
|
|
|
+
|
|
|
+```js
|
|
|
+const data = {
|
|
|
+ getterProp: {
|
|
|
+ a: 1
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+new Vue({
|
|
|
+ data,
|
|
|
+ watch: {
|
|
|
+ 'getterProp.a': () => {
|
|
|
+ console.log('这句话会输出')
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+上面的代码中,我们定义了数据 `data`,`data` 是一个嵌套的对象,在 `watch` 选项中观察了属性 `getterProp.a`,当我们修改 `getterProp.a` 的值时,以上代码是能够正常输出的,这也是预期行为。再看如下代码:
|
|
|
+
|
|
|
+```js
|
|
|
+const data = {}
|
|
|
+Object.defineProperty(data, 'getterProp', {
|
|
|
+ enumerable: true,
|
|
|
+ get: () => {
|
|
|
+ return {
|
|
|
+ a: 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const ins = new Vue({
|
|
|
+ data,
|
|
|
+ watch: {
|
|
|
+ 'getterProp.a': () => {
|
|
|
+ console.log('这句话不会输出')
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+我们仅仅修改了定义数据对象 `data` 的方式,此时 `data.getterProp` 本身已经是一个访问器属性,且拥有 `get` 方法。此时当我们尝试修改 `getterProp.a` 的值时,在 `watch` 中观察 `getterProp.a` 的函数不会被执行。这是因为属性 `getterProp` 是一个拥有 `get` 拦截器函数的访问器属性,而当 `Vue` 发现该属性拥有原本的 `getter` 时,是不会深度观测的。
|
|
|
+
|
|
|
+那么为什么当属性拥有自己的 `getter` 时就不会对其深度观测了呢?有两方面的原因,第一:由于当属性存在原本的 `getter` 时在深度观测之前不会取值,所以在在深度观测语句执行之前取不到属性值从而无法深度观测。第二:之所以在深度观测之前不取值是因为属性原本的 `getter` 由用户定义,用户可能在 `getter` 中做任何意想不到的事情,这么做是出于避免引发不必要的行为的考虑。
|
|
|
+
|
|
|
+我们回过头来再看这段 `if` 语句块:
|
|
|
+
|
|
|
+```js
|
|
|
+if (!getter && arguments.length === 2) {
|
|
|
+ val = obj[key]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+这么做难道不会有什么问题吗?当然有问题,我们知道当数据对象的某一个属性只拥有 `get` 拦截器函数而没有 `set` 拦截器函数时,此时该属性不会被深度观测。但是经过 `defineReactive` 函数的处理之后,该属性将被重新定义 `getter` 和 `setter`,此时该属性变成了既拥有 `get` 函数又拥有 `set` 函数。并且当我们尝试给该属性重新赋值时,那么新的值将会被观测。这时候矛盾就产生了:**原本该属性不会被深度观测,但是重新赋值之后,新的值却被观测了**。
|
|
|
+
|
|
|
+这就是所谓的**定义响应式数据时行为的不一致**,为了解决这个问题,我们的解决办法是当属性拥有原本的 `setter` 时,即使拥有 `getter` 也要获取属性值并观测之,这样代码就变成了最终这个样子:
|
|
|
+
|
|
|
+```js
|
|
|
+if ((!getter || setter) && arguments.length === 2) {
|
|
|
+ val = obj[key]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+##### 响应式数据之数组的处理
|
|
|
|
|
|
|
|
|
|