響應(yīng)式原理
- 響應(yīng)式是
Vue.js 組件化更新渲染的一個核心機制
Vue2.x響應(yīng)式實現(xiàn)
-
Object.defineProperty API 劫持數(shù)據(jù)的變化
- 在數(shù)據(jù)被訪問的時候收集依賴
- 然后在數(shù)據(jù)被修改的時候通知依賴更新
- 在
Vue.js 2.x 中,Watcher 就是依賴,
- 首先是依賴收集流程,組件在
render 的時候會訪問模板中的數(shù)據(jù),觸發(fā) getter 把 render watcher 作為依賴收集,并和數(shù)據(jù)建立聯(lián)系
- 然后是派發(fā)通知流程,當我對這些數(shù)據(jù)修改的時候,會觸發(fā)
setter,通知 render watcher 更新,進而觸發(fā)了組件的重新渲染
-
Object.defineProperty API 的一些缺點:
- 不能監(jiān)聽對象屬性新增和刪除
- 初始化階段遞歸執(zhí)行
Object.defineProperty 帶來的性能負擔(dān)
響應(yīng)式對象的實現(xiàn)差異
- 在
Vue.js 2.x 中構(gòu)建組件時,只要我們在 data、props、computed 中定義數(shù)據(jù),那么它就是響應(yīng)式的
- 到了
Vue.js 3.0 構(gòu)建組件時,你可以不依賴于 Options API,而使用 Composition API 去編寫
-
Composition API 更推薦用戶主動定義響應(yīng)式對象,而非內(nèi)部的黑盒處理
Reactive API
-
reactive 函數(shù)的具體實現(xiàn)過程
function reactive (target) {
// 如果嘗試把一個 readonly proxy 變成響應(yīng)式,直接返回這個 readonly proxy
if (target && target.__v_isReadonly) {
return target
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}
-
reactive 內(nèi)部通過 createReactiveObject 函數(shù)把 target 變成了一個響應(yīng)式對象
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
// 目標必須是對象或數(shù)組類型
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
// target 已經(jīng)是 Proxy 對象,直接返回
// 有個例外,如果是 readonly 作用于一個響應(yīng)式對象,則繼續(xù)
return target
}
if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) {
// target 已經(jīng)有對應(yīng)的 Proxy 了
return isReadonly ? target.__v_readonly : target.__v_reactive
}
// 只有在白名單里的數(shù)據(jù)類型才能變成響應(yīng)式
if (!canObserve(target)) {
return target
}
// 利用 Proxy 創(chuàng)建響應(yīng)式
const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)
// 給原始數(shù)據(jù)打個標識,說明它已經(jīng)變成響應(yīng)式,并且有對應(yīng)的 Proxy 了
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
return observed
}
- 1、 函數(shù)首先判斷
target 是不是數(shù)組或者對象類型,如果不是則直接返回。所以原始數(shù)據(jù) target 必須是對象或者數(shù)組。
- 2、通過
target.__v_raw 屬性,和__v_isReactive屬性來判斷 target 是否已經(jīng)是一個響應(yīng)式對象,如果是,直接返回該對象
- 3、使用
canObserve 函數(shù)對 target 對象做一進步限制
- 帶有
__v_skip 屬性的對象、被凍結(jié)的對象,以及不在白名單內(nèi)的對象如 Date 類型的對象實例是不能變成響應(yīng)式的
const canObserve = (value) => {
return (!value.__v_skip &&
isObservableType(toRawType(value)) &&
!Object.isFrozen(value))
}
const isObservableType = /*#__PURE__*/
makeMap('Object,Array,Map,Set,WeakMap,WeakSet')
- 4、通過
Proxy API 劫持 target 對象,把它變成響應(yīng)式
- 5、給原始數(shù)據(jù)打個標識,
target.__v_reactive = observed
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
Proxy 處理器對象 mutableHandlers
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
}
- 劫持了我們對 observed 對象的一些操作
- 1)訪問對象屬性會觸發(fā)
get 函數(shù);
- 2)設(shè)置對象屬性會觸發(fā)
set 函數(shù);
- 3)刪除對象屬性會觸發(fā)
deleteProperty 函數(shù);
- 4)in 操作符會觸發(fā)
has 函數(shù);
- 5)通過
Object.getOwnPropertyNames 訪問對象屬性名會觸發(fā) ownKeys 函數(shù)
依賴收集:get 函數(shù)
- 依賴收集發(fā)生在數(shù)據(jù)訪問的階段,get執(zhí)行
createGetter
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
if (key === "__v_isReactive" /* isReactive */) {
// 代理 observed.__v_isReactive
return !isReadonly
}
else if (key === "__v_isReadonly" /* isReadonly */) {
// 代理 observed.__v_isReadonly
return isReadonly;
}
else if (key === "__v_raw" /* raw */) {
// 代理 observed.__v_raw
return target
}
const targetIsArray = isArray(target)
// arrayInstrumentations 包含對數(shù)組一些方法修改的函數(shù)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 求值
const res = Reflect.get(target, key, receiver)
// 內(nèi)置 Symbol key 不需要依賴收集
if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {
return res
}
// 依賴收集
!isReadonly && track(target, "get" /* GET */, key)
return isObject(res)
? isReadonly
?
readonly(res)
// 如果 res 是個對象或者數(shù)組類型,則遞歸執(zhí)行 reactive 函數(shù)把 res 變成響應(yīng)式
: reactive(res)
: res
}
}
-
get 函數(shù)主要做了四件事情
- 1)首先對特殊的
key 做了代理
- 2)通過
Reflect.get 方法求值,如果 target 是數(shù)組且 key 命中了 arrayInstrumentations,則執(zhí)行對應(yīng)的函數(shù)
const arrayInstrumentations = {}
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
arrayInstrumentations[key] = function (...args) {
// toRaw 可以把響應(yīng)式對象轉(zhuǎn)成原始數(shù)據(jù)
const arr = toRaw(this)
for (let i = 0, l = this.length; i < l; i++) {
// 依賴收集
track(arr, "get" /* GET */, i + '')
}
// 先嘗試用參數(shù)本身,可能是響應(yīng)式數(shù)據(jù)
const res = arr[key](...args)
if (res === -1 || res === false) {
// 如果失敗,再嘗試把參數(shù)轉(zhuǎn)成原始數(shù)據(jù)
return arr[key](...args.map(toRaw))
}
else {
return res
}
}
})
- 3)通過
Reflect.get 求值,然后會執(zhí)行 track 函數(shù)收集依賴
- 4)對計算的值
res 進行判斷,如果它也是數(shù)組或?qū)ο螅瑒t遞歸執(zhí)行 reactive 把 res 變成響應(yīng)式對象,因為Proxy劫持的是對象本身,并不能劫持子對象的變化
track函數(shù)收集依賴
- 整個
get 函數(shù)最核心的部分其實是執(zhí)行 track 函數(shù)收集依賴
// 是否應(yīng)該收集依賴
let shouldTrack = true
// 當前激活的 effect
let activeEffect
// 原始數(shù)據(jù)對象 map
const targetMap = new WeakMap()
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
// 每個 target 對應(yīng)一個 depsMap
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// 每個 key 對應(yīng)一個 dep 集合
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 收集當前激活的 effect 作為依賴
dep.add(activeEffect)
// 當前激活的 effect 收集 dep 集合作為依賴
activeEffect.deps.push(dep)
}
}
- 收集的依賴就是數(shù)據(jù)變化后執(zhí)行的副作用函數(shù)
- 創(chuàng)建了全局的
targetMap 作為原始數(shù)據(jù)對象的 Map,它的鍵是 target,值是 depsMap,作為依賴的 Map
-
depsMap 的鍵是 target 的 key,值是 dep 集合
-
dep 集合中存儲的是依賴的副作用函數(shù)
派發(fā)通知:set 函數(shù)
- 派發(fā)通知發(fā)生在數(shù)據(jù)更新的階段,
set 函數(shù)的實現(xiàn),它是執(zhí)行 createSetter 函數(shù)的返回值
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
value = toRaw(value)
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// 如果目標的原型鏈也是一個 proxy,通過 Reflect.set 修改原型鏈上的屬性會再次觸發(fā) setter,這種情況下就沒必要觸發(fā)兩次 trigger 了
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value)
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue)
}
}
return result
}
}
- 主要做兩件事情:
- 1)通過
Reflect.set 求值
- 2)通過
trigger 函數(shù)派發(fā)通知,并依據(jù) key 是否存在于 target 上來確定通知類型,即新增還是修改
trigger 函數(shù)派發(fā)通知
// 原始數(shù)據(jù)對象 map
const targetMap = new WeakMap()
function trigger(target, type, key, newValue) {
// 通過 targetMap 拿到 target 對應(yīng)的依賴集合
const depsMap = targetMap.get(target)
if (!depsMap) {
// 沒有依賴,直接返回
return
}
// 創(chuàng)建運行的 effects 集合
const effects = new Set()
// 添加 effects 的函數(shù)
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
effects.add(effect)
})
}
}
// SET | ADD | DELETE 操作之一,添加對應(yīng)的 effects
if (key !== void 0) {
add(depsMap.get(key))
}
const run = (effect) => {
// 調(diào)度執(zhí)行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
// 直接運行
effect()
}
}
// 遍歷執(zhí)行 effects
effects.forEach(run)
}
- 主要做了四件事情
- 1)通過
targetMap 拿到 target 對應(yīng)的依賴集合 depsMap
- 2)創(chuàng)建運行的
effects 集合
- 3)根據(jù)
key 從 depsMap 中找到對應(yīng)的 effects 添加到 effects 集合;
- 4)遍歷
effects 執(zhí)行相關(guān)的副作用函數(shù)
副作用函數(shù)
// 全局 effect 棧
const effectStack = []
// 當前激活的 effect
let activeEffect
function effect(fn, options = EMPTY_OBJ) {
if (isEffect(fn)) {
// 如果 fn 已經(jīng)是一個 effect 函數(shù)了,則指向原始函數(shù)
fn = fn.raw
}
// 創(chuàng)建一個 wrapper,它是一個響應(yīng)式的副作用的函數(shù)
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
// lazy 配置,計算屬性會用到,非 lazy 則直接執(zhí)行一次
effect()
}
return effect
}
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect(...args) {
if (!effect.active) {
// 非激活狀態(tài),則判斷如果非調(diào)度執(zhí)行,則直接執(zhí)行原始函數(shù)。
return options.scheduler ? undefined : fn(...args)
}
if (!effectStack.includes(effect)) {
// 清空 effect 引用的依賴
cleanup(effect)
try {
// 開啟全局 shouldTrack,允許依賴收集
enableTracking()
// 壓棧
effectStack.push(effect)
activeEffect = effect
// 執(zhí)行原始函數(shù)
return fn(...args)
}
finally {
// 出棧
effectStack.pop()
// 恢復(fù) shouldTrack 開啟之前的狀態(tài)
resetTracking()
// 指向棧最后一個 effect
activeEffect = effectStack[effectStack.length - 1]
}
}
}
effect.id = uid++
// 標識是一個 effect 函數(shù)
effect._isEffect = true
// effect 自身的狀態(tài)
effect.active = true
// 包裝的原始函數(shù)
effect.raw = fn
// effect 對應(yīng)的依賴,雙向指針,依賴包含對 effect 的引用,effect 也包含對依賴的引用
effect.deps = []
// effect 的相關(guān)配置
effect.options = options
return effect
}
-
effect 內(nèi)部通過執(zhí)行 createReactiveEffect 函數(shù)去創(chuàng)建一個新的 effect 函數(shù),為了和外部的 effect 函數(shù)區(qū)分,我們把它稱作 reactiveEffect 函數(shù),并且還給它添加了一些額外屬性
- 這個
reactiveEffect 函數(shù)就是響應(yīng)式的副作用函數(shù),當執(zhí)行 trigger 過程派發(fā)通知的時候,執(zhí)行的 effect 就是它
-
reactiveEffect 函數(shù)只需要做兩件事情
- 1)把全局的
activeEffect 指向它
- 2)然后執(zhí)行被包裝的原始函數(shù)
fn
-
effectStack維護一個棧,解決嵌套場景,activeEffect指向問題,activeEffect 指向 effectStack 最后一個元素
- 入棧前會執(zhí)行
cleanup 函數(shù)清空 reactiveEffect函數(shù)對應(yīng)的依賴
readonly API
- 創(chuàng)建只讀對象,不能修改它的屬性,也不能給這個對象添加和刪除屬性
function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers)
}
-
readonly 和 reactive 函數(shù)的主要區(qū)別,就是執(zhí)行 createReactiveObject 函數(shù)時的參數(shù) isReadonly 不同。
- 首先
isReadonly 變量為 true,所以在創(chuàng)建過程中會給原始對象 target 打上一個 __v_readonly 的標識
- 另外還有一個特殊情況,如果
target 已經(jīng)是一個 reactive 對象,就會把它繼續(xù)變成一個 readonly 響應(yīng)式對象
- 創(chuàng)建代理是,傳入
readonlyHandlers
const readonlyHandlers = {
get: readonlyGet,
has,
ownKeys,
set(target, key) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target)
}
return true
},
deleteProperty(target, key) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target)
}
return true
}
}
-
readonlyHandlers和 mutableHandlers的區(qū)別主要在 get、set 和 deleteProperty 三個函數(shù)上
- 在非生產(chǎn)環(huán)境下
set 和 deleteProperty 函數(shù)的實現(xiàn)都會報警告,提示用戶 target 是 readonly 的
-
readonlyGet的實現(xiàn),即createGetter(true)
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
// ...
// isReadonly 為 true 則不需要依賴收集
!isReadonly && track(target, "get" /* GET */, key)
return isObject(res)
? isReadonly
?
// 如果 res 是個對象或者數(shù)組類型,則遞歸執(zhí)行 readonly 函數(shù)把 res readonly
readonly(res)
: reactive(res)
: res
}
}
- 和
reactive API 最大的區(qū)別就是不做依賴收集
ref API
-
reactive API對傳入的 target 類型有限制,必須是對象或者數(shù)組類型,而對于一些基礎(chǔ)類型(比如 String、Number、Boolean)是不支持的,因此有了ref API
- 使用
const msg = ref('Hello World')
msg.value = 'Hello Vue'
function ref(value) {
return createRef(value)
}
const convert = (val) => isObject(val) ? reactive(val) : val
function createRef(rawValue) {
if (isRef(rawValue)) {
// 如果傳入的就是一個 ref,那么返回自身即可,處理嵌套 ref 的情況。
return rawValue
}
// 如果是對象或者數(shù)組類型,則轉(zhuǎn)換一個 reactive 對象。
let value = convert(rawValue)
const r = {
__v_isRef: true,
get value() {
// getter
// 依賴收集,key 為固定的 value
track(r, "get" /* GET */, 'value')
return value
},
set value(newVal) {
// setter,只處理 value 屬性的修改
if (hasChanged(toRaw(newVal), rawValue)) {
// 判斷有變化后更新值
rawValue = newVal
value = convert(newVal)
// 派發(fā)通知
trigger(r, "set" /* SET */, 'value', void 0)
}
}
}
return r
}
- 首先處理嵌套
ref,如果傳入的 rawValue 也是 ref,那么直接返回
- 然后對
rawValue 做了一層轉(zhuǎn)換,如果 rawValue 是對象或者數(shù)組類型,那么把它轉(zhuǎn)換成一個 reactive 對象。
- 最后定義一個對
value 屬性做 getter 和 setter 劫持的對象并返回
-
get 部分就是執(zhí)行 track 函數(shù)做依賴收集然后返回它的值
-
set 部分就是設(shè)置新值并且執(zhí)行 trigger 函數(shù)派發(fā)通知
- 區(qū)別于
vue2.x
- 1)劫持數(shù)據(jù)的方式改成用
Proxy 實現(xiàn)
- 2)收集的依賴由
watcher 實例變成了組件副作用渲染函數(shù)