前言
拉取代码
$ git clone git@github.com:cordiverse/cordis.git
拉取依赖
# 仓库默认使用 yarn 作为包管理器,不折腾 $ yarn
预编译工作区子包
# 对应命令为 yarn yakumo build $ yarn build
项目结构:
cordis
:Meta-Framework for Modern JavaScript Applications@cordisjs/core
:↑create-cordis
:Setup a Cordis application@cordisjs/plugin-hmr
:Hot Module Replacement Plugin for Cordis、@cordisjs/loader
:Loader for cordis@cordisjs/logger
:Logger service for cordis@cordisjs/schema
:Schema service for cordis@cordisjs/timer
:Timer service for cordis
主要依赖:
c8
:output coverage reports using Node.js' built in coverage.esbuild
:An extremely fast bundler for the web.esbuild-register
:Transpile JSX, TypeScript and esnext features on the fly with esbuild.mocha
:simple, flexible, fun javascript test framework for node.js & the browser.shx
:Portable Shell Commands for Node.tsx
:TypeScript Execute | The easiest way to run TypeScript in Node.js.yakumo
:Manage complex workspaces with ease.yml-register
:Hooks into Node's require function to load.yaml
and.yml
files.
学习源码时,刚拿到一个全新的框架项目,不像常规的业务代码项目,往往抽象程序时是很高的,我的经验是,首先是略读一遍项目文档:介绍 | Cordis,之后针对代码细节回顾文档内容。
当前 Cordis 文档尚未完善,不过已有的内容已经够我理解一段时间了。
扫一眼项目结构和依赖后,可以聚焦到项目的单元测试代码,能最快的了解到整个项目,各个模块单元的情况或者说作用责任。
注意到,在 package.json
确实存在多条用于测试的命令,其中:yarn yakumo mocha --import tsx
,执行后发现,只打印了:
⚡Mahoo12138 ❯❯ yarn test
unknown command: mocha
好像有什么不对劲的地方,保持好奇心,继续试验了其他几条:
shx rm -rf coverage && c8 -r text yarn test
...
执行后都打印了 unknown command: mocha
,但是也如期输出了测试覆盖率,代码逻辑是正常运行的。
接着我们直接对每个用例进行测试:
$ npx mocha ./**/tests/*.spec.ts --require esbuild-register
也都正常输出,那么项目的基本配置应该没问题,可以针对每个测试项进行分析和学习了。
section: Association
case: service injection
// packages\core\tests\associate.spec.ts
const root = new Context()
class Foo extends Service {
qux = 1
constructor(ctx: Context) {
super(ctx, 'foo', true)
}
}
class FooBar extends Service {
constructor(ctx: Context) {
super(ctx, 'foo.bar', true)
}
}
root.plugin(Foo)
const fork = root.plugin(FooBar)
expect(root.foo).to.be.instanceof(Foo)
expect(root.foo.bar).to.be.instanceof(FooBar)
expect(root.foo.qux).to.equal(1)
fork.dispose()
expect(root.foo.bar).to.be.undefined
Context
首先创建了一个 Context 对象:
export class Context {
constructor(config?: any) {
config = resolveConfig(this.constructor, config)
this[symbols.store] = Object.create(null)
this[symbols.isolate] = Object.create(null)
this[symbols.internal] = Object.create(null)
this[symbols.intercept] = Object.create(null)
const self: Context = new Proxy(this, ReflectService.handler)
self.root = self
self.reflect = new ReflectService(self)
self.registry = new Registry(self, config)
self.lifecycle = new Lifecycle(self)
const attach = (internal: Context[typeof symbols.internal]) => {...}
attach(this[symbols.internal])
return self
}
}
export function resolveConfig(plugin: any, config: any) {
const schema = plugin['Config'] || plugin['schema']
if (schema && plugin['schema'] !== false) config = schema(config)
return config ?? {}
}
export function defineProperty<T, K extends keyof any>(object: T, key: K, value: any) {
return Object.defineProperty(object, key, { writable: true, value, enumerable: false })
}
- 接收一个可选的
config
配置参数,并调用了resolveConfig
,且这里是把 Context 当作一个插件传入,解析其Config
和schema
,生成一个config
; - 然后创建一些内部存储对象,如
store
、isolate
、internal
和intercept
,使用Object.create(null)
,确保了对象的纯洁性,是没有原型链的。 - 接着,它创建一个
Proxy
对象,且传入this
,也就是拦截属性访问,交由ReflectService.handler
处理。 - 执行它初始化
ReflectService
、Registry
和Lifecycle
,并将它们挂载到context
实例上,这里其实就已经在执行ReflectService.handler
中的set
逻辑了 。 - 其中
self.root = self
这一行很重要,对于使用new
创建出来的Context
会有该属性标记,即 root Context; - 最后,递归调用
attach
函数,附加内部服务来完成上下文对象的初始化。
可以看看 ReflectService.handler
内的set
逻辑:
class ReflectService {
static handler: ProxyHandler<Context> = {
set: (target, prop, value, ctx: Context) => {
if (typeof prop !== 'string') return Reflect.set(target, prop, value, ctx)
const [name, internal] = ReflectService.resolveInject(target, prop)
if (!internal) {
// TODO warning
return Reflect.set(target, name, value, ctx)
}
if (internal.type === 'accessor') {
if (!internal.set) return false
return internal.set.call(ctx, value, ctx[symbols.receiver])
} else {
ctx.reflect.set(name, value)
return true
}
},
}
static resolveInject(ctx: Context, name: string) {
let internal = ctx[symbols.internal][name]
while (internal?.type === 'alias') {
name = internal.name
internal = ctx[symbols.internal][name]
}
return [name, internal] as const
},
}
ReflectService.handler.set
根据 props
和 internal
处理了属性赋值的多种情况,根据已有的代码信息还不好理解其作用。
resolveInject
ReflectService.resolveInject
用于获取 ctx[symbols.internal]
中传入的 name
属性,针对internal?.type === 'alias'
做了递进获取;
其中如果 ReflectService.resolveInject
有值,且 type='accessor'
(构造阶段初始化的 internal
都是此种类型)则调用 internal.get
(reflectService._accessor
生成对象内的函数)获取值。
ReflectService
class ReflectService {
constructor(public ctx: Context) {
defineProperty(this, symbols.tracker, {
associate: 'reflect',
property: 'ctx',
})
this._mixin('reflect', ['get', 'set', 'provide', 'accessor', 'mixin', 'alias'])
this._mixin('scope', ['config', 'runtime', 'effect', 'collect', 'accept', 'decline'])
this._mixin('registry', ['using', 'inject', 'plugin'])
this._mixin('lifecycle', ['on', 'once', 'parallel', 'emit', 'serial', 'bail', 'start', 'stop'])
}
_mixin(source: any, mixins: string[] | Dict<string>) {
const entries = Array.isArray(mixins) ? mixins.map((key) => [key, key]) : Object.entries(mixins)
const getTarget = typeof source === 'string' ? (ctx: Context) => ctx[source] : () => source
const disposables = entries.map(([key, value]) => {
return this._accessor(value, {
get(receiver) {
const service = getTarget(this)
if (isNullable(service)) return service
const mixin = receiver ? withProps(receiver, service) : service
const value = Reflect.get(service, key, mixin)
if (typeof value !== 'function') return value
return value.bind(mixin ?? service)
},
set(value, receiver) {
const service = getTarget(this)
const mixin = receiver ? withProps(receiver, service) : service
return Reflect.set(service, key, value, mixin)
},
})
})
return () => disposables.forEach((dispose) => dispose())
}
_accessor(name: string, options: Omit<Context.Internal.Accessor, 'type'>) {
const internal = this.ctx.root[symbols.internal]
if (name in internal) return () => {}
internal[name] = { type: 'accessor', ...options }
return () => delete internal[name]
}
}
export function withProps(target: any, props?: {}) {
if (!props) return target
return new Proxy(target, {
get: (target, prop, receiver) => {
if (prop in props && prop !== 'constructor') return Reflect.get(props, prop, receiver)
return Reflect.get(target, prop, receiver)
},
set: (target, prop, value, receiver) => {
if (prop in props && prop !== 'constructor') return Reflect.set(props, prop, value, receiver)
return Reflect.set(target, prop, value, receiver)
},
})
}
构造函数中,通过调用 this._mixin
方法,对 reflect,scope,registry,lifecycle 这几个对象中的多个方法调用了 this._accessor
方法:
- 获取了
this.ctx.root[symbols.internal]
; - 将
internal[name]
设置为type=accessor
的get/set
的对象;
// TODO get/set
其中这里的 get/set
也就是 ReflectService.handler
的 get/set
进行 internal.set/get.call
调用时的函数。
Registry
Context
构造时,也初始化了一个 Registry
对象:
class Registry<C extends Context = Context> {
private _counter = 0
private _internal = new Map<Function, MainScope<C>>()
protected context: Context
constructor(
public ctx: C,
config: any
) {
defineProperty(this, symbols.tracker, {
associate: 'registry',
property: 'ctx',
})
this.context = ctx
const runtime = new MainScope(ctx, null!, config)
ctx.scope = runtime
runtime.ctx = ctx
runtime.status = ScopeStatus.ACTIVE
this.set(null!, runtime)
}
set(plugin: Plugin, state: MainScope<C>) {
const key = this.resolve(plugin)
this._internal.set(key!, state)
}
resolve(plugin: Plugin, assert = false): Function | undefined {
// Allow `null` as a special case.
if (plugin === null) return plugin
// ...
}
}
创建了一个 MainScope
对象赋值到 ctx.scope
,初始化 MainScope
的状态,然后 null
这个特殊键及 MainScope
存入 _internal
中。
MainScope
由于代码阅读周期长,最新 cordis 中,MainScope 已被移除,取而代之的是 EffectScope。
Registry
构造时,plugin
传入的是 null
:
export class MainScope<C extends Context = Context> extends EffectScope<C> {
name?: string
constructor(
ctx: C,
public plugin: Plugin,
config: any,
error?: any
) {
super(ctx, config)
if (!plugin) {
this.name = 'root'
this.isActive = true
} else {
this.setup()
this.init(error)
}
}
}
EffectScope
MainScope
继承自 EffectScope
:
export abstract class EffectScope<C extends Context = Context> {
public uid: number | null
public ctx: C
public acceptors = new DisposableList<() => boolean>()
public disposables = new DisposableList<Disposable>()
public status = ScopeStatus.PENDING
public dispose: () => Promise<void>
// Same as `this.ctx`, but with a more specific type.
protected context: Context
private _active = false
private _error: any
private _pending: Promise<void> | undefined
constructor(
public parent: C,
public config: C['config'],
private apply: (ctx: C, config: any) => any,
public runtime?: Plugin.Runtime
) {
if (parent.scope) {
this.uid = parent.registry.counter
this.ctx = this.context = parent.extend({ scope: this })
this.dispose = parent.scope.effect(() => {
const remove = runtime!.scopes.push(this)
this.context.emit('internal/plugin', this)
this.active = true
return async () => {
this.uid = null
this.context.emit('internal/plugin', this)
if (this.ctx.registry.has(runtime!.plugin)) {
remove()
if (!runtime!.scopes.length) {
this.ctx.registry.delete(runtime!.plugin)
}
}
this.active = false
await this._pending
}
})
} else {
this.uid = 0
this.ctx = this.context = parent
this._active = true
this.status = ScopeStatus.ACTIVE
this.dispose = () => {
throw new Error('cannot dispose root scope')
}
}
}
}
parent.extend({ scope: this })
调用后,执行的代码量非常多,整体看下来,是根据 ctx[symbols.shadow]
和[symbols.tracker]
返回 context
或基于其的一个 Proxy
对象,以及还有 { scope: this }
的 MainScope
,需要理解 Traceable
这个概念才能搞懂这里代码的作用。
export class Context {
extend(meta = {}): this {
const source = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value
const self = Object.assign(Object.create(getTraceable(this, this)), meta)
if (!source) return self
return Object.assign(Object.create(self), { [symbols.shadow]: source })
}
}
getTraceable
export function getTraceable<T>(ctx: Context, value: T, noTrap?: boolean): T {
if (!isObject(value)) return value
if (Object.hasOwn(value, symbols.shadow)) {
return Object.getPrototypeOf(value)
}
const tracker = value[symbols.tracker]
if (!tracker) return value
return createTraceable(ctx, value, tracker, noTrap)
}
实际调试中发现,对于 root 级 Context ,getTraceable
倒数第二行返回了, createTraceable
并未调用到,也不考虑其逻辑。
Lifecycle
class Lifecycle {
constructor(private ctx: Context) {
defineProperty(this, symbols.tracker, {
associate: 'lifecycle',
property: 'ctx',
})
ctx.scope.leak(this.on('internal/listener', function () {}))
for (const level of ['info', 'error', 'warning']) {
ctx.scope.leak(this.on(`internal/${level}`, (format, ...param) => {}))
}
// non-reusable plugin forks are not responsive to isolated service changes
ctx.scope.leak(
this.on('internal/before-service', function (this: Context, name) {}, {
global: true,
})
)
// ...
}
}
Lifecycle
构造函数中调用了this.on
注册了多个事件监听,且也都将解除绑定的 dispose
函数传入了ctx.scope.leak
方法;
export abstract class EffectScope<C extends Context = Context> {
leak<T>(disposable: T) {
return defineProperty(disposable, Context.static, this)
}
collect(label: string, callback: () => any) {
const dispose = defineProperty(
() => {
remove(this.disposables, dispose)
return callback()
},
'name',
label
)
this.disposables.push(dispose)
return dispose
}
}
leak
方法的逻辑很简单,就是在销毁函数 dispose(able)
上挂载 MainScope
。
class Lifecycle {
on(name: string, listener: (...args: any) => any, options?: boolean | EventOptions) {
if (typeof options !== 'object') {
options = { prepend: options }
}
// handle special events
this.ctx.scope.assertActive()
listener = this.ctx.reflect.bind(listener)
const result = this.bail(this.ctx, 'internal/listener', name, listener, options)
if (result) return result
const hooks = (this._hooks[name] ||= [])
const label = typeof name === 'string' ? `event <${name}>` : 'event (Symbol)'
return this.register(label, hooks, listener, options)
}
bail(...args: any[]) {
for (const result of this.dispatch('bail', args)) {
if (isBailed(result)) return result
}
}
*dispatch(type: string, args: any[]) {
const thisArg =
typeof args[0] === 'object' || typeof args[0] === 'function' ? args.shift() : null
const name = args.shift()
if (name !== 'internal/event') {
this.emit('internal/event', type, name, args, thisArg)
}
for (const hook of this.filterHooks(this._hooks[name] || [], thisArg)) {
yield hook.callback.apply(thisArg, args)
}
}
emit(...args: any[]) {
Array.from(this.dispatch('emit', args))
}
filterHooks(hooks: Hook[], thisArg?: object) {
thisArg = getTraceable(this.ctx, thisArg)
return hooks.slice().filter((hook) => {
const filter = thisArg?.[Context.filter]
return hook.global || !filter || filter.call(thisArg, hook.ctx)
})
}
register(label: string, hooks: Hook[], callback: any, options: EventOptions) {
const method = options.prepend ? 'unshift' : 'push'
hooks[method]({ ctx: this.ctx, callback, ...options })
return this.ctx.scope.collect(label, () => this.unregister(hooks, callback))
}
}
再来看看 Lifecycle.on
方法:
对传入的
listener
调用ReflectService.bind
,跟getTraceable
有关;接着调用
bail
方法,前两个参数是固定的this.ctx
和'internal/listener'
- 其内部再调用了
dispatch
生成器函数,使用isBailed
判断真值则返回; - 生成器内部,获取首个对象类型的参数作为
thisArg
,存在则将第二个参数作为name
; - 其次,所有
name
不为internal/event
的调用,都会调用emit
方法,且事件名为'internal/event'
,递归调用dispatch
; - 最后,通过
filterHooks
方法筛选一遍this._hooks['internal/listener']
,返回hook.callback
执行结果;
- 其内部再调用了
若
bail
结果有值,则返回;否则直接获取_hooks[name]
,调用register
方法;register
方法内,则将listener
放入对应的hooks
数组中,并通过EffectScope
收集了unregister
方法;collect
方法的实现,我觉得很巧妙,又很自然;- 定义了一个
dispose
函数,函数体则是将从数组disposables
中移除,并返回unregister
回调执行结果; - 然后将
dispose
push 到disposables
数组中,返回dispose
函数,即为leak
的参数;
整体上来看,on
方法执行时,会调用 bail
做一个前置操作,包括:
- 触发
internal/event
事件,以及internal/listener
事件; - 如果有返回值,那就终止
on
后续事件注册的register
方法执行;
// TODO trace bind
class ReflectService {
trace<T>(value: T) {
return getTraceable(this.ctx, value)
}
bind<T extends Function>(callback: T) {
return new Proxy(callback, {
apply: (target, thisArg, args) => {
return target.apply(
this.trace(thisArg),
args.map((arg) => this.trace(arg))
)
},
})
}
}
再回到 Context 的构造,最后是调用了 attach
函数:
const attach = (internal: Context[typeof symbols.internal]) => {
if (!internal) return
attach(Object.getPrototypeOf(internal))
for (const key of Object.getOwnPropertyNames(internal)) {
const constructor = internal[key]['prototype']?.constructor
if (!constructor) continue
self[internal[key]['key']] = new constructor(self, config)
defineProperty(self[internal[key]['key']], 'ctx', self)
}
}
经过 ReflectService
,Registry
,Lifecycle
这三个对象的初始化后,internal
内部已经包含很多函数了。
首先对 internal
的原型递归调用 attach
,但是从上文可知,internal
是使用 Object.create(null)
创建的,并没有原型;之后遍历属性,获取对应值的 prototype.constructor
,算是对象实例的类吧。之后获取了该实例的key
作为键,创建一个新实例挂载到 Context
上,并设置了 ctx
属性。
从目前来看,这个功能比较抽象,应为测试代码执行到此,internal
都是 type='accessor'
的get/set
对象。
Context
就这么水灵灵地初始化完毕了,可不是一般的复杂。
接下来,继续回到测试代码,调用了 context.plugin
方法,深入下ReflectService.handler
:
// const root = new Context()
// root.plugin(Foo)
static handler: ProxyHandler<Context> = {
get: (target, prop, ctx: Context) => {
if (typeof prop !== 'string') return Reflect.get(target, prop, ctx)
if (Reflect.has(target, prop)) {
return getTraceable(ctx, Reflect.get(target, prop, ctx), true)
}
const [name, internal] = ReflectService.resolveInject(target, prop)
// trace caller
const error = new Error(`property ${name} is not registered, declare it as \`inject\` to suppress this warning`)
if (!internal) {
ReflectService.checkInject(ctx, name, error)
return Reflect.get(target, name, ctx)
} else if (internal.type === 'accessor') {
return internal.get.call(ctx, ctx[symbols.receiver])
} else {
if (!internal.builtin) ReflectService.checkInject(ctx, name, error)
return ctx.reflect.get(name)
}
}
}
这里 prop 传入的是 string 类型(其余是参数访问的 Symbol,如 symbols.internal
等),然后第二个判断主要针对直接在 Context 构造时挂载的几个属性:
self.root = self
self.scope = new EffectScope(self, {}, () => {})
self.reflect = new ReflectService(self)
self.registry = new Registry(self)
self.events = new EventsService(self)
之后是 resolveInject 会在 ctx[symbols.internal]
读取 prop 对应的属性,例如这里是 plugin
,属性值为 ReflectService.accessor
和ReflectService.mixin
创建的对象 { type: 'accessor', ...options }
。
在之后检查 internal
的值,暂且猜测是兜底操作,先走目标逻辑,判断为 internal.type === 'accessor'
,则调用 internal.get
方法。
- 这里有个
ctx[symbols.receiver]
又会进来ReflectService.handler.get
,暂时不清楚symbols.receiver
的作用; - 进入
internal.get
函数体,代码在 ReflectService._mixin,这里有很大的闭包,如:source = 'registry'
,来源于this._mixin('registry', ['inject', 'plugin'])
调用;- 以及
getTarget
,key = 'plugin'
;
- 在
getTarget
中又会进入ReflectService.handler.get
,此时则对应上述的第二个判断,调用getTraceable
,代码在这 getTraceable:- 非对象直接返回该对象;
- 有
symbols.shadow
属性,返回其原型对象; - 如果对象及其原型链上不包含属性
symbols.tracker
的值,返回该对象; - 最后调用
createTraceable
函数;
对于 Registry
对象,其在初始化时,有如下操作:
class Registry<C extends Context = Context> {
constructor(public ctx: C) {
defineProperty(this, symbols.tracker, {
associate: 'registry',
property: 'ctx',
noShadow: true,
})
this.context = ctx
}
}
然后则会调用 createTraceable
,主要逻辑就是包了一层 Proxy,但是内部很复杂,等到调用时,才继续深入内部:
function createTraceable(ctx: Context, value: any, tracker: Tracker) {
if (ctx[symbols.shadow]) {
ctx = Object.getPrototypeOf(ctx)
}
const proxy = new Proxy(value, {
get: (target, prop, receiver) => {...},
set: (target, prop, value, receiver) => {...},
apply: (target, thisArg, args) => {...},
})
return proxy
}
重新捋一下思路,现在是 context 去读取 plugin 属性的函数,进行调用,传入一个插件,plugin 属性是 Registry 对象提供的方法,且通过 mixin 混入到 context 中的;
调用栈位于 ReflectService._mixin 方法的 get 函数中,获取到 createTraceable
创建的 service 后:
- 根据
receiver
判断是否需要调用withProps
,ctx[symbols.receiver]
值 undefined,跳过; - 拿到 Registry 对象实例中
key = 'plugin'
的方法函数,bind 调用;
最终,进入到了 Registry.plugin
函数体内:
plugin(plugin: Plugin<C>, config?: any) {
// check if it's a valid plugin
const key = this.resolve(plugin, true)
this.ctx.scope.assertActive()
let runtime = this._internal.get(key)
if (!runtime) {
runtime = Plugin.resolve<C>(plugin)
this._internal.set(key!, runtime)
}
const outerError = new Error()
return new EffectScope(this.ctx, config, async (ctx, config) => {
const innerError = new Error()
try {
config = resolveConfig(plugin, config)
if (typeof plugin !== 'function') {
await plugin.apply(ctx, config)
} else if (isConstructor(plugin)) {
// eslint-disable-next-line new-cap
const instance = new plugin(ctx, config)
for (const hook of instance?.[symbols.initHooks] ?? []) {
hook()
}
await instance?.[symbols.setup]?.()
} else {
await plugin(ctx, config)
}
} catch (error: any) {
const outerLines = outerError.stack!.split('\n')
const innerLines = innerError.stack!.split('\n')
// malformed error
if (typeof error?.stack !== 'string') {
outerLines[0] = `Error: ${error}`
outerError.stack = outerLines.join('\n')
throw outerError
}
// long stack trace
const lines: string[] = error.stack.split('\n')
const index = lines.indexOf(innerLines[2])
if (index === -1) throw error
lines.splice(index - 1, Infinity)
// lines.push(' at Registry.plugin (<anonymous>)')
lines.push(...outerLines.slice(2))
error.stack = lines.join('\n')
throw error
}
}, runtime)
}
调用
resolve
判断是否合法插件形式;调用
ctx.scope.assertActive()
,做一个检测;在
_internal
中根据 plugin 获取对应的 scope,无则调用Plugin.resolve
创建:export function resolve<C extends Context = Context>(plugin: Plugin<C>): Runtime<C> { let name = plugin.name if (name === 'apply') name = undefined const schema = plugin['Config'] || plugin['schema'] const inject = Inject.resolve(plugin['using'] || plugin['inject']) return { name, schema, inject, plugin, scopes: new DisposableList() } }
该对象中会除了 plugin 的基本属性,还有一个 scopes,实例化一个
DisposableList
对象。最后,实例化
EffectScope
对象,并返回;
createTraceable
实例化 EffectScope
时,会在 Registry
中调用 this.ctx
,而 Registry
被 createTraceable
处理过,包上了一层 Proxy,现在来看看其机制的作用:
function createTraceable(ctx: Context, value: any, tracker: Tracker) {
if (ctx[symbols.shadow]) {
ctx = Object.getPrototypeOf(ctx)
}
const proxy = new Proxy(value, {
get: (target, prop, receiver) => {
if (prop === symbols.original) return target
if (prop === tracker.property) return ctx
if (typeof prop === 'symbol') {
return Reflect.get(target, prop, receiver)
}
if (tracker.associate && ctx[symbols.internal][`${tracker.associate}.${prop}`]) {
return Reflect.get(
ctx,
`${tracker.associate}.${prop}`,
withProp(ctx, symbols.receiver, receiver)
)
}
let shadow: any, innerValue: any
const desc = getPropertyDescriptor(target, prop)
if (desc && 'value' in desc) {
innerValue = desc.value
} else {
shadow = createShadow(ctx, target, tracker.property, receiver)
innerValue = Reflect.get(target, prop, shadow)
}
const innerTracker = innerValue?.[symbols.tracker]
if (innerTracker) {
return createTraceable(ctx, innerValue, innerTracker)
} else if (!tracker.noShadow && typeof innerValue === 'function') {
shadow ??= createShadow(ctx, target, tracker.property, receiver)
return createShadowMethod(ctx, innerValue, receiver, shadow)
} else {
return innerValue
}
},
})
return proxy
}
对于 this.ctx
,在 get 函数的第二行即返回了 ctx;