Vue3.0採用了ES6的Proxy來進行數據監聽javascript
優勢:java
1. 對對象進行直接監聽, 能夠彌補Object.defineProperty沒法監聽新增刪除屬性的短板
2. 無需在遍歷對象進行設置監聽函數
3. 能夠適用於Array, 不須要再分紅兩種寫法
複製代碼
缺點:react
1. 兼容性不足, 致使目前IE11沒法使用
複製代碼
在分析源碼以前,咱們須要得知幾個變量先:性能優化
rawToReactive:
類型: <WeakMap>
值: {
原始對象: Proxy對象
}
reactiveToRaw:
類型: <WeakMap>
值: {
Proxy對象: 原始對象
}
targetMap:
類型: <WeakMap>
值: {
原始對象: new Map([key, new Set([effect])]) // key是原始對象裏的屬性, 值爲該key改變後會觸發的一系列的函數, 好比渲染、computed
}
複製代碼
首先咱們來看一下reactive函數bash
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (readonlyToRaw.has(target)) {
return target
}
// target is explicitly marked as readonly by user
if (readonlyValues.has(target)) {
return readonly(target)
}
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
複製代碼
首先咱們檢測了原始對象是不是隻讀的代理對象, 緊接着又檢測了是不是隻讀對象, 緣由是readonly對象是不容許進行修改編輯, 因此是不須要進行響應處理, 接下來, 主要的響應系統都在createReactiveObject裏, 下面對該函數進行分析函數
function createReactiveObject( target: any, toProxy: WeakMap<any, any>, // { originObj: proxyObj } toRaw: WeakMap<any, any>, // { proxyObj: originObj } baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target already has corresponding Proxy
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// target is already a Proxy
if (toRaw.has(target)) {
return target
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
}
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
複製代碼
首先對於類型進行了判斷,若不是對象形式則會報錯,而且不進行響應式處理 其次對是否已經處理過該對象進行了判斷,因爲Proxy對象與原對象已經不是同一個指針,因此Vue對兩個對象進行了分別的判斷性能
canObserve判斷是否符合如下四個條件優化
對象符合如下配置
* 1. 不是Vue實例
* 2. 不是虛擬DOM
* 3. 是屬於Object|Array|Map|Set|WeakMap|WeakSet其中一種
* 4. 不存在於nonReactiveValues
複製代碼
handlers這裏判斷了兩種狀況:ui
最後進行原對象的代理處理, 而且綁定了二者的關係, 在這裏咱們看見了targtMap的綁定, 這個WeakMap對於數據響應起到了很關鍵的做用,咱們下面會講到,先看下面, 緊接着就返回了代理對象spa
接下來咱們來看下代理對象handler的處理
因爲百分之99的處理都是由baseHandlers來處理,那麼咱們接下來就針對這個handlers進行分析
export const mutableHandlers: ProxyHandler<any> = {
get: createGetter(false),
set,
deleteProperty,
has,
ownKeys
}
複製代碼
很簡單的一個賦值, 咱們從get函數開始分析
function createGetter(isReadonly: boolean) {
return function get(target: any, key: string | symbol, receiver: any) {
const res = Reflect.get(target, key, receiver)
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
return res
}
if (isRef(res)) {
return res.value
}
track(target, OperationTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
複製代碼
首先獲取了該屬性的值,而後咱們看見Vue對Symbol的一些類型進行分辨, 如果符合條件則直接返回, 接下來這裏劃重點,要考, track函數對於數據響應起到了相當重要的做用, 咱們來看下track函數源碼是怎麼寫的
export function track( target: any, type: OperationTypes, key?: string | symbol ) {
if (!shouldTrack) {
return
}
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
if (effect) {
if (type === OperationTypes.ITERATE) {
key = ITERATE_KEY
}
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key!)
if (dep === void 0) {
depsMap.set(key!, (dep = new Set()))
}
if (!dep.has(effect)) {
dep.add(effect)
effect.deps.push(dep)
if (__DEV__ && effect.onTrack) {
effect.onTrack({
effect,
target,
type,
key
})
}
}
}
}
複製代碼
首先這裏定義了一個shouldTrack, 這個變量是用來控制調用生命週期的時候的開關,防止觸發屢次
獲取targetMap裏該對象各個屬性的值, 若沒有,則進行數據初始化new Set, 而且將effect添加到了該集合裏, 這裏咱們看見了 不止dep添加了, effect也添加了, 這裏是有緣由的,咱們等下進行分析
看到這裏, 相信你們都明白了track函數使用進行數據依賴採集的, 以便於後面數據更改可以觸發對應的函數
接下來咱們分析下set函數
function set( target: any, key: string | symbol, value: any, receiver: any // proxy對象 ): boolean {
value = toRaw(value)
const hadKey = hasOwn(target, key)
const oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) { // 判斷更改對象是不是
/* istanbul ignore else */
if (__DEV__) {
const extraInfo = { oldValue, newValue: value }
if (!hadKey) {
trigger(target, OperationTypes.ADD, key, extraInfo)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key, extraInfo)
}
} else {
if (!hadKey) {
trigger(target, OperationTypes.ADD, key)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key)
}
}
}
return result
}
複製代碼
首先一開始也是對於各個類型進行分析而且處理對應的狀況, 咱們主要看一下trigger函數
export function trigger( target: any, // 原始對象 type: OperationTypes, // 判斷是替換仍是增長等等操做 key?: string | symbol, // 對象屬性 extraInfo?: any ) {
const depsMap = targetMap.get(target) // new Map()
// console.log('depsMap1', depsMap);
if (depsMap === void 0) {
// never been tracked
return
}
const effects = new Set<ReactiveEffect>()
const computedRunners = new Set<ReactiveEffect>()
if (type === OperationTypes.CLEAR) {
// collection being cleared, trigger all effects for target
depsMap.forEach(dep => {
addRunners(effects, computedRunners, dep)
})
} else {
// console.log(key);
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
addRunners(effects, computedRunners, depsMap.get(key))
}
// also run for iteration key on ADD | DELETE
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
addRunners(effects, computedRunners, depsMap.get(iterationKey))
}
}
const run = (effect: ReactiveEffect) => {
scheduleRun(effect, target, type, key, extraInfo)
}
// Important: computed effects must be run first so that computed getters
// can be invalidated before any normal effects that depend on them are run.
computedRunners.forEach(run)
effects.forEach(run)
}
複製代碼
在trigger函數裏咱們看到了兩個集合變量, effects與computedRunners, 兩個集合針對兩種類型進行數據採集我麼往下看
引入眼簾的估計就是addRunners, 咱們猜想下 這個addRunners顧名思義應該就是添加執行任務, 下面看下源碼
function addRunners( effects: Set<ReactiveEffect>, computedRunners: Set<ReactiveEffect>, effectsToAdd: Set<ReactiveEffect> | undefined // Effects集合 ) {
// console.log('effectsToAdd', effectsToAdd);
if (effectsToAdd !== void 0) {
effectsToAdd.forEach(effect => {
if (effect.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
})
}
}
複製代碼
effectsToAdd就是track函數裏添加的對象屬性的值 new Set, 用於收集依賴的, 根據effect是不是計算屬性來分別添加到不一樣的集合下, 回到trigger函數裏, 咱們看見了effects與computedRunners進行遍歷執行, 那麼咱們在分析下具體的scheduleRun函數
function scheduleRun( effect: ReactiveEffect, target: any, type: OperationTypes, key: string | symbol | undefined, extraInfo: any ) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(
extend(
{
effect,
target,
key,
type
},
extraInfo
)
)
}
if (effect.scheduler !== void 0) {
effect.scheduler(effect)
} else {
effect()
}
}
複製代碼
這裏咱們看見了Vue對effect進行了兩種狀況的判斷, 首先判斷了effect.scheduler是否存在, 若存在則使用scheduler來調用effect, 不存在則進行直接調用, 那麼scheduler究竟是什麼呢? 這裏的scheduler就是Vue的性能優化點,放入隊裏裏, 等到miscroTask裏進行調用, 熟悉Vue2.x的同窗都知道nextTick函數, 這個scheduler能夠看作就是調用了nextTick函數
咱們來看下effect具體是什麼
const effect: ReactiveEffect = function effect(...args: any[]): any {
return run(effect as ReactiveEffect, fn, args)
}
function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
if (!effect.active) {
return fn(...args)
}
if (activeReactiveEffectStack.indexOf(effect) === -1) {
cleanup(effect)
// 初始化mount的時候會執行effect函數, 當前effect是componentEffect, 也就是渲染函數, 此時因爲去獲取了變量數據,也就是觸發了get函數,get函數會觸發track函數, track函數就是用來收集effect,
try {
activeReactiveEffectStack.push(effect)
return fn(...args)
} finally {
activeReactiveEffectStack.pop()
}
}
}
複製代碼
effect實際上就是運行了run函數, 咱們看下run函數的運行, 在運行以前會先cleanup, 這裏咱們就要返回以前所說的track函數, 你們還記得track函數裏, 不僅dep添加了effect, effect也同時添加了dep嗎? 緣由就在這裏, cleanup須要用到
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
// console.log('deps', deps);
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
複製代碼
該函數清空了dep裏全部的依賴, 那麼膽大心細的同窗會發現一個問題:
在track函數裏已經添加了effect, 那麼爲何在這裏要從新清除掉全部的依賴呢?
理論上看起來是個很雞肋的操做, 可是實際上Vue已經考慮了全方面, 試想一個場景: A組件與B組件是經過v-if來控制展現, 當A組件首先渲染以後, 所對應的的數據就會採集對應的依賴, 此時更改v-if條件, 渲染了B組件, 如果B組件此時更改了A組件裏的變量, 如果A組件的依賴沒有被清除掉, 那麼會產生沒必要要的依賴調用, 因此Vue要事先清除掉全部的依賴, 確保依賴始終是最新的
分析到這咱們已經清楚了Vue3.0的數據響應到底是如何了!
Vue3.0從根本上解決了Vue2.x數據響應系統留下的短板, 可是兼容性上還存在問題,採用尤大一句話, IE11百足之蟲,死而不僵, 暫時還不能徹底拋棄IE11, 但願後期能有新的突破!