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, 但愿后期能有新的突破!