Skip to content

Commit 2c982fb

Browse files
committed
提交了深入响应式原理翻译
1 parent a229ea4 commit 2c982fb

File tree

1 file changed

+25
-203
lines changed

1 file changed

+25
-203
lines changed

src/guide/reactivity.md

Lines changed: 25 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,43 @@ type: guide
44
order: 15
55
---
66

7-
大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue.js 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。下面我们开始深挖 Vue.js 响应系统的底层细节。
7+
大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue.js 最显著的一个功能是响应系统,只是普通的JavaScript对象模型,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。本节中,下面我们开始深挖 Vue.js 响应系统的底层细节。
88

99
## 如何追踪变化
1010

11-
把一个普通对象传给 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.js 将遍历它的属性,用 [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 将它们转为 `getter/setter`。这是 ES5 特性,不能打补丁实现,这便是为什么 Vue.js 不支持 IE8 及更低版本。
1212

13-
用户看不到 getter/setters,但是在内部它们让 Vue.js 追踪依赖,在属性被访问和修改时通知变化。一个问题是在浏览器控制台打印数据对象时 `getter/setter` 的格式化不同,使用 `vm.$log()` 实例方法可以得到更友好的输出。
13+
用户看不到 getter/setters,但是在内部它们让 Vue.js追踪依赖,在属性被访问和修改时通知变化。一个问题是在浏览器控制台打印数据对象时 getter/setter 的格式化不同,使用 `vm.$log()` 实例方法可以得到更友好的输出。所以你可能想要安装`vue-devtools`更多的检查接口
1414

1515
模板中每个指令/数据绑定都有一个对应的 **watcher** 对象,在计算过程中它把属性记录为依赖。之后当依赖的 `setter` 被调用时,会触发 **watcher** 重新计算 ,也就会导致它的关联指令更新 DOM。
1616

17-
> !!TODO: 因为vdom层的引入,这里可能会更新。
18-
1917
![data](/images/data.png)
2018

2119
## 变化检测问题
2220

23-
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 `getter/setter`,所以属性必须在 `data` 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:
21+
受 ES5 的限制,Vue.js **不能检测到对象属性的添加或删除**。因为 Vue.js 在初始化实例时将属性转为 `getter/setter`,所以属性必须在 `data` 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:
2422

2523
``` js
26-
var data = { a: 1 }
2724
var vm = new Vue({
28-
data: data
25+
data:{
26+
a:1
27+
}
2928
})
30-
// `vm.a` and `data.a` 现在是响应的
29+
//`vm.a`是响应
3130

3231
vm.b = 2
33-
// `vm.b` 不是响应的
34-
35-
data.b = 2
36-
// `data.b` 不是响应的
32+
// `vm.b` 是非响应
3733
```
3834

39-
不过,有办法在实例创建之后**添加属性并且让它是响应的**
40-
41-
对于 Vue 实例,可以使用 `Vue.$set(key, value)` 实例方法:
35+
Vue 不允许动态地将新的顶级响应属性添加到已经创建的实例。然而,它是可能将响应属性添加到嵌套的对象,可以使用 `Vue.$set(key, value)` 实例方法:
4236

4337
``` js
44-
Vue.set(data, 'c', 3)
45-
// `vm.c` and `data.c` 现在是响应的
38+
Vue.set(vm.someObject, 'b', 2)
39+
40+
```
41+
您还可以使用`vm.$set`实例方法,也是全局 `Vue.set`的别名:
42+
```js
43+
this.$set(this.someObject,'b',2)
4644
```
4745

4846
有时你想向已有对象上添加一些属性,例如使用 `Object.assign()``_.extend()`添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:
@@ -56,22 +54,7 @@ this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
5654

5755
## 初始化数据
5856

59-
尽管 Vue.js 提供了 API 动态地添加响应属性,还是推荐在 `data` 对象上声明所有的响应属性。
60-
61-
不这么做:
62-
63-
不这么做:
64-
65-
``` js
66-
var vm = new Vue({
67-
template: '<div>{{ message }}</div>'
68-
})
69-
// 之后设置 `message`
70-
Vue.set(vm.$data, 'message', 'Hello!')
71-
```
72-
73-
这么做:
74-
57+
由于 Vue 不允许动态添加顶级响应属性,这意味着你必须初始化声明的所有顶级响应前期数据属性,哪怕是有空值的实例:
7558
``` js
7659
var vm = new Vue({
7760
data: {
@@ -84,193 +67,32 @@ var vm = new Vue({
8467
vm.message = 'Hello!'
8568
```
8669

87-
这么做有两个原因:
70+
如果你不声明中数据选项的消息,Vue 将警告你的渲染功能试图访问一个不存在的属性。
8871

89-
1. `data` 对象就像组件状态的模式(schema)。在它上面声明所有的属性让组件代码更易于理解。
90-
91-
2.添加一个顶级响应属性会强制所有的 watcher 重新计算,因为它之前不存在,没有 watcher 追踪它。这么做性能通常是可以接受的(特别是对比 Angular 的脏检查),但是可以在初始化时避免
72+
这种限制背后是有技术的原因,它消除了边缘情况下依赖项跟踪系统中一类,也使得 Vue 实例和类型检查系统发挥。但也是一个重要的考虑,在代码可维护性方面:数据对象就像组件状态的模式(Schema),在它上面声明所有的属性让组织代码更易于其他开发者阅读理解。
9273

9374
## 异步更新队列
9475

95-
> !!TODO: 我认为这里很大部分已经失效,需要尤小右来重写
96-
97-
By default, Vue performs DOM updates **asynchronously**. Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. Then, in the next event loop "tick", Vue flushes the queue and performs only the necessary DOM updates. Internally Vue uses `MutationObserver` if available for the asynchronous queuing and falls back to `setTimeout(fn, 0)`.
76+
默认情况下,Vue 执行 DOM 更新**异步**,只要观察到的数据更改,它将打开一个队列和缓冲区发生的所有数据更改在相同的事件循环。如果相同的观察者多次触发,它将会只有一次推送到队列。然后,在接下来的事件循环"时钟"中,Vue 刷新队列并执行仅有必要的 DOM 更新。Vue内部使用`MutationObserver` 如果可用的异步队列调用回调`setTimeout(fn, 0)`.
9877

99-
For example, when you set `vm.someData = 'new value'`, the DOM will not update immediately. It will update in the next "tick", when the queue is flushed. Most of the time we don't need to care about this, but it can be tricky when you want to do something that depends on the post-update DOM state. Although Vue.js generally encourages developers to think in a "data-driven" fashion and avoid touching the DOM directly, sometimes it might be necessary to get your hands dirty. In order to wait until Vue.js has finished updating the DOM after a data change, you can use `Vue.nextTick(callback)` immediately after the data is changed. The callback will be called after the DOM has been updated. For example:
100-
101-
``` html
102-
<div id="example">{{ message }}</div>
78+
例如,当你设置`vm.someData = 'new value'`,该组件不会马上被重新渲染。当刷新队列时,这个组件将会在下一个的'时钟'中更新。很多时候我们不需要关心这个,但可可能会非常的棘手,当你要执行某个动作的时候,将会取决于后更新的DOM状态。一般地,Vue.js鼓励开发人员用“数据驱动”的方式,尽量避免直接接触DOM,因为有时是完全没有必要。等待Vue.js已完成DOM数据更改后,可以使用`Vue.nextTick(callback)`实时更改数据,之后更新DOM会调用回调。例如:
79+
```html
80+
<div id="example">{{message}}</div>
10381
```
104-
10582
``` js
10683
var vm = new Vue({
10784
el: '#example',
10885
data: {
10986
message: '123'
11087
}
11188
})
112-
vm.message = 'new message' // change data
89+
vm.message = 'new message' // 更改数据
11390
vm.$el.textContent === 'new message' // false
11491
Vue.nextTick(function () {
11592
vm.$el.textContent === 'new message' // true
11693
})
11794
```
118-
119-
There is also the `vm.$nextTick()` instance method, which is especially handy inside components, because it doesn't need global `Vue` and its callback's `this` context will be automatically bound to the current Vue instance:
120-
121-
``` js
122-
Vue.component('example', {
123-
template: '<span>{{ message }}</span>',
124-
data: function () {
125-
return {
126-
message: 'not updated'
127-
}
128-
},
129-
methods: {
130-
updateMessage: function () {
131-
this.message = 'updated'
132-
console.log(this.$el.textContent) // => 'not updated'
133-
this.$nextTick(function () {
134-
console.log(this.$el.textContent) // => 'updated'
135-
})
136-
}
137-
}
138-
})
139-
```
140-
141-
## Inside Computed Properties
142-
143-
It should be noted that Vue's computed properties are **not** simple getters. Each computed property keeps track of its own reactive dependencies. When a computed property is evaluated, Vue updates its dependency list and caches the returned value. The cached value is only invalidated when one of the tracked dependencies have changed. Therefore, as long as the dependencies did not change, accessing the computed property will directly return the cached value instead of calling the getter.
144-
145-
Why do we need caching? Imagine we have an expensive computed property **A**, which requires looping through a huge Array and doing a lot of computations. Then we may have other computed properties that in turn depend on **A**. Without caching, we would be executing **A**’s getter many more times than necessary!
146-
147-
Because of computed property caching, the getter function is not always called when you access a computed property. Consider the following example:
148-
149-
``` js
150-
var vm = new Vue({
151-
data: {
152-
message: 'hi'
153-
},
154-
computed: {
155-
example: function () {
156-
return Date.now() + this.message
157-
}
158-
}
159-
})
160-
```
161-
162-
The computed property `example` has only one dependency: `vm.message`. `Date.now()` is **not** a reactive dependency, because it has nothing to do with Vue's data observation system. Therefore, when you programmatically access `vm.example`, you will find the timestamp remains the same unless `vm.message` triggers a re-evaluation.
163-
164-
In some use cases, you may want to preserve the simple getter-like behavior, where every time you access `vm.example` it simply calls the getter again. You can do that by turning off caching for a specific computed property:
165-
166-
``` js
167-
computed: {
168-
example: {
169-
cache: false,
170-
get: function () {
171-
return Date.now() + this.message
172-
}
173-
}
174-
}
175-
```
176-
177-
Now, every time you access `vm.example`, the timestamp will be up-to-date. **However, note this only affects programmatic access inside JavaScript; data-bindings are still dependency-driven.** When you bind to a computed property in the template as `{% raw %}{{ example }}{% endraw %}`, the DOM will only be updated when a reactive dependency has changed.
178-
179-
We've covered most of the basics - now it's time to take a deep dive! One of Vue's most distinct features is the unobtrusive reactivity system. Models are just plain JavaScript objects. When you modify them, the view updates. It makes state management very simple and intuitive, but it's also important to understand how it works to avoid some common gotchas. In this section, we are going to dig into some of the lower-level details of Vue's reactivity system.
180-
181-
## How Changes Are Tracked
182-
183-
When you pass a plain JavaScript object to a Vue instance as its `data` option, Vue will walk through all of its properties and convert them to getter/setters using [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). This is an ES5-only and un-shimmable feature, which is why Vue doesn't support IE8 and below.
184-
185-
The getter/setters are invisible to the user, but under the hood they enable Vue to perform dependency-tracking and change-notification when properties are accessed or modified. One caveat is that browser consoles format getter/setters differently when converted data objects are logged, so you may want to install [vue-devtools](https://github.com/vuejs/vue-devtools) for a more inspection-friendly interface.
186-
187-
Every component instance has a corresponding **watcher** instance, which records any properties "touched" during the component's render as dependencies. Later on when a dependency's setter is triggered, it notifies the watcher, which in turn causes the component to re-render.
188-
189-
![Reactivity Cycle](/images/data.png)
190-
191-
## Change Detection Caveats
192-
193-
Due to the limitations of modern JavaScript (and the abandonment of `Object.observe`), Vue **cannot detect property addition or deletion**. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the `data` object in order for Vue to convert it and make it reactive. For example:
194-
195-
``` js
196-
var vm = new Vue({
197-
data: {
198-
a: 1
199-
}
200-
})
201-
// `vm.a` is now reactive
202-
203-
vm.b = 2
204-
// `vm.b` is NOT reactive
205-
```
206-
207-
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it's possible to add reactive properties to a nested object using the `Vue.set(object, key, value)` method:
208-
209-
``` js
210-
Vue.set(vm.someObject, 'b', 2)
211-
```
212-
213-
You can also use the `vm.$set` instance method, which is just an alias to the global `Vue.set`:
214-
215-
``` js
216-
this.$set(this.someObject, 'b', 2)
217-
```
218-
219-
Sometimes you may want to assign a number of properties to an existing object, for example using `Object.assign()` or `_.extend()`. However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object:
220-
221-
``` js
222-
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
223-
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
224-
```
225-
226-
There are also a few array-related caveats, which were discussed earlier in the [list rendering section](/guide/list.html#Caveats).
227-
228-
## Declaring Reactive Properties
229-
230-
Since Vue doesn't allow dynamically adding root-level reactive properties, this means you have to initialize you instances by declaring all root-level reactive data properties upfront, even just with an empty value:
231-
232-
``` js
233-
var vm = new Vue({
234-
data: {
235-
// declare message with an empty value
236-
message: ''
237-
},
238-
template: '<div>{{ message }}</div>'
239-
})
240-
// set `message` later
241-
vm.message = 'Hello!'
242-
```
243-
244-
If you don't declare `message` in the data option, Vue will warn you that the render function is trying to access a property that doesn't exist.
245-
246-
There are technical reasons behind this restriction - it eliminates a class of edge cases in the dependency tracking system, and also makes Vue instances play nicer with type checking systems. But there is also an important consideration in terms of code maintainability: the `data` object is like the schema for your component's state. Declaring all reactive properties upfront makes the component code easier to understand when revisited later or read by another developer.
247-
248-
## Async Update Queue
249-
250-
In case you haven't noticed yet, Vue performs DOM updates **asynchronously**. Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop "tick", Vue flushes the queue and performs the actual (already de-duped) work. Internally Vue uses `MutationObserver` if available for the asynchronous queuing and falls back to `setTimeout(fn, 0)`.
251-
252-
For example, when you set `vm.someData = 'new value'`, the component will not re-render immediately. It will update in the next "tick", when the queue is flushed. Most of the time we don't need to care about this, but it can be tricky when you want to do something that depends on the post-update DOM state. Although Vue.js generally encourages developers to think in a "data-driven" fashion and avoid touching the DOM directly, sometimes it might be necessary to get your hands dirty. In order to wait until Vue.js has finished updating the DOM after a data change, you can use `Vue.nextTick(callback)` immediately after the data is changed. The callback will be called after the DOM has been updated. For example:
253-
254-
``` html
255-
<div id="example">{{ message }}</div>
256-
```
257-
258-
``` js
259-
var vm = new Vue({
260-
el: '#example',
261-
data: {
262-
message: '123'
263-
}
264-
})
265-
vm.message = 'new message' // change data
266-
vm.$el.textContent === 'new message' // false
267-
Vue.nextTick(function () {
268-
vm.$el.textContent === 'new message' // true
269-
})
270-
```
271-
272-
There is also the `vm.$nextTick()` instance method, which is especially handy inside components, because it doesn't need global `Vue` and its callback's `this` context will be automatically bound to the current Vue instance:
273-
95+
这也是`vm.$nextTick()`实例方法,是在组件内特别方便,因为它不需要全局`Vue`和其回调`this`上下文将自动绑定到当前的Vue实例:
27496
``` js
27597
Vue.component('example', {
27698
template: '<span>{{ message }}</span>',

0 commit comments

Comments
 (0)