@@ -4,57 +4,59 @@ type: guide
4
4
order : 15
5
5
---
6
6
7
- 大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue.js 最显著的一个功能是响应系统,只是普通的JavaScript对象模型 ,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。本节中,下面我们开始深挖 Vue.js 响应系统的底层细节。
7
+ 大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue 最显著的一个功能是响应系统 —— 模型只是普通对象 ,修改它则更新视图。这会让状态管理变得非常简单且直观,不过理解它的原理以避免一些常见的陷阱也是很重要的。在本节中,我们将开始深挖 Vue 响应系统的底层细节。
8
8
9
9
## 如何追踪变化
10
10
11
- 把一个普通Javascript对象传给 Vue 实例作为它的 ` data ` 选项,Vue.js 将遍历它的属性,用 [ Object.defineProperty] ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty ) 将它们转为 ` getter/setter ` 。这是 ES5 特性 ,不能打补丁实现,这便是为什么 Vue.js 不支持 IE8 及更低版本 。
11
+ 把一个普通 Javascript 对象传给 Vue 实例来作为它的 ` data ` 选项,Vue 将遍历它的属性,用 [ Object.defineProperty] ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty ) 将它们转为 getter/setter。这是 ES5 的特性 ,不能打补丁实现,这便是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因 。
12
12
13
- 用户看不到 getter/setters,但是在内部它们让 Vue.js追踪依赖,在属性被访问和修改时告知。需要注意的问题是在浏览器控制台打印数据对象时 , getter/setter 需要设置不同格式,则需要安装 ` vue devtools ` 以更加友好的检查接口 。
14
-
15
- 每个组件实例都有相应的** watcher** 程序实例,在计算过程它把属性记录为依赖,当触发时依赖项的 ` setter ` 被调用时,通知 `` watcher `` 重新计算,从而致使它关联的组件得以更新。
13
+ 用户看不到 getter/setters,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 [ vue- devtools] ( https://github.com/vuejs/vue-devtools ) 来获取更加友好的检查接口 。
14
+
15
+ 每个组件实例都有相应的 ** watcher** 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 ` 】 setter` 被调用时,会通知 ` watcher ` 重新计算,从而致使它关联的组件得以更新。
16
16
17
17
![ data] ( /images/data.png )
18
18
19
19
## 变化检测问题
20
20
21
-
22
- 由于现代 JavaScript (放弃了 ` Object.observe ` ) 的局限性,Vue ** 不能检测到的属性添加或删除** 。因为 Vue 在实例初始化期间执行的 getter/setter 转换过程,属性必须存在于此 ` data ` 对象中,以便 Vue 转换它,使其响应式。例如︰
21
+ 受现代 Javascript 的限制(以及 ` Object.observe ` 的废弃),Vue ** 不能检测到对象属性的添加或删除** 。因为 Vue 在初始化实例时将属性转为 ` getter/setter ` ,所以属性必须在 ` data ` 对象上才能让 Vue 转换它,这样才能让它是响应的。例如:
23
22
24
23
``` js
25
24
var vm = new Vue ({
26
25
data: {
27
26
a: 1
28
27
}
29
28
})
30
- // `vm.a`是响应式
29
+
30
+ // `vm.a` 是响应的
31
31
32
32
vm .b = 2
33
- // `vm.b` 是非响应式
33
+ // `vm.b` 是非响应的
34
34
```
35
35
36
- Vue 不允许动态地将新的顶级响应属性添加到已经创建的实例。然而,它是可能将响应属性添加到嵌套的对象,可以使用 ` Vue.$ set(key, value) ` 实例方法 :
36
+ Vue 不允许动态地将新的顶级响应属性添加到已经创建的实例上。然而它可能使用 ` Vue.set(object, key, value) ` 方法将响应属性添加到嵌套的对象上 :
37
37
38
38
``` js
39
39
Vue .set (vm .someObject , ' b' , 2 )
40
40
41
41
```
42
- 您还可以使用` vm.$set ` 实例方法,也是全局 ` Vue.set ` 的别名:
43
- ``` js
42
+ 您还可以使用 ` vm.$set ` 实例方法,这也是全局 ` Vue.set ` 方法的别名:
43
+
44
+ ``` js
44
45
this .$set (this .someObject ,' b' ,2 )
45
46
```
46
47
47
- 有时你想向已有对象上添加一些属性,例如使用 ` Object.assign() ` 或 ` _.extend() ` 添加属性 。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性 :
48
+ 有时你想向已有对象上添加一些属性,例如使用 ` Object.assign() ` 或 ` _.extend() ` 方法来添加属性 。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性 :
48
49
49
50
``` js
50
- // 不使用 `Object.assign(this.someObject, { a: 1, b: 2 })`
51
+ // 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
51
52
this .someObject = Object .assign ({}, this .someObject , { a: 1 , b: 2 })
52
53
```
53
54
54
55
也有一些数组相关的问题,之前已经在[ 列表渲染] ( /guide/list.html#Caveats ) 中讲过。
55
56
56
- ## 声明响应式属性
57
- 由于 Vue 不允许动态添加根数据属性,所以你必须在初始化实例前声明根数据属性,哪怕只是一个空值:
57
+ ## 声明响应属性
58
+ 由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明根级响应式属性,哪怕只是一个空值:
59
+
58
60
``` js
59
61
var vm = new Vue ({
60
62
data: {
@@ -66,16 +68,17 @@ var vm = new Vue({
66
68
// 之后设置 `message`
67
69
vm .message = ' Hello!'
68
70
```
71
+
72
+ 如果你不在 data 对象中声明 ` message ` ,Vue 将发出警告表明你的渲染方法正试图访问一个不存在的属性。
69
73
70
- 如果你不声明中数据选项的` 消息 ` ,Vue 将警告你的渲染功能试图访问一个不存在的属性。
71
74
72
- 这种限制背后是有技术的原因,它消除了边缘情况下依赖项跟踪系统中一类,也使得 Vue 实例和类型检查系统发挥。但也是一个重要的考虑,在代码可维护性方面:数据对象就像组件状态的模式 (Schema),在它上面声明所有的属性让组织代码更易于其他开发者阅读理解 。
75
+ 这样的限制在背后是有其技术原因的,在依赖项跟踪系统中,它消除了一类边界情况,也使 Vue 实例在类型检查系统的帮助下运行的更高效。在代码可维护性方面上这也是重要的一点: ` data ` 对象就像组件状态的模式 (Schema),在它上面声明所有的属性让组织代码更易于被其他开发者或是自己回头重新阅读时更加快速地理解 。
73
76
74
77
## 异步更新队列
75
78
76
- 默认情况下, Vue 执行 DOM 更新 ** 异步 ** ,只要观察到的数据更改,它将打开一个队列和缓冲区发生的所有数据更改在相同的事件循环。如果相同的观察者多次触发,它将会只有一次推送到队列 。然后,在接下来的事件循环"时钟"中 ,Vue 刷新队列并执行仅有必要的 DOM 更新。Vue内部使用 ` Promise.then ` 和 ` MutationObserver ` 为可用的异步队列调用回调` setTimeout(fn, 0) ` .
79
+ 你应该注意到 Vue 执行 DOM 更新是 ** 异步的 ** ,只要观察到数据变化,Vue 就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个 watcher 被多次触发,只会推入一次到队列中 。然后,在接下来的事件循环中 ,Vue 刷新队列并仅执行必要的 DOM 更新。Vue 在内部使用 ` Promise.then ` 和 ` MutationObserver ` 为可用的异步队列调用回调 ` setTimeout(fn, 0) ` .
77
80
78
- 例如,当你设置` vm.someData = 'new value' ` , 该组件不会马上被重新渲染。当刷新队列时,这个组件将会在下一个的'时钟'中更新。很多时候我们不需要关心这个,但可可能会非常的棘手,当你要执行某个动作的时候,将会取决于后更新的DOM状态。一般地 ,Vue.js鼓励开发人员用“数据驱动”的方式,尽量避免直接接触DOM,因为有时是完全没有必要。等待Vue.js已完成DOM数据更改后,可以使用 ` Vue.nextTick(callback) ` 实时更改数据,之后更新DOM会调用回调 。例如:
81
+ 例如,当你设置 ` vm.someData = 'new value' ` , 该组件不会马上被重新渲染。当刷新队列时,这个组件会在下一次事件循环清空队列时更新。我们基本不用关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。一般来讲 ,Vue 鼓励开发者沿着数据驱动的思路,尽量避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 ` Vue.nextTick(callback) ` 。这样回调在 DOM 更新完成后就会调用 。例如:
79
82
80
83
``` html
81
84
<div id =" example" >{{message}}</div >
@@ -93,7 +96,7 @@ Vue.nextTick(function () {
93
96
vm .$el .textContent === ' new message' // true
94
97
})
95
98
```
96
- 这也是 ` vm.$nextTick() ` 实例方法,是在组件内特别方便, 因为它不需要全局` Vue ` 和其回调 ` this ` 上下文将自动绑定到当前的Vue实例 :
99
+ ` vm.$nextTick() ` 这个实例方法在组件内使用特别方便, 因为它不需要全局 ` Vue ` ,它的回调 ` this ` 将自动绑定到当前的 Vue 实例上 :
97
100
``` js
98
101
Vue .component (' example' , {
99
102
template: ' <span>{{ message }}</span>' ,
0 commit comments