-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathreactive-object.html
333 lines (326 loc) · 73.8 KB
/
reactive-object.html
1
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>响应式对象 | Vue.js 技术揭秘</title>
<meta name="generator" content="VuePress 1.5.3">
<link rel="icon" href="/vue-analysis/logo.png">
<link rel="manifest" href="/vue-analysis/manifest.json">
<link rel="apple-touch-icon" href="/vue-analysis/icons/apple-touch-icon-152x152.png">
<link rel="mask-icon" href="/vue-analysis/icons/safari-pinned-tab.svg" color="#3eaf7c">
<meta name="description" content="Analysis vue.js deeply">
<meta name="theme-color" content="#3eaf7c">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="msapplication-TileImage" content="/icons/msapplication-icon-144x144.png">
<meta name="msapplication-TileColor" content="#000000">
<link rel="preload" href="/vue-analysis/assets/css/0.styles.1df1aa93.css" as="style"><link rel="preload" href="/vue-analysis/assets/js/app.dc0db797.js" as="script"><link rel="preload" href="/vue-analysis/assets/js/1.116748a5.js" as="script"><link rel="preload" href="/vue-analysis/assets/js/15.888d192c.js" as="script"><link rel="prefetch" href="/vue-analysis/assets/js/2.fc8e0a65.js"><link rel="prefetch" href="/vue-analysis/assets/js/3.fcc611e4.js"><link rel="prefetch" href="/vue-analysis/assets/js/4.6849b7a5.js"><link rel="prefetch" href="/vue-analysis/assets/js/5.b1d7f308.js"><link rel="prefetch" href="/vue-analysis/assets/js/6.8c027019.js"><link rel="prefetch" href="/vue-analysis/assets/js/7.f58945fe.js"><link rel="prefetch" href="/vue-analysis/assets/js/8.d2701782.js"><link rel="prefetch" href="/vue-analysis/assets/js/9.037ca01f.js"><link rel="prefetch" href="/vue-analysis/assets/js/10.af0fff33.js"><link rel="prefetch" href="/vue-analysis/assets/js/11.59f03371.js"><link rel="prefetch" href="/vue-analysis/assets/js/12.9e9b1a70.js"><link rel="prefetch" href="/vue-analysis/assets/js/13.d968b2a6.js"><link rel="prefetch" href="/vue-analysis/assets/js/14.87c5c852.js"><link rel="prefetch" href="/vue-analysis/assets/js/16.973d1149.js"><link rel="prefetch" href="/vue-analysis/assets/js/17.5ee13a1e.js"><link rel="prefetch" href="/vue-analysis/assets/js/18.96228bde.js"><link rel="prefetch" href="/vue-analysis/assets/js/19.5acc8fba.js"><link rel="prefetch" href="/vue-analysis/assets/js/20.8b41793c.js"><link rel="prefetch" href="/vue-analysis/assets/js/21.ac4c6642.js"><link rel="prefetch" href="/vue-analysis/assets/js/22.c08c41a2.js"><link rel="prefetch" href="/vue-analysis/assets/js/23.06639f65.js"><link rel="prefetch" href="/vue-analysis/assets/js/24.47c1e3d4.js"><link rel="prefetch" href="/vue-analysis/assets/js/25.4b97b7e9.js"><link rel="prefetch" href="/vue-analysis/assets/js/26.69498640.js"><link rel="prefetch" href="/vue-analysis/assets/js/27.8fcd6a1a.js"><link rel="prefetch" href="/vue-analysis/assets/js/28.47607775.js"><link rel="prefetch" href="/vue-analysis/assets/js/29.8cc49e12.js"><link rel="prefetch" href="/vue-analysis/assets/js/30.fe9ed92d.js"><link rel="prefetch" href="/vue-analysis/assets/js/31.a5972532.js"><link rel="prefetch" href="/vue-analysis/assets/js/32.2153f0ad.js"><link rel="prefetch" href="/vue-analysis/assets/js/33.d4a2c81a.js"><link rel="prefetch" href="/vue-analysis/assets/js/34.8dace7fb.js"><link rel="prefetch" href="/vue-analysis/assets/js/35.ad412d1c.js"><link rel="prefetch" href="/vue-analysis/assets/js/36.a2e34e00.js"><link rel="prefetch" href="/vue-analysis/assets/js/37.cb604173.js"><link rel="prefetch" href="/vue-analysis/assets/js/38.8984ce41.js"><link rel="prefetch" href="/vue-analysis/assets/js/39.40e01aad.js"><link rel="prefetch" href="/vue-analysis/assets/js/40.83999d11.js"><link rel="prefetch" href="/vue-analysis/assets/js/41.6f4c7220.js"><link rel="prefetch" href="/vue-analysis/assets/js/42.442e8bc5.js"><link rel="prefetch" href="/vue-analysis/assets/js/43.1c57200a.js"><link rel="prefetch" href="/vue-analysis/assets/js/44.8c421ed3.js"><link rel="prefetch" href="/vue-analysis/assets/js/45.fd597b3a.js"><link rel="prefetch" href="/vue-analysis/assets/js/46.cf7f2c4d.js"><link rel="prefetch" href="/vue-analysis/assets/js/47.5e501866.js"><link rel="prefetch" href="/vue-analysis/assets/js/48.4be72693.js"><link rel="prefetch" href="/vue-analysis/assets/js/49.02e368f8.js"><link rel="prefetch" href="/vue-analysis/assets/js/50.2acc3318.js"><link rel="prefetch" href="/vue-analysis/assets/js/51.1b9098e3.js"><link rel="prefetch" href="/vue-analysis/assets/js/52.3fa951d8.js"><link rel="prefetch" href="/vue-analysis/assets/js/53.58b8b965.js"><link rel="prefetch" href="/vue-analysis/assets/js/54.9e1f9c8f.js"><link rel="prefetch" href="/vue-analysis/assets/js/55.78ccca63.js"><link rel="prefetch" href="/vue-analysis/assets/js/56.82b929ea.js">
<link rel="stylesheet" href="/vue-analysis/assets/css/0.styles.1df1aa93.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/vue-analysis/" class="home-link router-link-active"><!----> <span class="site-name">Vue.js 技术揭秘</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/vue-analysis/v2/prepare/" class="nav-link">
2.x 版本
</a></div><div class="nav-item"><a href="/vue-analysis/v3/new/" class="nav-link">
3.x 版本
</a></div><div class="nav-item"><a href="https://coding.imooc.com/class/228.html" target="_blank" rel="noopener noreferrer" class="nav-link external">
2.x 源码配套视频
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div><div class="nav-item"><a href="https://kaiwu.lagou.com/course/courseInfo.htm?courseId=326#/content" target="_blank" rel="noopener noreferrer" class="nav-link external">
3.x 源码解析课程
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div><div class="nav-item"><a href="https://react.iamkasong.com/" target="_blank" rel="noopener noreferrer" class="nav-link external">
React技术揭秘
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div> <a href="https://github.com/ustbhuangyi/vue-analysis" target="_blank" rel="noopener noreferrer" class="repo-link">
GitHub
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/vue-analysis/v2/prepare/" class="nav-link">
2.x 版本
</a></div><div class="nav-item"><a href="/vue-analysis/v3/new/" class="nav-link">
3.x 版本
</a></div><div class="nav-item"><a href="https://coding.imooc.com/class/228.html" target="_blank" rel="noopener noreferrer" class="nav-link external">
2.x 源码配套视频
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div><div class="nav-item"><a href="https://kaiwu.lagou.com/course/courseInfo.htm?courseId=326#/content" target="_blank" rel="noopener noreferrer" class="nav-link external">
3.x 源码解析课程
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div><div class="nav-item"><a href="https://react.iamkasong.com/" target="_blank" rel="noopener noreferrer" class="nav-link external">
React技术揭秘
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div> <a href="https://github.com/ustbhuangyi/vue-analysis" target="_blank" rel="noopener noreferrer" class="repo-link">
GitHub
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></nav> <ul class="sidebar-links"><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>准备工作</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/prepare/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/prepare/flow.html" class="sidebar-link">认识 Flow</a></li><li><a href="/vue-analysis/v2/prepare/directory.html" class="sidebar-link">Vue.js 源码目录设计</a></li><li><a href="/vue-analysis/v2/prepare/build.html" class="sidebar-link">Vue.js 源码构建</a></li><li><a href="/vue-analysis/v2/prepare/entrance.html" class="sidebar-link">从入口开始</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>数据驱动</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/data-driven/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/data-driven/new-vue.html" class="sidebar-link">new Vue 发生了什么</a></li><li><a href="/vue-analysis/v2/data-driven/mounted.html" class="sidebar-link">Vue 实例挂载的实现</a></li><li><a href="/vue-analysis/v2/data-driven/render.html" class="sidebar-link">render</a></li><li><a href="/vue-analysis/v2/data-driven/virtual-dom.html" class="sidebar-link">Virtual DOM</a></li><li><a href="/vue-analysis/v2/data-driven/create-element.html" class="sidebar-link">createElement</a></li><li><a href="/vue-analysis/v2/data-driven/update.html" class="sidebar-link">update</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>组件化</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/components/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/components/create-component.html" class="sidebar-link">createComponent</a></li><li><a href="/vue-analysis/v2/components/patch.html" class="sidebar-link">patch</a></li><li><a href="/vue-analysis/v2/components/merge-option.html" class="sidebar-link">合并配置</a></li><li><a href="/vue-analysis/v2/components/lifecycle.html" class="sidebar-link">生命周期</a></li><li><a href="/vue-analysis/v2/components/component-register.html" class="sidebar-link">组件注册</a></li><li><a href="/vue-analysis/v2/components/async-component.html" class="sidebar-link">异步组件</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading open"><span>深入响应式原理</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/reactive/" aria-current="page" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/reactive/reactive-object.html" aria-current="page" class="active sidebar-link">响应式对象</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#object-defineproperty" class="sidebar-link">Object.defineProperty</a></li><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#initstate" class="sidebar-link">initState</a></li><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#proxy" class="sidebar-link">proxy</a></li><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#observe" class="sidebar-link">observe</a></li><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#observer" class="sidebar-link">Observer</a></li><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#definereactive" class="sidebar-link">defineReactive</a></li><li class="sidebar-sub-header"><a href="/vue-analysis/v2/reactive/reactive-object.html#总结" class="sidebar-link">总结</a></li></ul></li><li><a href="/vue-analysis/v2/reactive/getters.html" class="sidebar-link">依赖收集</a></li><li><a href="/vue-analysis/v2/reactive/setters.html" class="sidebar-link">派发更新</a></li><li><a href="/vue-analysis/v2/reactive/next-tick.html" class="sidebar-link">nextTick</a></li><li><a href="/vue-analysis/v2/reactive/questions.html" class="sidebar-link">检测变化的注意事项</a></li><li><a href="/vue-analysis/v2/reactive/computed-watcher.html" class="sidebar-link">计算属性 VS 侦听属性</a></li><li><a href="/vue-analysis/v2/reactive/component-update.html" class="sidebar-link">组件更新</a></li><li><a href="/vue-analysis/v2/reactive/props.html" class="sidebar-link">Props (v2.6.11)</a></li><li><a href="/vue-analysis/v2/reactive/summary.html" class="sidebar-link">原理图</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>编译</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/compile/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/compile/entrance.html" class="sidebar-link">编译入口</a></li><li><a href="/vue-analysis/v2/compile/parse.html" class="sidebar-link">parse</a></li><li><a href="/vue-analysis/v2/compile/optimize.html" class="sidebar-link">optimize</a></li><li><a href="/vue-analysis/v2/compile/codegen.html" class="sidebar-link">codegen</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>扩展</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/extend/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/extend/event.html" class="sidebar-link">event</a></li><li><a href="/vue-analysis/v2/extend/v-model.html" class="sidebar-link">v-model</a></li><li><a href="/vue-analysis/v2/extend/slot.html" class="sidebar-link">slot</a></li><li><a href="/vue-analysis/v2/extend/keep-alive.html" class="sidebar-link">keep-alive</a></li><li><a href="/vue-analysis/v2/extend/tansition.html" class="sidebar-link">transition</a></li><li><a href="/vue-analysis/v2/extend/tansition-group.html" class="sidebar-link">transition-group</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>Vue Router</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/vue-router/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/vue-router/install.html" class="sidebar-link">路由注册</a></li><li><a href="/vue-analysis/v2/vue-router/router.html" class="sidebar-link">VueRouter 对象</a></li><li><a href="/vue-analysis/v2/vue-router/matcher.html" class="sidebar-link">matcher</a></li><li><a href="/vue-analysis/v2/vue-router/transition-to.html" class="sidebar-link">路径切换</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>Vuex</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-analysis/v2/vuex/" class="sidebar-link">Introduction</a></li><li><a href="/vue-analysis/v2/vuex/init.html" class="sidebar-link">Vuex 初始化</a></li><li><a href="/vue-analysis/v2/vuex/api.html" class="sidebar-link">API</a></li><li><a href="/vue-analysis/v2/vuex/plugin.html" class="sidebar-link">插件</a></li></ul></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="响应式对象"><a href="#响应式对象" class="header-anchor">#</a> 响应式对象</h1> <p>可能很多小伙伴之前都了解过 Vue.js 实现响应式的核心是利用了 ES5 的 <code>Object.defineProperty</code>,这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因,我们先来对它有个直观的认识。</p> <h2 id="object-defineproperty"><a href="#object-defineproperty" class="header-anchor">#</a> Object.defineProperty</h2> <p><code>Object.defineProperty</code> 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,先来看一下它的语法:</p> <div class="language-js extra-class"><pre class="language-js"><code>Object<span class="token punctuation">.</span><span class="token function">defineProperty</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> prop<span class="token punctuation">,</span> descriptor<span class="token punctuation">)</span>
</code></pre></div><p><code>obj</code> 是要在其上定义属性的对象;<code>prop</code> 是要定义或修改的属性的名称;<code>descriptor</code> 是将被定义或修改的属性描述符。</p> <p>比较核心的是 <code>descriptor</code>,它有很多可选键值,具体的可以去参阅它的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty" target="_blank" rel="noopener noreferrer">文档<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>。这里我们最关心的是 <code>get</code> 和 <code>set</code>,<code>get</code> 是一个给属性提供的 getter 方法,当我们访问了该属性的时候会触发 getter 方法;<code>set</code> 是一个给属性提供的 setter 方法,当我们对该属性做修改的时候会触发 setter 方法。</p> <p>一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象。那么 Vue.js 把哪些对象变成了响应式对象了呢,接下来我们从源码层面分析。</p> <h2 id="initstate"><a href="#initstate" class="header-anchor">#</a> initState</h2> <p>在 Vue 的初始化阶段,<code>_init</code> 方法执行的时候,会执行 <code>initState(vm)</code> 方法,它的定义在 <code>src/core/instance/state.js</code> 中。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">initState</span> <span class="token punctuation">(</span><span class="token parameter">vm<span class="token operator">:</span> Component</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
vm<span class="token punctuation">.</span>_watchers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">const</span> opts <span class="token operator">=</span> vm<span class="token punctuation">.</span>$options
<span class="token keyword">if</span> <span class="token punctuation">(</span>opts<span class="token punctuation">.</span>props<span class="token punctuation">)</span> <span class="token function">initProps</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> opts<span class="token punctuation">.</span>props<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>opts<span class="token punctuation">.</span>methods<span class="token punctuation">)</span> <span class="token function">initMethods</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> opts<span class="token punctuation">.</span>methods<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>opts<span class="token punctuation">.</span>data<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">initData</span><span class="token punctuation">(</span>vm<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">observe</span><span class="token punctuation">(</span>vm<span class="token punctuation">.</span>_data <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token comment">/* asRootData */</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>opts<span class="token punctuation">.</span>computed<span class="token punctuation">)</span> <span class="token function">initComputed</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> opts<span class="token punctuation">.</span>computed<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>opts<span class="token punctuation">.</span>watch <span class="token operator">&&</span> opts<span class="token punctuation">.</span>watch <span class="token operator">!==</span> nativeWatch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">initWatch</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> opts<span class="token punctuation">.</span>watch<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>initState</code> 方法主要是对 <code>props</code>、<code>methods</code>、<code>data</code>、<code>computed</code> 和 <code>watcher</code> 等属性做了初始化操作。这里我们重点分析 <code>props</code> 和 <code>data</code>,对于其它属性的初始化我们之后再详细分析。</p> <ul><li>initProps</li></ul> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">function</span> <span class="token function">initProps</span> <span class="token punctuation">(</span><span class="token parameter">vm<span class="token operator">:</span> Component<span class="token punctuation">,</span> propsOptions<span class="token operator">:</span> Object</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> propsData <span class="token operator">=</span> vm<span class="token punctuation">.</span>$options<span class="token punctuation">.</span>propsData <span class="token operator">||</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token keyword">const</span> props <span class="token operator">=</span> vm<span class="token punctuation">.</span>_props <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token comment">// cache prop keys so that future props updates can iterate using Array</span>
<span class="token comment">// instead of dynamic object key enumeration.</span>
<span class="token keyword">const</span> keys <span class="token operator">=</span> vm<span class="token punctuation">.</span>$options<span class="token punctuation">.</span>_propKeys <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">const</span> isRoot <span class="token operator">=</span> <span class="token operator">!</span>vm<span class="token punctuation">.</span>$parent
<span class="token comment">// root instance props should be converted</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isRoot<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">toggleObserving</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> key <span class="token keyword">in</span> propsOptions<span class="token punctuation">)</span> <span class="token punctuation">{</span>
keys<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">validateProp</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> propsOptions<span class="token punctuation">,</span> propsData<span class="token punctuation">,</span> vm<span class="token punctuation">)</span>
<span class="token comment">/* istanbul ignore else */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> hyphenatedKey <span class="token operator">=</span> <span class="token function">hyphenate</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isReservedAttribute</span><span class="token punctuation">(</span>hyphenatedKey<span class="token punctuation">)</span> <span class="token operator">||</span>
config<span class="token punctuation">.</span><span class="token function">isReservedAttr</span><span class="token punctuation">(</span>hyphenatedKey<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">warn</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">"</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>hyphenatedKey<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" is a reserved attribute and cannot be used as component prop.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
vm
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token function">defineReactive</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>vm<span class="token punctuation">.</span>$parent <span class="token operator">&&</span> <span class="token operator">!</span>isUpdatingChildComponent<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">warn</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Avoid mutating a prop directly since the value will be </span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">overwritten whenever the parent component re-renders. </span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Instead, use a data or computed property based on the prop's </span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">value. Prop being mutated: "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
vm
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">defineReactive</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// static props are already proxied on the component's prototype</span>
<span class="token comment">// during Vue.extend(). We only need to proxy props defined at</span>
<span class="token comment">// instantiation here.</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>key <span class="token keyword">in</span> vm<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">proxy</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_props</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> key<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token function">toggleObserving</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>props</code> 的初始化主要过程,就是遍历定义的 <code>props</code> 配置。遍历的过程主要做两件事情:一个是调用 <code>defineReactive</code> 方法把每个 <code>prop</code> 对应的值变成响应式,可以通过 <code>vm._props.xxx</code> 访问到定义 <code>props</code> 中对应的属性。对于 <code>defineReactive</code> 方法,我们稍后会介绍;另一个是通过 <code>proxy</code> 把 <code>vm._props.xxx</code> 的访问代理到 <code>vm.xxx</code> 上,我们稍后也会介绍。</p> <ul><li>initData</li></ul> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">function</span> <span class="token function">initData</span> <span class="token punctuation">(</span><span class="token parameter">vm<span class="token operator">:</span> Component</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> data <span class="token operator">=</span> vm<span class="token punctuation">.</span>$options<span class="token punctuation">.</span>data
data <span class="token operator">=</span> vm<span class="token punctuation">.</span>_data <span class="token operator">=</span> <span class="token keyword">typeof</span> data <span class="token operator">===</span> <span class="token string">'function'</span>
<span class="token operator">?</span> <span class="token function">getData</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> vm<span class="token punctuation">)</span>
<span class="token operator">:</span> data <span class="token operator">||</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isPlainObject</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
data <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span> <span class="token operator">&&</span> <span class="token function">warn</span><span class="token punctuation">(</span>
<span class="token string">'data functions should return an object:\n'</span> <span class="token operator">+</span>
<span class="token string">'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function'</span><span class="token punctuation">,</span>
vm
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// proxy data on instance</span>
<span class="token keyword">const</span> keys <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
<span class="token keyword">const</span> props <span class="token operator">=</span> vm<span class="token punctuation">.</span>$options<span class="token punctuation">.</span>props
<span class="token keyword">const</span> methods <span class="token operator">=</span> vm<span class="token punctuation">.</span>$options<span class="token punctuation">.</span>methods
<span class="token keyword">let</span> i <span class="token operator">=</span> keys<span class="token punctuation">.</span>length
<span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> key <span class="token operator">=</span> keys<span class="token punctuation">[</span>i<span class="token punctuation">]</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>methods <span class="token operator">&&</span> <span class="token function">hasOwn</span><span class="token punctuation">(</span>methods<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">warn</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Method "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" has already been defined as a data property.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
vm
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>props <span class="token operator">&&</span> <span class="token function">hasOwn</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span> <span class="token operator">&&</span> <span class="token function">warn</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">The data property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" is already declared as a prop. </span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span>
<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Use prop default value instead.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
vm
<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isReserved</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">proxy</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_data</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> key<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// observe data</span>
<span class="token function">observe</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token comment">/* asRootData */</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>data</code> 的初始化主要过程也是做两件事,一个是对定义 <code>data</code> 函数返回对象的遍历,通过 <code>proxy</code> 把每一个值 <code>vm._data.xxx</code> 都代理到 <code>vm.xxx</code> 上;另一个是调用 <code>observe</code> 方法观测整个 <code>data</code> 的变化,把 <code>data</code> 也变成响应式,可以通过 <code>vm._data.xxx</code> 访问到定义 <code>data</code> 返回函数中对应的属性,<code>observe</code> 我们稍后会介绍。</p> <p>可以看到,无论是 <code>props</code> 或是 <code>data</code> 的初始化都是把它们变成响应式对象,这个过程我们接触到几个函数,接下来我们来详细分析它们。</p> <h2 id="proxy"><a href="#proxy" class="header-anchor">#</a> proxy</h2> <p>首先介绍一下代理,代理的作用是把 <code>props</code> 和 <code>data</code> 上的属性代理到 <code>vm</code> 实例上,这也就是为什么比如我们定义了如下 props,却可以通过 vm 实例访问到它。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">let</span> comP <span class="token operator">=</span> <span class="token punctuation">{</span>
props<span class="token operator">:</span> <span class="token punctuation">{</span>
msg<span class="token operator">:</span> <span class="token string">'hello'</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
methods<span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token function">say</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>msg<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>我们可以在 <code>say</code> 函数中通过 <code>this.msg</code> 访问到我们定义在 <code>props</code> 中的 <code>msg</code>,这个过程发生在 <code>proxy</code> 阶段:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> sharedPropertyDefinition <span class="token operator">=</span> <span class="token punctuation">{</span>
enumerable<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
configurable<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
get<span class="token operator">:</span> noop<span class="token punctuation">,</span>
set<span class="token operator">:</span> noop
<span class="token punctuation">}</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">proxy</span> <span class="token punctuation">(</span><span class="token parameter">target<span class="token operator">:</span> Object<span class="token punctuation">,</span> sourceKey<span class="token operator">:</span> string<span class="token punctuation">,</span> key<span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
sharedPropertyDefinition<span class="token punctuation">.</span><span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">proxyGetter</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">[</span>sourceKey<span class="token punctuation">]</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
sharedPropertyDefinition<span class="token punctuation">.</span><span class="token function-variable function">set</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">proxySetter</span> <span class="token punctuation">(</span><span class="token parameter">val</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">[</span>sourceKey<span class="token punctuation">]</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> val
<span class="token punctuation">}</span>
Object<span class="token punctuation">.</span><span class="token function">defineProperty</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> key<span class="token punctuation">,</span> sharedPropertyDefinition<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>proxy</code> 方法的实现很简单,通过 <code>Object.defineProperty</code> 把 <code>target[sourceKey][key]</code> 的读写变成了对 <code>target[key]</code> 的读写。所以对于 <code>props</code> 而言,对 <code>vm._props.xxx</code> 的读写变成了 <code>vm.xxx</code> 的读写,而对于 <code>vm._props.xxx</code> 我们可以访问到定义在 <code>props</code> 中的属性,所以我们就可以通过 <code>vm.xxx</code> 访问到定义在 <code>props</code> 中的 <code>xxx</code> 属性了。同理,对于 <code>data</code> 而言,对 <code>vm._data.xxxx</code> 的读写变成了对 <code>vm.xxxx</code> 的读写,而对于 <code>vm._data.xxxx</code> 我们可以访问到定义在 <code>data</code> 函数返回对象中的属性,所以我们就可以通过 <code>vm.xxxx</code> 访问到定义在 <code>data</code> 函数返回对象中的 <code>xxxx</code> 属性了。</p> <h2 id="observe"><a href="#observe" class="header-anchor">#</a> <code>observe</code></h2> <p><code>observe</code> 的功能就是用来监测数据的变化,它的定义在 <code>src/core/observer/index.js</code> 中:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">observe</span> <span class="token punctuation">(</span><span class="token parameter">value<span class="token operator">:</span> any<span class="token punctuation">,</span> asRootData<span class="token operator">:</span> <span class="token operator">?</span>boolean</span><span class="token punctuation">)</span><span class="token operator">:</span> Observer <span class="token operator">|</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isObject</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">||</span> value <span class="token keyword">instanceof</span> <span class="token class-name">VNode</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token keyword">let</span> ob<span class="token operator">:</span> Observer <span class="token operator">|</span> <span class="token keyword">void</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">hasOwn</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token string">'__ob__'</span><span class="token punctuation">)</span> <span class="token operator">&&</span> value<span class="token punctuation">.</span>__ob__ <span class="token keyword">instanceof</span> <span class="token class-name">Observer</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
ob <span class="token operator">=</span> value<span class="token punctuation">.</span>__ob__
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>
shouldObserve <span class="token operator">&&</span>
<span class="token operator">!</span><span class="token function">isServerRendering</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&&</span>
<span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">isPlainObject</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&&</span>
Object<span class="token punctuation">.</span><span class="token function">isExtensible</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">&&</span>
<span class="token operator">!</span>value<span class="token punctuation">.</span>_isVue
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ob <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Observer</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>asRootData <span class="token operator">&&</span> ob<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ob<span class="token punctuation">.</span>vmCount<span class="token operator">++</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> ob
<span class="token punctuation">}</span>
</code></pre></div><p><code>observe</code> 方法的作用就是给非 VNode 的对象类型数据添加一个 <code>Observer</code>,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 <code>Observer</code> 对象实例。接下来我们来看一下 <code>Observer</code> 的作用。</p> <h2 id="observer"><a href="#observer" class="header-anchor">#</a> Observer</h2> <p><code>Observer</code> 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Observer</span> <span class="token punctuation">{</span>
value<span class="token operator">:</span> any<span class="token punctuation">;</span>
dep<span class="token operator">:</span> Dep<span class="token punctuation">;</span>
vmCount<span class="token operator">:</span> number<span class="token punctuation">;</span> <span class="token comment">// number of vms that has this object as root $data</span>
<span class="token function">constructor</span> <span class="token punctuation">(</span><span class="token parameter">value<span class="token operator">:</span> any</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> value
<span class="token keyword">this</span><span class="token punctuation">.</span>dep <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Dep</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>vmCount <span class="token operator">=</span> <span class="token number">0</span>
<span class="token function">def</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token string">'__ob__'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> augment <span class="token operator">=</span> hasProto
<span class="token operator">?</span> protoAugment
<span class="token operator">:</span> copyAugment
<span class="token function">augment</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> arrayMethods<span class="token punctuation">,</span> arrayKeys<span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">observeArray</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">walk</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/</span>
<span class="token function">walk</span> <span class="token punctuation">(</span><span class="token parameter">obj<span class="token operator">:</span> Object</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> keys <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> keys<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">defineReactive</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> keys<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Observe a list of Array items.
*/</span>
<span class="token function">observeArray</span> <span class="token punctuation">(</span><span class="token parameter">items<span class="token operator">:</span> Array<span class="token operator"><</span>any<span class="token operator">></span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> l <span class="token operator">=</span> items<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i <span class="token operator"><</span> l<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">observe</span><span class="token punctuation">(</span>items<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>Observer</code> 的构造函数逻辑很简单,首先实例化 <code>Dep</code> 对象,这块稍后会介绍,接着通过执行 <code>def</code> 函数把自身实例添加到数据对象 <code>value</code> 的 <code>__ob__</code> 属性上,<code>def</code> 的定义在 <code>src/core/util/lang.js</code> 中:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">/**
* Define a property.
*/</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">def</span> <span class="token punctuation">(</span><span class="token parameter">obj<span class="token operator">:</span> Object<span class="token punctuation">,</span> key<span class="token operator">:</span> string<span class="token punctuation">,</span> val<span class="token operator">:</span> any<span class="token punctuation">,</span> enumerable<span class="token operator">?</span><span class="token operator">:</span> boolean</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
Object<span class="token punctuation">.</span><span class="token function">defineProperty</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> key<span class="token punctuation">,</span> <span class="token punctuation">{</span>
value<span class="token operator">:</span> val<span class="token punctuation">,</span>
enumerable<span class="token operator">:</span> <span class="token operator">!</span><span class="token operator">!</span>enumerable<span class="token punctuation">,</span>
writable<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
configurable<span class="token operator">:</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>def</code> 函数是一个非常简单的<code>Object.defineProperty</code> 的封装,这就是为什么我在开发中输出 <code>data</code> 上对象类型的数据,会发现该对象多了一个 <code>__ob__</code> 的属性。</p> <p>回到 <code>Observer</code> 的构造函数,接下来会对 <code>value</code> 做判断,对于数组会调用 <code>observeArray</code> 方法,否则对纯对象调用 <code>walk</code> 方法。可以看到 <code>observeArray</code> 是遍历数组再次调用 <code>observe</code> 方法,而 <code>walk</code> 方法是遍历对象的 key 调用 <code>defineReactive</code> 方法,那么我们来看一下这个方法是做什么的。</p> <h2 id="definereactive"><a href="#definereactive" class="header-anchor">#</a> defineReactive</h2> <p><code>defineReactive</code> 的功能就是定义一个响应式对象,给对象动态添加 getter 和 setter,它的定义在 <code>src/core/observer/index.js</code> 中:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">/**
* Define a reactive property on an Object.
*/</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">defineReactive</span> <span class="token punctuation">(</span>
<span class="token parameter">obj<span class="token operator">:</span> Object<span class="token punctuation">,</span>
key<span class="token operator">:</span> string<span class="token punctuation">,</span>
val<span class="token operator">:</span> any<span class="token punctuation">,</span>
customSetter<span class="token operator">?</span><span class="token operator">:</span> <span class="token operator">?</span>Function<span class="token punctuation">,</span>
shallow<span class="token operator">?</span><span class="token operator">:</span> boolean</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> dep <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Dep</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> property <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">getOwnPropertyDescriptor</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> key<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>property <span class="token operator">&&</span> property<span class="token punctuation">.</span>configurable <span class="token operator">===</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token comment">// cater for pre-defined getter/setters</span>
<span class="token keyword">const</span> getter <span class="token operator">=</span> property <span class="token operator">&&</span> property<span class="token punctuation">.</span>get
<span class="token keyword">const</span> setter <span class="token operator">=</span> property <span class="token operator">&&</span> property<span class="token punctuation">.</span>set
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token operator">!</span>getter <span class="token operator">||</span> setter<span class="token punctuation">)</span> <span class="token operator">&&</span> arguments<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
val <span class="token operator">=</span> obj<span class="token punctuation">[</span>key<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token keyword">let</span> childOb <span class="token operator">=</span> <span class="token operator">!</span>shallow <span class="token operator">&&</span> <span class="token function">observe</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span>
Object<span class="token punctuation">.</span><span class="token function">defineProperty</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> key<span class="token punctuation">,</span> <span class="token punctuation">{</span>
enumerable<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
configurable<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token function-variable function">get</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token function">reactiveGetter</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> getter <span class="token operator">?</span> <span class="token function">getter</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token operator">:</span> val
<span class="token keyword">if</span> <span class="token punctuation">(</span>Dep<span class="token punctuation">.</span>target<span class="token punctuation">)</span> <span class="token punctuation">{</span>
dep<span class="token punctuation">.</span><span class="token function">depend</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>childOb<span class="token punctuation">)</span> <span class="token punctuation">{</span>
childOb<span class="token punctuation">.</span>dep<span class="token punctuation">.</span><span class="token function">depend</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dependArray</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> value
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token function-variable function">set</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token function">reactiveSetter</span> <span class="token punctuation">(</span><span class="token parameter">newVal</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> getter <span class="token operator">?</span> <span class="token function">getter</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token operator">:</span> val
<span class="token comment">/* eslint-disable no-self-compare */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>newVal <span class="token operator">===</span> value <span class="token operator">||</span> <span class="token punctuation">(</span>newVal <span class="token operator">!==</span> newVal <span class="token operator">&&</span> value <span class="token operator">!==</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token comment">/* eslint-enable no-self-compare */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span> <span class="token operator">&&</span> customSetter<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">customSetter</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>setter<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">setter</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> newVal<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
val <span class="token operator">=</span> newVal
<span class="token punctuation">}</span>
childOb <span class="token operator">=</span> <span class="token operator">!</span>shallow <span class="token operator">&&</span> <span class="token function">observe</span><span class="token punctuation">(</span>newVal<span class="token punctuation">)</span>
dep<span class="token punctuation">.</span><span class="token function">notify</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>defineReactive</code> 函数最开始初始化 <code>Dep</code> 对象的实例,接着拿到 <code>obj</code> 的属性描述符,然后对子对象递归调用 <code>observe</code> 方法,这样就保证了无论 <code>obj</code> 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 <code>obj</code> 中一个嵌套较深的属性,也能触发 getter 和 setter。最后利用 <code>Object.defineProperty</code> 去给 <code>obj</code> 的属性 <code>key</code> 添加 getter 和 setter。而关于 getter 和 setter 的具体实现,我们会在之后介绍。</p> <h2 id="总结"><a href="#总结" class="header-anchor">#</a> 总结</h2> <p>这一节我们介绍了响应式对象,核心就是利用 <code>Object.defineProperty</code> 给数据添加了 getter 和 setter,目的就是为了在我们访问数据以及写数据的时候能自动执行一些逻辑:getter 做的事情是依赖收集,setter 做的事情是派发更新,那么在接下来的章节我们会重点对这两个过程分析。</p></div> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/ustbhuangyi/vue-analysis/edit/master/docs/v2/reactive/reactive-object.md" target="_blank" rel="noopener noreferrer">在 GitHub 上编辑此页</a> <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></div> <div class="last-updated"><span class="prefix">上次更新:</span> <span class="time">10/23/2019, 12:56:55 PM</span></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
←
<a href="/vue-analysis/v2/reactive/" class="prev router-link-active">
Introduction
</a></span> <span class="next"><a href="/vue-analysis/v2/reactive/getters.html">
依赖收集
</a>
→
</span></p></div> </main></div><div class="global-ui"></div></div>
<script src="/vue-analysis/assets/js/app.dc0db797.js" defer></script><script src="/vue-analysis/assets/js/1.116748a5.js" defer></script><script src="/vue-analysis/assets/js/15.888d192c.js" defer></script>
</body>
</html>