Vue.js 源碼分析 - Vue 物件(Object)響應式原理

在網路上搜尋有關 Vue 響應式源碼文章時,真的是看不懂,所以研究了好幾天,希望自己理解的過程是對的。

響應式概念

觀察 Vue 的實例會發現 Vue 響應式的數據資料會有幾個特點:

  • 值會是 ...

  • 有 getter 跟 setter 。

Vue 響應式裡的重點 Object.defineProperty(obj, prop, descriptor)

Object.defineProperty()

1
Object.defineProperty(obj, prop, descriptor)

簡而言之就是透過 Object.defineProperty() 去取得(getter)或是更改(setter)指定的屬性值。

If a descriptor has both [value or writable] and [get or set] keys, an exception is thrown.

但必須注意 [value or writable] 跟 [get or set] 是不能同時使用的。

數據資料響應式分析圖式

接下來以這個範例來解釋 Vue 是如何將 data 內的資料變成響應式的。

1
2
3
4
5
6
7
8
9
10
11
data: {
text: "響應式",
person: {
name: "Mark",
age: 18,
likes: {
fruit: "apple",
hobby: "reading"
}
}
}

先上幾個 Vue 裡如何拆解物件變成響應式的步驟及循環圖。

觀察 $data

接下來從 console.log(this) 中找到 $data 來觀察一下資料內有甚麼。

會發現每個物件中都有相似的幾個屬性,像是物件屬性值、 gettersetter__ob__

接下來打開 __ob__

展開 __ob__ 後,會發現 __ob__ 是由一個叫 Observer 的類所建構的。

之前的圖示所標記出來的幾個重點(gettersetter__ob__)都可以從 $data 裡找到相關的資料。

實際觀察 Vue 響應式源碼

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
// vue/src/core/observer/index.js
import Dep from './dep'
import VNode from '../vdom/vnode'
import {
def,
warn,
hasOwn,
hasProto,
isObject,
isPlainObject,
isPrimitive,
isUndef,
isValidArrayIndex,
isServerRendering
} from '../util/index'

/**
* 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.
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 判斷 value 是不是物件 ,不是物件就返回
if (!isObject(value) || value instanceof VNode) {
return
}
// 是物件的話
let ob: Observer | void

// 判斷 value 裡面有沒有屬性 __ob__
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 有 __ob__ 的話
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 沒有 __ob__ 的話,透過建構 Observer 類得到值
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}


/*
* 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.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data

constructor (value: any) {
// 以 Observer 類建構的實例,會擁有 value、dep、vmCount 等屬性
this.value = value
this.dep = new Dep()
this.vmCount = 0

// 執行 def() → 也是利用 object.defineProperty(),將 value 內的屬性 __ob__ 設定為 this(而 this 是指以 Observer 類建構的實例)
def(value, '__ob__', this)

// 判斷 value 是否為陣列
if (Array.isArray(value)) {
// 是陣列
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// 不是陣列,就執行實例內的 walk 函數
this.walk(value)
}
}

/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
// 將帶入的物件透過Object.keys()取得物件中所有的 key,並且將取得的 key 變成陣列
const keys = Object.keys(obj)

// 循環剛剛取得的 keys 的陣列
for (let i = 0; i < keys.length; i++) {
// 將每個循環到的 key 帶入 defineReactive()
defineReactive(obj, keys[i])
}
}

/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}

/**
* Define a reactive property on an Object.
*/
// 讓帶入的值可以變成響應式
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()

const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set

// 透過 obj[key] 的方式取得所代入屬性的值
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}

// childOb 的目的就是將被代入屬性裡的值(val)丟入observe()中進行檢查是否為物件,形成循環(observe() → new Observer() → defineReactive())
let childOb = !shallow && observe(val)

//將被代入的屬性變成響應式(擁有getter跟setter)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果該屬性的值被更改後,再透過 observe(newVal) 檢查新值是否為物件(進入循環 observe() → new Observer() → defineReactive())
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
1
2
3
4
5
6
7
8
9
// vue/src/core/util/lang.js
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}

參考資料

探索 Vue.js 响应式原理

浅谈Vue响应式原理

Vue 的数据响应式原理