前言
通過珠峯課程的學習來理解 Vue 源碼的響應式原理 。
響應式原理
1.核心點: Object.defineProperty
2.默認 Vue 在初始化數據時,會給 data 中的屬性使用 Object.defineProperty 重新定義所有屬性,當頁面獲取到對應屬性時。會進行依賴收集(收集當前組件的watcher) 如果屬性發生變化會通知相關依賴進行更新操作。
什麼叫依賴收集 ?
通過Object.defineProperty
在重新定義data屬性的時候,進行攔截,再進行實際渲染 ; 那實際渲染之前的一系列處理邏輯就是依賴收集上邊有說,會在依賴收集的時候爲每一個屬性創建一個watcher,如果屬性發生變化,則通知對應的 watcher 更新視圖
。
來看看源碼
1,首先從構造函數初始化看起 ,src/core/instance/index.js
由於我們主要分享響應式數據原理,也就是初始化Vue數據是如何渲染並建立監聽的,主要看 stateMixin 模塊
2,進入stateMixin
模塊 , 我們直接看向 initData
函數
function initData (vm: Component) { // 初始化data
let data = vm.$options.data // 獲取到用戶傳入的data數據
data = vm._data = typeof data === 'function' // 模板語法與標準語法區分獲取data數據
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key) // es6 proxy 代理
}
}
/*
中間我就跳過了,看意思是非生產環境下,對 props , methods 的一些定義,聲明做的判斷,不允許重複聲明
另外就是添加了 proxy , es6 新增代理屬性 , 包含所有 Object.defineProperty 的功能, 重要的一點是解決 了不能對,數組,對象監聽的問題等。
*/
// observe data
observe(data, true /* asRootData */) // 重點在這兒
}
重點 : observe(data, true /* asRootData */)
接下來我們看向 observer 方法
src/core/observer/index.js
113 行 ,其實 obsrver 方法基本沒有做多少事兒 , 就是對 data 類型做了判斷,然後判斷data屬性是否已經被監聽,如果監聽了直接賦值,沒有監聽則創建監聽 new Observer()
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
/*
不是對象不進行觀測,如:不管是模板語法還是標準語法data均是一個對象
data () { 模板語法返回一個對象
return {}
}
new Vue ({ 標準語法 data 也是一個對象
data:{}
})
*/
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 已經被監聽的,不會重複監聽
ob = value.__b__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && // 是否可擴展
!value._isVue
) {
ob = new Observer(value) //重點,重點,重點, 回調回去,觀測對象類型
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
寫了三個重點,那麼我們來看一下 Observer 類到底做了那些事兒
看代碼
src/core/observer/index.js
37 行
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) { // 構造函數
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
/*
觀測呢分爲兩種,一種是數組,一種是對象
數組:是通過改寫數組原型方法,包含 push,shift,unshift,pop ,splice, sort 等,也就是說 vue 監聽數組變化是通過改寫原型方法 + 遞歸遍歷實現的數據觀測 。 後續我們詳解
*/
if (Array.isArray(value)) { // 是數組
if (hasProto) {
protoAugment(value, arrayMethods) // 改寫數組原型方法
} else {
copyAugment(value, arrayMethods, arrayKeys) // 複製數組已有方法
}
this.observeArray(value) // 深度觀察數組中的每一項 , observeArray 往下看
} else {
this.walk(value) // 重新定義對象類型數據 walk 往下看
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) { // 是對象則走進這個方法,上邊有判斷
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) { // 遍歷對象
defineReactive(obj, keys[i]); // 定義響應式數據,這裏可以看到 defineReactive 方法
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) { // 是數組則遍歷數組,走進observer方法查看是否被監聽
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 觀測數組中的每一項
}
}
}
通過上邊的代碼,我們看到了Observer 類
主要做了一件事,就是區分數組和對象,並對數組和對象遍歷,創建觀察者 watcher
=> defineReactive 方法
總之都會走到 defineReactive
方法 接下來我們看代碼
src/core/observer/index.js
148 行 defineReactive 響應式數據綁定關鍵方法
也就是我們常常說到的 Object.defineProperty() 應用的地方 ;所有的初始化數據都會走到這裏 。
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
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val) // 是數組則遞歸觀測
Object.defineProperty(obj, key, { // 重點
enumerable: true,
configurable: true,
get: function reactiveGetter () { // 數據的取值
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // 收集依賴 watcher , 也就是創建觀察者
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
}
childOb = !shallow && observe(newVal)
dep.notify() // 觸發數據對應的依賴進行更新 , 重點,重點,往下看
}
})
}
Dep 所有觀察者的集合,也就是 wathcer 的集合,在創建觀測的時候爲每一個data屬性創建了watcher
觀察者(Object.defineProperty方法的 get 裏邊
),那麼觸發數據更新的 set
方法會調用 dep.notify()
,看代碼
src/core/observer/dep.js
13行,Dep 類可以看見有構造函數,添加watcher ,刪除watcher 等方法,那麼我看來看更新方法notify()
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () { // 通知存儲的依賴更新
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 依賴中對應修改屬性的update方法
}
}
}
update ()
方法在 src/core/observer/wachter.js
166行 ,其實這裏是定義 wachter
觀察者類,裏邊有各種操作 wachter
觀察者的方法,如:增加,修改,清除等 。
總結
好了,分析到這裏,其實就已經很明瞭,針對數據響應式原理,總體的過程就是: