提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
一、首先了解Object.defineProperty、Proxy、Reflect
1. Object.defineProperty()
2. proxy
3. 解决了defineproperty的那些问题?
4. Reflect
这个函数有三个参数分别为 obj、prop、descriptor。
obj为我们要监听的对象 。 prop一个字符串或 Symbol,指定了要定义或修改的属性键。
descriptor要定义或修改的属性的描述符。
(1)写一个简单示例
(2) 监听多个属性:
(3)深度监听对象属性
(4)监听数组
let hobby = ['抽烟','喝酒','烫头']
let person = {
name:'Barry',
age:22
}
// 把 hobby 作为 person 属性监听
Object.defineProperty(person,'hobby',{
get(){
console.log('tigger get');
return hobby
},
set(newVal){
console.log('tigger set',newVal);
hobby = newVal
}
})
console.log(person.hobby);
person.hobby = ['看书','游泳','听歌']
// 不能被监听
person.hobby.push('游泳')
数组的push、unshift、splice、sort、reverse等方法,set方法是监听不到的。
vue2.x通过 劫持这些方法实现响应式。
具体实现:
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { TriggerOpTypes } from '../../v3'
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
if (__DEV__) {
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
ob.dep.notify()
}
return result
})
})
首先调用def方法,内部实现了方法的劫持。
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
处理方法的逻辑都在 mutator() 函数中。其中如果是push,unshift、splice 就会触发observeArray方法。
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
最后都会执行 notify,通知变更。notify 函数中通过onTrigger派发依赖,update方法渲染dom;就不具体看了,大体流程就这样。
notify(info?: DebuggerEventExtraInfo) {
// stabilize the subscriber list first
const subs = this.subs.filter(s => s) as DepTarget[]
if (__DEV__ && !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++) {
const sub = subs[i]
if (__DEV__ && info) {
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
sub.update()
}
}
概念:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
new Proxy() 包含两个参数,分别为target和handler。代表要使用Proxy包装的对象,定义一些行为。
简单实例
Reflect是ES6的高级Api,是一个内置对象,他提供了拦截js的方法。Reflect不是函数对象,所以不能被构造。
Reflect的所有方法都是静态的
为什么要使用Reflect?
(1)触发代理对象的劫持时,保证正确的this上下文指向。其中receiver的作用就是修改this指向
(2)代码的健壮性
(3)操作对象出错时返回false,避免大量try cache
vue的版本是"version": "3.2.45", 知道大概的实现思路即可,深入理解还得慢慢来,别给自己太大压力。
reactive的实现函数是createReactiveObject 。
// 获取数据类型
function targetTypeMap(rawType) {
switch (rawType) {
case 'Object':
case 'Array':
return 1 /* TargetType.COMMON */;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return 2 /* TargetType.COLLECTION */;
default:
return 0 /* TargetType.INVALID */;
}
}
// 根据target 生成proxy实例
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler,
proxyMap: WeakMap
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// 目标值已经是proxy对象,就直接返回
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 目标值已经有对应的proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
//只能观察到特定的值类型
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === 2 ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
createReactiveObject简要分析:(1)判断reactive传值是否为引用类型,如果不是引用类型则直接返回值,不具备响应式。(2)判断传值是狗已经是proxy对象,是就直接返回。(3)判断该proxy对象是否存在,存在直接返回。(4)判断是否是可跳过或者非扩展对象,是就直接返回(5)最后生成proxy对象,并对目标值进行存储,避免重复代理。
注意:
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
劫持对象的主要方法分为两种:第一种代理的值为Map、Set、WeakMap、WeakSet对象,代理以上对象内部只监听get方法,第二种代理的值为Array、Object,内部监听get,set,
deleteProperty、has、ownKeys。其中get、has、ownKeys都是执行track函数进行依赖收集,set、deleteProperty执行trigger函数进行派发依赖
依赖收集和依赖派发是实现在effect.ts中,大家可以自己查看。
这里就不说collectionHandlers、baseHandlers,就是通过这个函数定义了监听对象的一些方法,proxy的内部原理。当修改或者读取时触发相应的方法。
然后就是get、has、ownKeys会进行依赖收集。set、deleteProperty方法会进行派发依赖。
在分析依赖收集和派发时,我们先了解一下effect.ts中的ReactiveEffect类,effect()
// 这个类的作用是创建一个响应式副作用函数,这个函数会在依赖数据发生变化时执行
export class ReactiveEffect {
// 是否处于活动状态
active = true
// 响应式依赖项集合
deps: Dep[] = []
// 父级作用域
parent: ReactiveEffect | undefined = undefined
computed?: ComputedRefImpl
allowRecurse?: boolean
private deferStop?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
// 用来记录副作用函数
public fn: () => T,
// 用户记录调度器
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
// 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
}
stop() {
}
}
知道里边有两个重要的方法:stop和run。
run:就是执行副作用函数
function run() {
// 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
if (!this.active) {
return this.fn();
}
// 寻找当前 ReactiveEffect 对象的最顶层的父级作用域
let parent = activeEffect;
let lastShouldTrack = shouldTrack;
while (parent) {
if (parent === this) {
return;
}
parent = parent.parent;
}
try {
// 记录父级作用域为当前活动的 ReactiveEffect 对象
this.parent = activeEffect;
// 将当前活动的 ReactiveEffect 对象设置为 “自己”
activeEffect = this;
// 将 shouldTrack 设置为 true (表示是否需要收集依赖)
shouldTrack = true;
// effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1
trackOpBit = 1 << ++effectTrackDepth;
// 这里是用于控制 "effect调用栈的深度" 在一个阈值之内
if (effectTrackDepth <= maxMarkerBits) {
// 初始依赖追踪标记
initDepMarkers(this);
}
else {
// 清除所有的依赖追踪标记
cleanupEffect(this);
}
// 执行副作用函数,并返回执行结果
return this.fn();
}
finally {
// 如果 effect调用栈的深度 没有超过阈值
if (effectTrackDepth <= maxMarkerBits) {
// 确定最终的依赖追踪标记
finalizeDepMarkers(this);
}
// 执行完毕会将 effectTrackDepth 减 1
trackOpBit = 1 << --effectTrackDepth;
// 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域”
activeEffect = this.parent;
// 将 shouldTrack 设置为上一个值
shouldTrack = lastShouldTrack;
// 将父级作用域设置为 undefined
this.parent = undefined;
// 延时停止,这个标志是在 stop 方法中设置的
if (this.deferStop) {
this.stop();
}
}
}
stop
function stop() {
// 如果当前 活动的 ReactiveEffect 对象是 “自己”
// 延迟停止,需要执行完当前的副作用函数之后再停止
if (activeEffect === this) {
// 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法
this.deferStop = true;
}
// 如果当前 ReactiveEffect 对象处于活动状态
else if (this.active) {
// 清除所有的依赖追踪标记
cleanupEffect(this);
// 如果有 onStop 回调函数,就执行
if (this.onStop) {
this.onStop();
}
// 将 active 设置为 false
this.active = false;
}
}
effect()
effect()函数会真正的执行run方法(执行副作用函数)。
export function effect(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 这里得到ReactiveEffect类 的实例
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
这里再看依赖收集和派发就会大概清楚执行顺序。
/ 收集依赖
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
// targetMap 为全局WeakMap对象,在依赖派发的时候使用
let depsMap = targetMap.get(target)
// 不存在target
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (shouldTrack) {
dep.add(activeEffect!)
}
}
以上是依赖收集的过程,定义了一个targetMap对象(WeakMap)键值target,值是一个Map,这个Map的键key(即对象的属性值),值是Set集合。使用Set避免重复的副作用函数。
收集完依赖以后如下结构:
/**
* 触发依赖
* @param target 指向的对象
* @param type 操作类型
* @param key 指向对象的 key
* @param newValue 新值
* @param oldValue 旧值
* @param oldTarget 旧的 target
*/
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map | Set
) {
// 获取targetMap中的depsMap
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 创建一个数组,用来存放需要执行的ReactiveEffect对象
let deps: (Dep | undefined)[] = []
// 如果 type 为 clear,就会将 depsMap 中的所有 ReactiveEffect 对象都添加到 deps 中
if (type === ‘clear’) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
// 如果 key 为 length ,并且 target 是一个数组
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newLength) {
deps.push(dep)
}
})
} else {
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// 执行 add、delete、set 操作时,就会触发的依赖变更
switch (type) {
case 'add':
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case 'delete':
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case 'set':
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// 如果dep不是数组,就会将dep转成数组,因为dep可能时Set对象
const effects = isArray(dep) ? dep : [...dep]
// 遍历所有的依赖,
for (const effect of effects) {
// 执行computed依赖
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
// 执行其他依赖
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
// 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
// 如果 effect 是一个调度器,就会执行 scheduler
if (effect.scheduler) {
effect.scheduler()
} else {
// 否则直接执行 effect.run()
effect.run()
}
}
}
tigger
函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
总结
reactive(obj) vue3内部先执行(在reactive.ts中)createReactiveObject() 参数 target(要代理的对象)、isReadonly(是否只读)、baseHanlers(拦截Object和Array类型数据的函数集合)、collectionHandlers(拦截Map,Set,WeakMap,WeakSet的函数集合)、proxyMap(WeakMap集合用来记录代理的对象,避免重复代理)。此函数主要用来创建响应式对象。
然后我们需要关注proxy的第二个参数,也就是baseHandlers和collectionHandlers 分别在对应的文件里。在文件中我们可以看到mutableHandler和mutableCollectionHandlers 作为Proxy的第二个参数。用来监听被代理的对象。再次对象的方法中get、has 、ownKeys方法执行会收集依赖,set和deleteproperty方法会触发依赖。重点关注set函数(createSetter()返回值)和get函数(createGetter())
然后关注track()和trigger(),分别实现收集和触发依赖。都在effect,ts文件中。
track---->trackEffects.。 trigger--->triggerEffects--->triggerEffect(执行副作用)
然后看 effect() 也在effect.ts文件中,这里会const _effect = new ReactiveEffect(fn);_effect.run()
然后在这个文件中找到ReactiveEffect构造函数。这就是大概的流程。
收集的依赖就是ReactiveEffect实例,解释初始化组件时,会执行ReactiveEffect构造函数,执行run方法,会将this赋值activeEffect,这里的this指向调用run的对象,这就是ReactiveEffect的实例。 收集依赖时:dep.add(activeEffect!)。