在上一篇中 认识Vue3.js 的源码架构我们介绍了Vue3 的目录结构,编写了第一个示例代码。本篇将介绍,如何debugger进行代码阅读。
一、 更改sourceMap 配置
为什么要更改sourceMap 配置呢!我们在运行的页面,打开控制台查看source
可以看到做source 中的代码是打包过的,非常不好阅读和调试。所以我们就需要更改sourceMap的配置。
打开package.json ,这里面配置了我们之前执行的命令
我们在控制台执行pnpm run build 执行的其实就是这个一行代码,他执行了scripts/build.js, 我们现在来打开script/build.js, 找到193 行,可以看到这里的sourceMap 变量的值决定了是否开启sourceMap,现在我们在看到
现在找到77行
可以看到sourceMap 的值来自于values, 再找到54行和35行
可以看到values 的值取决于parseArgs 这个函数,再找到21行
可以看到parseArgs 函数来自于node的工具模块,是一个处理命令行参数的模块,不太清楚的可以查看node 的官方文档util.parseArgs([config])
由54行和35行可以得出只要传入sourceMap 参数就可以开启sourceMap了,所以现在我们需要去package.json, 修改build 的命令行参数:
现在我们来执行下打包命令: pnpm run build
停掉之前的页面运行,在vue/examples/test/reactive.html 鼠标右键选择Stop Live Server,
重新启动运行 Open with Live Server
现在我们来看浏览器控制台的source 变化
可以看到,我们现在就能看到Vue 的源码了,调试起来就会方便很多。
二、reactive 源码阅读
在阅读代码之前,一定要记住两个个原则:
- 不要力图一行一行的把每行代码都看懂看明白,Vue 源码少说也有几万行代码,甚至更多,我们不可能有那么多精力。我们学习核心思想就好
- debugger 时没有进入的分支先不要看,否则你也会放弃的,因为这些比较完美的框架考虑的因素都是比较多的,你看的时候就会发现这里点进去有if分支,再进去又有if分支, 买噶,看不下去了。所以只需要关注当前debugger 进入了哪个分支即可
我们现在在vue/examples/test/reactive.html 中打一个断点
刷新页面,就会进入debugger 状态,
我们点击下一步:
就会进入
reactive 这一行代码
在点击下一步进入Vue 源码中的
reactive方法:
可以看到Vue源码中reactive 方法接收了target 参数,就是我们传进去的,{desc: 'Vue'} 对象,这里Vue会判断是否是只读,如果是只读会直接返回target,这里的只读方法我们不用太关注,因为我们今天的主要任务是,阅读reactive相关的代码。我们点击下一步会进入isReadonly 方法,
我们直接点击跳出该函数:
就会进入return createReactiveObject 这一行代码,
点击下一步进入createReactiveObject 函数,
这里我们看到,会判读是不是一个对象,我们知道我们传进来的是一个对象,所以直接点击跳到Step over next function call按钮
可以看到就直接接入下一个
if 判断了, 没有进入isObject方法。我们继续点击跳到(跳到下一行)按钮
我们继续点击跳到Step over next function call的按钮,然后把鼠标放到
targetType 上,看到targetType 的值为1,
而在TargetType枚举当中,INVALID 的值为0,
所以这里依然不会进入if里面,我们继续点击Step over next function call按钮
在这里可以看到
proxyMap是一个WeakMap, 调用它的get方法获取key值为target的值,在右侧我们可以看到proxyMap 为空, 所以肯定获取不到。我们继续点击跳到Step over next function call的按钮,
我们知道
proxyMap.get(target) 没有获取到值,所以肯定不会进到if里面,我们继续点击跳到Step over next function call按钮
可以看到,所有的判断都没有进入,直接到了
new Proxy,这就是实现reactive 的关键了。new Proxy 第一个参数是被代理对象,第二个参数是handler(对象),属性是定义了在对代理执行操作时的行为的函数。在这里targetType 的值是1, 而在packages/reactivity/eactive.ts 中可以看到,COLLECTION 值为2, 所以,这里会应用baseHandlers。我们继续点击跳到Step over next function call按钮,
可以看会把target作为键,proxy(代理对象)作为值添加到proxyMap里面,前面我们已经知道proxyMap是一个WeakMap。我们在点击跳到Step over next function call按钮,
就会将proxy 返回出去。再点击跳到下Step over next function call按钮。
再继续点击跳到Step over next function call按钮
可以看到进入了我们写的
effect这一行代码,到这里,reactive方法就执行完了,我们现在可以在控制台打印下data。
可以看到
data就变成了Proxy代理对象, 具有响应性。
现在我们再回来看一下baseHandlers,到packages/reactivity/src/reactive.ts 搜索baseHandlers,找到260行,可以看到baseHandlers 来自createReactiveObject的第三个参数。
现在我们来搜索createReactiveObject 调用的地方,
可以看到
baseHandlers 来自mutableHandlers,我们现在再搜索mutableHandlers
可以看到
mutableHandlers 来自baseHandlers.ts, 我们按住ctrl 鼠标点击进入定义的位置。
可以看到mutableHandlers 是一个new MutableReactiveHandler 的对象,我们再按住ctrl 鼠标点击MutableReactiveHandler 跳转到鼠标点击MutableReactiveHandler。
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow)
}
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = target[key]
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
return false
} else {
oldValue.value = value
return true
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver,
)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
deleteProperty(
target: Record<string | symbol, unknown>,
key: string | symbol,
): boolean {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}
}
MutableReactiveHandler 就很重要了,定义了设置属性,删除属性的处理逻辑,MutableReactiveHandler 还继承了BaseReactiveHandler, 我们再按住ctrl 鼠标点击BaseReactiveHandler 进去看看
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object): any {
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
const isReadonly = this._isReadonly,
isShallow = this._isShallow
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return isShallow
} else if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}
const targetIsArray = isArray(target)
if (!isReadonly) {
let fn: Function | undefined
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver,
)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (isShallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
这也是一个关键的类,它定义了get的处理逻辑。到这里我们reactive 的代码我们就阅读得差不多了。
来理一下这里面的主要流程
1. 执行reactive 方法
2. reactive 方法中返回createReactiveObject 的执行
3. createReactiveObject 方法中 使用new Proxy 将传入的target 变为具有响应式的代理对象。
4. new Proxy 中传入的handler 是baseHandlers
5. baseHandlers 方法来自mutableHandlers
6. mutableHandlers 来自MutableReactiveHandler 的实例
7. MutableReactiveHandler 中定义了删除属性,设置属性相关方法的拦截处理
8. MutableReactiveHandler 还继承了BaseReactiveHandler 类
9. BaseReactiveHandler 定义了get,也就是读取属性相关的处理逻辑
三、总结
本篇中首先介绍了sourceMap 的配置,开启了sourceMap之后可以在浏览器中进行源码的调试。然后我们对reactive 方法相关逻辑进行了调试和阅读。了解了reactive 的最主要流程。大家想要更详细的了解reactive方法的话可以深入阅读MutableReactiveHandler 和BaseReactiveHandler