響應(yīng)式
響應(yīng)式處理的入口
- src\core\instance\init.js
- initState(vm) vm 狀態(tài)的初始化
- 初始化了 _data溃蔫、_props健提、methods 等
- src\core\instance\state.js
// 數(shù)據(jù)的初始化
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
- initData(vm) vm 數(shù)據(jù)的初始化
function initData (vm: Component) {
let data = vm.$options.data
// 初始化 _data私痹,組件中 data 是函數(shù)痪伦,調(diào)用函數(shù)返回結(jié)果
// 否則直接返回 data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
……
// proxy data on instance
// 獲取 data 中的所有屬性
const keys = Object.keys(data)
// 獲取 props / methods
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 判斷 data 上的成員是否和 props/methods 重名
……
// observe data
// 數(shù)據(jù)的響應(yīng)式處理
observe(data, true /* asRootData */)
}
- src\core\observer\index.js
- observe(value, asRootData)
- 負責(zé)為每一個 Object 類型的 value 創(chuàng)建一個 observer 實例
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 判斷 value 是否是對象
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 如果 value 有 __ob__(observer對象) 屬性 結(jié)束
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue ) {
// 創(chuàng)建一個 Observer 對象
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Observer
- src\core\observer\index.js
- 對對象做響應(yīng)化處理
- 對數(shù)組做響應(yīng)化處理
export class Observer {
// 觀測對象
value: any;
// 依賴對象
dep: Dep;
// 實例計數(shù)器
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
// 初始化實例的 vmCount 為0
this.vmCount = 0
// 將實例掛載到觀察對象的 __ob__ 屬性
def(value, '__ob__', this)
// 數(shù)組的響應(yīng)式處理
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 為數(shù)組中的每一個對象創(chuàng)建一個 observer 實例
this.observeArray(value)
} else {
// 遍歷對象中的每一個屬性,轉(zhuǎn)換成 setter/getter
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) {
// 獲取觀察對象的每一個屬性
const keys = Object.keys(obj)
// 遍歷每一個屬性辉哥,設(shè)置為響應(yīng)式數(shù)據(jù)
for (let i = 0; i < keys.length; i++) {
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])
}
}
}
- walk(obj)
遍歷 obj 的所有屬性,為每一個屬性調(diào)用 defineReactive() 方法饲齐,設(shè)置 getter/setter
defineReactive()
- src\core\observer\index.js
- defineReactive(obj, key, val, customSetter, shallow)
- 為一個對象定義一個響應(yīng)式的屬性捂人,每一個屬性對應(yīng)一個 dep 對象
- 如果該屬性的值是對象滥搭,繼續(xù)調(diào)用 observe
- 如果給屬性賦新值,繼續(xù)調(diào)用 observe
- 如果數(shù)據(jù)更新發(fā)送通知
對象響應(yīng)式處理
// 為一個對象定義一個響應(yīng)式的屬性
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 創(chuàng)建依賴對象實例
const dep = new Dep()
// 獲取 obj 的屬性描述符對象
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 提供預(yù)定義的存取器函數(shù)
// 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]
}
// 判斷是否遞歸觀察子對象闽坡,并將子對象屬性都轉(zhuǎn)換成 getter/setter疾嗅,返回子觀察對象
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 如果預(yù)定義的 getter 存在則 value 等于getter 調(diào)用的返回值
// 否則直接賦予屬性值
const value = getter ? getter.call(obj) : val
// 如果存在當(dāng)前依賴目標冕象,即 watcher 對象交惯,則建立依賴
if (Dep.target) {
dep.depend()
// 如果子觀察目標存在,建立子對象的依賴關(guān)系
if (childOb) {
childOb.dep.depend()
// 如果屬性是數(shù)組意荤,則特殊處理收集數(shù)組對象依賴
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 返回屬性值
return value
},
set: function reactiveSetter (newVal) {
// 如果預(yù)定義的 getter 存在則 value 等于getter 調(diào)用的返回值
// 否則直接賦予屬性值
const value = getter ? getter.call(obj) : val
// 如果新值等于舊值或者新值舊值為NaN則不執(zhí)行
/* 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()
}
// 如果沒有 setter 直接返回
// #7981: for accessor properties without setter
if (getter && !setter) return
// 如果預(yù)定義setter存在則調(diào)用玖像,否則直接更新新值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果新值是對象齐饮,觀察子對象并返回 子的 observer 對象
childOb = !shallow && observe(newVal)
// 派發(fā)更新(發(fā)布更改通知)
dep.notify()
}
})
}
數(shù)組的響應(yīng)式處理
- Observer 的構(gòu)造函數(shù)
export class Observer {
// 觀測對象
value: any;
// 依賴對象
dep: Dep;
// 實例計數(shù)器
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
// 初始化實例的 vmCount 為0
this.vmCount = 0
// 將實例掛載到觀察對象的 __ob__ 屬性
def(value, '__ob__', this)
// 數(shù)組的響應(yīng)式處理
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 為數(shù)組中的每一個對象創(chuàng)建一個 observer 實例
this.observeArray(value)
} else {
// 遍歷對象中的每一個屬性祖驱,轉(zhuǎn)換成 setter/getter
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) {
// 獲取觀察對象的每一個屬性
const keys = Object.keys(obj)
// 遍歷每一個屬性捺僻,設(shè)置為響應(yīng)式數(shù)據(jù)
for (let i = 0; i < keys.length; i++) {
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])
}
}
}
// helpers
/**
* Augment a target Object or Array by intercepting
* the prototype chain using __proto__
*/
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
/**
* Augment a target Object or Array by defining
* hidden properties.
*/
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
- 處理數(shù)組修改數(shù)據(jù)的方法
- src\core\observer\array.js
const arrayProto = Array.prototype
// 使用數(shù)組的原型創(chuàng)建一個新的對象
export const arrayMethods = Object.create(arrayProto)
// 修改數(shù)組元素的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 保存數(shù)組原方法
const original = arrayProto[method]
// 調(diào)用 Object.defineProperty() 重新定義修改數(shù)組的方法
def(arrayMethods, method, function mutator (...args) {
// 執(zhí)行數(shù)組的原始方法
const result = original.apply(this, args)
// 獲取數(shù)組對象的 ob 對象
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 對插入的新元素,重新遍歷數(shù)組元素設(shè)置為響應(yīng)式數(shù)據(jù)
if (inserted) ob.observeArray(inserted)
// notify change
// 調(diào)用了修改數(shù)組的方法拔稳,調(diào)用數(shù)組的ob對象發(fā)送通知
ob.dep.notify()
return result
})
})
Dep 類
- src\core\observer\dep.js
- 依賴對象
- 記錄 watcher 對象
- depend() -- watcher 記錄對應(yīng)的 dep
- 發(fā)布通知
- 在 defineReactive() 的 getter 中創(chuàng)建 dep 對象锹雏,并判斷 Dep.target 是否有值, 調(diào)用 dep.depend()
- dep.depend() 內(nèi)部調(diào)用 Dep.target.addDep(this)礁遵,也就是 watcher 的 addDep() 方 法,它內(nèi)部最 調(diào)用 dep.addSub(this)铲球,把 watcher 對象晰赞,添加到 dep.subs.push(watcher) 中掖鱼,也 就是把訂閱者 添加到 dep 的 subs 數(shù)組中,當(dāng)數(shù)據(jù)變化的時候調(diào)用 watcher 對象的 update() 方法
- 調(diào)用 mountComponent() 方法的時 候芍瑞,創(chuàng)建了 渲染 watcher 對象褐墅,執(zhí)行 watcher 中的 get() 方法。設(shè)置 Dep.target
- get() 方法內(nèi)部調(diào)用 pushTarget(this)竟贯,把當(dāng)前 Dep.target = watcher屑那,同時把當(dāng)前 watcher 入棧艘款, 因為有父子組件嵌套的時候先把父組件對應(yīng)的 watcher 入棧,再去處理子組件的 watcher蜘欲,子 組件的處理完畢 后晌柬,再把父組件對應(yīng)的 watcher 出棧空繁,繼續(xù)操作
- Dep.target 用來存放目前正在使用的watcher。全局唯一闷祥,并且一次也只能有一個 watcher 被使用
// dep 是個可觀察對象傲诵,可以有多個指令訂閱它
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
// 靜態(tài)屬性拴竹,watcher 對象
static target: ?Watcher;
// dep 實例 Id
id: number;
// dep 實例對應(yīng)的 watcher 對象/訂閱者數(shù)組
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 添加新的訂閱者 watcher 對象
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除訂閱者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 將觀察對象和 watcher 建立依賴
depend () {
if (Dep.target) {
// 如果 target 存在,把 dep 對象添加到 watcher 的依賴中
Dep.target.addDep(this)
}
}
// 發(fā)布通知
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)
}
// 調(diào)用每個訂閱者的update方法實現(xiàn)更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// Dep.target 用來存放目前正在使用的watcher
// 全局唯一座泳,并且一次也只能有一個watcher被使用
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []
// 入棧并將當(dāng)前 watcher 賦值給 Dep.target
// 父子組件嵌套的時候先把父組件對應(yīng)的 watcher 入棧挑势,
// 再去處理子組件的 watcher啦鸣,子組件的處理完畢后诫给,再把父組件對應(yīng)的 watcher 出棧,繼續(xù)操作
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
// 出棧操作
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Watcher 類
- Watcher 分為三種凫碌,Computed Watcher吃型、用戶 Watcher (偵聽器)勤晚、渲染 Watcher
- 渲染 Watcher 的創(chuàng)建時機
- /src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
- 渲染 wacher 創(chuàng)建的位置 lifecycle.js 的 mountComponent 函數(shù)中
- Wacher 的構(gòu)造函數(shù)初始化鸟蜡,處理 expOrFn (渲染 watcher 和偵聽器處理不同)
- 調(diào)用 this.get() 挺邀,它里面調(diào)用 pushTarget() 然后 this.getter.call(vm, vm) (對于渲染 wacher 調(diào) 用 updateComponent)跳座,如果是用戶 wacher 會獲取屬性的值(觸發(fā)get操作)
- 當(dāng)數(shù)據(jù)更新的時候疲眷,dep 中調(diào)用 notify() 方法您朽,notify() 中調(diào)用 wacher 的 * update() 方法
- update() 中調(diào)用 queueWatcher()
- queueWatcher() 是一個核心方法,去除重復(fù)操作几颜,調(diào)用flushSchedulerQueue() 刷新隊列并執(zhí)行watcher
- flushSchedulerQueue() 中對 wacher 排序蛋哭,遍歷所有 wacher 涮母,如果有 before,觸發(fā)生命周期的鉤子函數(shù) beforeUpdate棺妓,執(zhí)行 wacher.run()炮赦,它內(nèi)部調(diào)用 this.get()吠勘,然后調(diào)用 this.cb() (渲染wacher 的 cb 是 noop)
- 整個流程結(jié)束
set 、delete植锉、watch
-
vm.$set
向響應(yīng)式對象中添加一個屬性峭拘,并確保這個新屬性同樣是響應(yīng)式的鸡挠,且觸發(fā)視圖更新。它必須用于向響應(yīng)式對象上添加新屬性彭沼,因為 Vue 無法探測普通的新增屬性 (比如this.myObject.newProperty = 'hi')
- 使用方式:vm.$set(obj, 'foo', 'test')
-
vm.$delete
刪除對象的屬性备埃。如果對象是響應(yīng)式的褐奴,確保刪除能觸發(fā)更新視圖敦冬。這個方法主要用于避開 Vue不能檢測到屬性被刪除的限制望众,但是你應(yīng)該很少會使用它烂翰。
- 使用方式:vm.$delete(vm.obj, 'msg')
-
vm.$watch
觀察 Vue 實例變化的一個表達式或計算屬性函數(shù)蚤氏。回調(diào)函數(shù)得到的參數(shù)為新值和舊值佳恬。表達式只接受監(jiān)督的鍵路徑毁葱。對于更復(fù)雜的表達式贰剥,用一個函數(shù)取代。
// expOrFn 是表達式
vm.$watch('msg', function (newVal, oldVal) {
console.log(newVal, oldVal)
})
vm.$watch('user.firstName', function (newVal, oldVal) {
console.log(newVal)
})
// expOrFn 是函數(shù)
vm.$watch(function () {
return this.a + this.b
}, function (newVal, oldVal) {
console.log(newVal)
})
// deep 是 true前痘,消耗性能
vm.$watch('user', function (newVal, oldVal) {
// 此時的 newVal 是 user 對象
console.log(newVal === vm.user)
}, {
deep: true
})
// immediate 是 true
vm.$watch('msg', function (newVal, oldVal) {
console.log(newVal)
}, {
immediate: true
})
三種類型的 Watcher 對象
沒有靜態(tài)方法芹缔,因為$watch
方法中要使用 Vue 的實例
Watcher 分三種:計算屬性 Watcher瓶盛、用戶 Watcher (偵聽器)惩猫、渲染 Watcher
創(chuàng)建順序:計算屬性 Watcher、用戶 Watcher (偵聽器)吵取、渲染 Watcher
渲染 watcher 的執(zhí)行過程
1.當(dāng)數(shù)據(jù)更新锯厢,defineReactive 的 set 方法中調(diào)用 dep.notify()
2.調(diào)用 watcher 的 update()
3.調(diào)用 queueWatcher(),把 wacher 存入隊列捺氢,如果已經(jīng)存入摄乒,不重復(fù)添加
4.循環(huán)調(diào)用 flushSchedulerQueue()
通過 nextTick(),在消息循環(huán)結(jié)束之前時候調(diào)用 flushSchedulerQueue()
6.調(diào)用 wacher.run()
7.調(diào)用 wacher.get() 獲取最新值
如果是渲染 wacher 結(jié)束
如果是用戶 watcher斋否,調(diào)用 this.cb()
異步更新隊列-nextTick()
- Vue 更新 DOM 是異步執(zhí)行的拭荤,批量的
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)舅世。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM缨硝。 - vm.$nextTick(function () { /* 操作 DOM */ }) / Vue.nextTick(function () {})