DeepSeek Harness深度拆解-08:如何实现在自定义的逻辑范围内的隔离通过实例演示的方式体现了@deepseek-ai/dsh-scope提供的基于Scope的隔离机制。通过提供的createScope函数,我们可用构建一个逻辑层面的Scope树,并将节点动态绑定到Context树的某个节点上,再利用scopeTarget函数提供的对象作为触发事件的主体,该对象会利用提供的过滤函数让基于某个Scope发出的事件只能在限定的Scope内传播。结合使用ScopedLayers,我们可以实现基于Scope的可继承注册表。接下来我们就来看看这一切都是如何实现的。1. ScopeKey和Scope树的构建Scope树的节点通过如下这个Scope接口表示,每个Scope通过一个ScopeKey对象作为唯一标识。ScopeKey本质上就是object类型的别名。exporttypeScopeKey=objectScope树通过如下这个类型为WeakMap<ScopeKey, ScopeKey>的全局弱引用字段来维护,每个KV表示一个具有父子关系的ScopeKey对,父子是一对多的关系,所以这里Key和Value分别是Child和Parent。constscopeParents=newWeakMap<ScopeKey,ScopeKey>()如下所示的linkScopeParent函数会根据指定的两个ScopeKey,构建一个KV并添加在scopeParents全局字典中。在添加之前会进行环路检验。我们可调用的bindScopeParent这个导出的函数,它除了调用linkScopeParent方法完成指定的这对父子关系的构建外,还利用返回对象提供的rebind方法实现针对另一个Parent的重新绑定。bindScopeParent函数返回类型被声明为ScopeParentBinding接口。exportfunctionbindScopeParent(key:ScopeKey,parent:ScopeKey):ScopeParentBinding{if(scopeParents.has(key)){thrownewError('dsh-scope: scope key is already bound to a parent; re-linking requires the binding returned by the original bind')}linkScopeParent(key,parent)return{rebind(next:ScopeKey):void{linkScopeParent(key,next)},}}functionlinkScopeParent(key:ScopeKey,parent:ScopeKey):void{for(letcursor:ScopeKey|undefined=parent;cursor!==undefined;cursor=scopeParents.get(cursor)){if(cursor===key)thrownewError('dsh-scope: scope parent link would form a cycle')}scopeParents.set(key,parent)}exportinterfaceScopeParentBinding{rebind(parent:ScopeKey):void}2. Scope的创建Scope树的节点通过如下这个Scope接口表示,它本质上是对一个Context的封装。Scope同时是一个Disposable对象,可以调用其dispose方法将其终结释放。exportinterfaceScope{ctx:ContextrawDispose:()=>Promise<void>|voiddispose():Promise<void>}定义在Scope接口的三个成员说明如下:ctx:该Scope绑定的Context;rawDispose:原始释放函数。它是真正执行清理的逻辑,可能是同步或异步。以raw作为命名前缀是因为它通常不包含框架的包装逻辑(幂等、并发控制、事件触发、子Scope清理等),一般不直接对外暴露或调用;dispose:对外释放方法,统一返回Promise<void>。Scope对象最终通过如下这个createScope函数根据指定Conext、ScopeKey和可选的CreateScopeOptions创建而成。CreateScopeOptions利用parent提供作为Parent标识的ScopeKey,这是一个可选的参数。如果利用这个配置选项提供了Parent节点,会调用上述的bindScopeParent函数建立父子关系。exportfunctioncreateScope(ctx:Context,key:ScopeKey,options?:CreateScopeOptions):Scope{if(options?.parent!==undefined)bindScopeParent(key,options.parent)constfiber=ctx.plugin(scope)constscoped:Context=fiber.ctx.extend({[kScope]:key})letdisposing:Promise<void>|undefinedreturn{ctx:scoped,rawDispose:fiber.dispose,dispose:()=>(disposing??=quiesceFiber(fiber)),}}functionscope():void{}constkScope=Symbol('dsh.scope')由于Scope同时是一个Disposable对象,需要一个生命周期管理器。在Cordis的世界里,说到管理生命周期,自然会想到Fiber。从createScope函数的定义可用看出,Scope对象也采用Fiber来管理自己的生命周期。由于Fiber随着插件的注册而创建,所以虽然不需要一个具体的插件,这里不得不调用指定Context的plugin方法注册了一个什么都没做的插件函数,其目的就是为了得到这个Fiber对象。我们知道插件是与创建的子Context绑定在一起的,这个子Context通过调用extend方法作进一步扩展,其目的是将当前Scope的Key利用kScope常量表示的Symbol附加到Context上,Socpe的ctx字段最终返回的就是这个Context。至于为创建的Scope对象定义的其他两个方法,rawDispose直接对应Fiber对象的dispose方法,dispose方法返回的Promise<void>是如下这个quiesceFiber函数的返回值,该方法在调用Fiber对象的dispose方法之余,还会利用一个循环内持续调用Fiber的inertia方法,其目的在于确保整个释放过程完全结束。asyncfunctionquiesceFiber(fiber:Fiber):Promise<void>{awaitPromise.resolve(fiber.dispose())while(fiber.inertia!==undefined)awaitfiber.inertia}3. 根据Scope过滤事件处理器DeepSeek Harness插件内核-15:再谈Cordis的事件总线对Cordis事件总线的事件分发机制进行了详细介绍,我们知道除了internal/update这种特殊的事件会将作为事件函数至于Fiber自身的_hooks字段外,其余的事件处理函数都会被封装成Hook被注册到EventsService这个单例服务对象的_hooks字典中。所以在不进行任何过滤的前提下,只要指定了正确的事件名称,不论当前身在何处,都能监听到来自当前Context树任何一个地方触发的事件。但是如果在分发事件的时候指定了一个主体对象,该对象除了会作为事件处理函数的执行上下文(this)之外,定义其中的symbols.filter方法还会用来过滤获选的Hook对象。这个过滤方法的签名(Context)=>boolean,实施过滤时将会使用Hook的ctx字段作为参数。scopeTarget函数正是利用了这一原理创建了一个对象,并利用当前Scope定义了这个过滤函数实现了事件向上传递的特性。如下面的代码所示,scopeTarget函数提供了两个参数。base参数提供的对象可以利用自身的symbols.filter方法实施前置过滤,key参数则提供一个ScopeKey表示触发事件所在的Scope。scopeTarget函数最终返回一个只包含symbols.filter方法的对象。exportfunctionscopeTarget<Textendsobject>(base:T,key:ScopeKey|undefined):Scoped<T>{constbaseFilter=(baseas{[CordisContext.filter]?:(ctx:Context)=>boolean})[CordisContext.filter]constcarrier={[CordisContext.filter](ctx:Context):boolean{if(baseFilter!==undefined&&!baseFilter.call(base,ctx))returnfalseconsttag=scopeOf(ctx)if(tag===undefined)returntruefor(letcursor=key;cursor!==undefined;cursor=scopeParents.get(cursor)){if(cursor===tag)returntrue}returnfalse},}carrierKeys.set(carrier,key)returncarrierasunknownasScoped<T>}exportfunctionscopeOf(ctx:Context):ScopeKey|undefined{return(ctxasContext&{[kScope]?:ScopeKey})[kScope]}constcarrierKeys=newWeakMap<object,ScopeKey|undefined>()返回对象的过滤方法具有这样的逻辑:如果base参数指定的对象定义了过滤方法,并且返回false,则直接返回false;调用scopeOf函数提取Context携带的Scope标识,如果不存在,意味Context并没有被限定为某个Scope,直接返回true;根据参数key指定的Scope作为起点在Scope树(通过scopeParents表示)中不断向上追溯,只要遇到一个Scope与当前Context携带的Scope一致,这意味着Hook对应的Context在触发事件源Scope的向上继承链路上,符合事件向上传递的法则,返回true;不满足以上条件,即表明Hook并不在监听的目标范围内,返回false阻止它的执行。scopeTarget函数返回的对象被视为一个携带过滤方法的对象会被添加到carrierKeys这个全局字典中,用来存储这样的对象与对应ScopeKey的映射。scopeTarget函数的返回类型被声明为Scoped<T>,定义如下。exporttypeScoped<Textendsobject>=object&{readonly[ScopedBrand]:T}declareconstScopedBrand:uniquesymbol4. 基于ScopedLayers的注册表Scope树具有两种消费形式,一种就是利用scopeTarget函数构建一个主体对象来过滤事件监听器,另一种就是利用ScopedLayers<L>构建一个具有层级继承关系的注册表。我们可以将ScopedLayers<L>视为多层注册表的集合,而作为泛型参数的L表示具体某个单层的注册表。L派生于接口ScopeLayer,后者仅仅定义了唯一的isEmpty方法来确定注册表是否为空。exportclassScopedLayers<LextendsScopeLayer>{readonlyglobal:Lprivatereadonlyscoped=newMap<ScopeKey,L>()constructor(privatereadonlycreateLayer:(scope:ScopeKey|undefined)=>L,privatereadonlyonChange:()=>void){this.global=createLayer(undefined)}}exportinterfaceScopeLayer{isEmpty():boolean}如ScopedLayers<L>的定义可知,它利用global字段返回的ScopeLayer来表示全局共享的注册表,而scoped字段返回的字典则用来表示基于某个Scope的注册表。构造函数提供如下两个参数(同时绑定为同名的字段):createLayer: 用来创建ScopeLayer的工厂函数;onChange: 注册表发生更新后的回调函数。当ScopedLayers被构造的时候,由参数createLayer指定的工厂函数会被用来创建一个ScopeLayer对象,并将其作为全局注册表赋值给global字段。如下定义的effect方法是ScopedLayers最为核心的方法,因为我们会调用此方法利用作为参数的action函数来完成具体的注册操作。除了action参数,该方法提供的ctx参数体现了注册操作针对的Scope。如果没有绑定具体的Scope,意味着直接注册到全局注册表中。该方法最终返回Fiber同名方法的调用,options参数的label最终会作为调用该方法的同名参数,notify字段表示注册成功或者解除注册时是否调用onChange函数来发送通知。exportclassScopedLayers<LextendsScopeLayer>{effect(ctx:Context,action:(layer:L)=>()=>void,options:{label:string;notify?:boolean},):()=>void{constscope=scopeOf(ctx)constnotify=options.notify??trueconstdispose=ctx.effect(function*(this:ScopedLayers<L>){letlayer:Lletcreated=falseif(scope===undefined){layer=this.global}else{constexisting=this.scoped.get(scope)if(existing===undefined){layer=this.createLayer(scope)this.scoped.set(scope,layer)created=true}else{layer=existing}}letundo:()=>voidtry{undo=action(layer)}catch(error){if(scope!==undefined&&created&&layer.isEmpty())this.scoped.delete(scope)throwerror}yield()=>{undo()if(scope!==undefined&&layer.isEmpty())this.scoped.delete(scope)if(notify)this.onChange()}if(notify)this.onChange()}.bind(this),options.label)returndispose}}如上述的代码所示,在将指定Context绑定的Scope提取出来后,方法调用Context对象effect方法(其实就是Fiber的effect方法),并利用设置的生成器函数执行如下的操作:确定操作的ScopeLayer:如果当前Context没有绑定一个具体的Scope,直接使用global字段返回的作为全局注册表的ScopeLayer对象;否则从scoped字典提出对应的ScopeLayer对象,如果不存在就调用createLayer函数创建一个新的ScopeLayer对象并添加到scoped字典中。将上面解析出来的ScopeLayer作为参数调用action函数实施注册操作,并得到返回的清理函数(比如解除注册操作就可以实现在这里);如果开启通知,则调用onChange函数;yield语句会保证执行返回的函数会:执行action函数返回的清理函数;如果ScopeLayer为空,且不是全局注册表,则将其从scoped字典中移除。ScopedLayers还定义了如下三个方法:peek:从scoped字典中提取指定Scope的ScopeLayer;chainLayers:提取指定Scope及其上级的所有ScopeLayer(不包含global),按照Scope从高到低排序;merge<V>: 将指定Scope及其上级的所有ScopeLayer(包含global)注册的条目合并成字典(Map<string, V>)输出。exportclassScopedLayers<LextendsScopeLayer>{peek(scope:ScopeKey|undefined):L|undefined{if(scope===undefined)returnundefinedreturnthis.scoped.get(scope)}chainLayers(scope:ScopeKey|undefined):L[]{constlayers:L[]=[]for(constkeyofscopeChainOf(scope).reverse()){constlayer=this.scoped.get(key)if(layer!==undefined)layers.push(layer)}returnlayers}merge<V>(scope:ScopeKey|undefined,pick:(layer:L)=>NamedEntries<V>):Map<string,V>{constmerged=newMap(pick(this.global).entries())for(constlayerofthis.chainLayers(scope)){for(const[name,value]ofpick(layer).entries())merged.set(name,value)}returnmerged}}exportfunctionscopeChainOf(key:ScopeKey|undefined):ScopeKey[]{constchain:ScopeKey[]=[]for(letcursor=key;cursor!==undefined;cursor=scopeParents.get(cursor))chain.push(cursor)returnchain}merge<V>方法除了传入标识Scope的ScopeKey之外,还需要提供一个函数将ScopeLayer转换成NamedEntries<V>。我们自定义的ScopeLayer一般使用NamedEntries<V>来存储注册的需要命名的条目。与之对应的AnonymousEntries<V>,它们都派生自EntryValues<V>接口。interfaceEntryValues<V>{values():IterableIterator<V>isEmpty():boolean}exportclassNamedEntries<V>implementsEntryValues<V>{privatedata=newMap<string,V>()constructor(privatereadonlyduplicateError:(name:string)=>Error)insert(name:string,value:V):()=>voidget(name:string):V|undefinedhas(name:string):booleankeys():IterableIterator<string>entries():IterableIterator<[string,V]>values():IterableIterator<V>isEmpty():boolean}exportclassAnonymousEntries<V>implementsEntryValues<V>{privatedata=newMap<symbol,V>()append(value:V):()=>voidvalues():IterableIterator<V>isEmpty():boolean}
DeepSeek Harness深度拆解-08:如何实现在自定义的逻辑范围内的隔离通过实例演示的方式体现了@deepseek-ai/dsh-scope提供的基于Scope的隔离机制。通过提供的createScope函数,我们可用构建一个逻辑层面的Scope树,并将节点动态绑定到Context树的某个节点上,再利用scopeTarget函数提供的对象作为触发事件的主体,该对象会利用提供的过滤函数让基于某个Scope发出的事件只能在限定的Scope内传播。结合使用ScopedLayers,我们可以实现基于Scope的可继承注册表。接下来我们就来看看这一切都是如何实现的。1. ScopeKey和Scope树的构建Scope树的节点通过如下这个Scope接口表示,每个Scope通过一个ScopeKey对象作为唯一标识。ScopeKey本质上就是object类型的别名。exporttypeScopeKey=objectScope树通过如下这个类型为WeakMap<ScopeKey, ScopeKey>的全局弱引用字段来维护,每个KV表示一个具有父子关系的ScopeKey对,父子是一对多的关系,所以这里Key和Value分别是Child和Parent。constscopeParents=newWeakMap<ScopeKey,ScopeKey>()如下所示的linkScopeParent函数会根据指定的两个ScopeKey,构建一个KV并添加在scopeParents全局字典中。在添加之前会进行环路检验。我们可调用的bindScopeParent这个导出的函数,它除了调用linkScopeParent方法完成指定的这对父子关系的构建外,还利用返回对象提供的rebind方法实现针对另一个Parent的重新绑定。bindScopeParent函数返回类型被声明为ScopeParentBinding接口。exportfunctionbindScopeParent(key:ScopeKey,parent:ScopeKey):ScopeParentBinding{if(scopeParents.has(key)){thrownewError('dsh-scope: scope key is already bound to a parent; re-linking requires the binding returned by the original bind')}linkScopeParent(key,parent)return{rebind(next:ScopeKey):void{linkScopeParent(key,next)},}}functionlinkScopeParent(key:ScopeKey,parent:ScopeKey):void{for(letcursor:ScopeKey|undefined=parent;cursor!==undefined;cursor=scopeParents.get(cursor)){if(cursor===key)thrownewError('dsh-scope: scope parent link would form a cycle')}scopeParents.set(key,parent)}exportinterfaceScopeParentBinding{rebind(parent:ScopeKey):void}2. Scope的创建Scope树的节点通过如下这个Scope接口表示,它本质上是对一个Context的封装。Scope同时是一个Disposable对象,可以调用其dispose方法将其终结释放。exportinterfaceScope{ctx:ContextrawDispose:()=>Promise<void>|voiddispose():Promise<void>}定义在Scope接口的三个成员说明如下:ctx:该Scope绑定的Context;rawDispose:原始释放函数。它是真正执行清理的逻辑,可能是同步或异步。以raw作为命名前缀是因为它通常不包含框架的包装逻辑(幂等、并发控制、事件触发、子Scope清理等),一般不直接对外暴露或调用;dispose:对外释放方法,统一返回Promise<void>。Scope对象最终通过如下这个createScope函数根据指定Conext、ScopeKey和可选的CreateScopeOptions创建而成。CreateScopeOptions利用parent提供作为Parent标识的ScopeKey,这是一个可选的参数。如果利用这个配置选项提供了Parent节点,会调用上述的bindScopeParent函数建立父子关系。exportfunctioncreateScope(ctx:Context,key:ScopeKey,options?:CreateScopeOptions):Scope{if(options?.parent!==undefined)bindScopeParent(key,options.parent)constfiber=ctx.plugin(scope)constscoped:Context=fiber.ctx.extend({[kScope]:key})letdisposing:Promise<void>|undefinedreturn{ctx:scoped,rawDispose:fiber.dispose,dispose:()=>(disposing??=quiesceFiber(fiber)),}}functionscope():void{}constkScope=Symbol('dsh.scope')由于Scope同时是一个Disposable对象,需要一个生命周期管理器。在Cordis的世界里,说到管理生命周期,自然会想到Fiber。从createScope函数的定义可用看出,Scope对象也采用Fiber来管理自己的生命周期。由于Fiber随着插件的注册而创建,所以虽然不需要一个具体的插件,这里不得不调用指定Context的plugin方法注册了一个什么都没做的插件函数,其目的就是为了得到这个Fiber对象。我们知道插件是与创建的子Context绑定在一起的,这个子Context通过调用extend方法作进一步扩展,其目的是将当前Scope的Key利用kScope常量表示的Symbol附加到Context上,Socpe的ctx字段最终返回的就是这个Context。至于为创建的Scope对象定义的其他两个方法,rawDispose直接对应Fiber对象的dispose方法,dispose方法返回的Promise<void>是如下这个quiesceFiber函数的返回值,该方法在调用Fiber对象的dispose方法之余,还会利用一个循环内持续调用Fiber的inertia方法,其目的在于确保整个释放过程完全结束。asyncfunctionquiesceFiber(fiber:Fiber):Promise<void>{awaitPromise.resolve(fiber.dispose())while(fiber.inertia!==undefined)awaitfiber.inertia}3. 根据Scope过滤事件处理器DeepSeek Harness插件内核-15:再谈Cordis的事件总线对Cordis事件总线的事件分发机制进行了详细介绍,我们知道除了internal/update这种特殊的事件会将作为事件函数至于Fiber自身的_hooks字段外,其余的事件处理函数都会被封装成Hook被注册到EventsService这个单例服务对象的_hooks字典中。所以在不进行任何过滤的前提下,只要指定了正确的事件名称,不论当前身在何处,都能监听到来自当前Context树任何一个地方触发的事件。但是如果在分发事件的时候指定了一个主体对象,该对象除了会作为事件处理函数的执行上下文(this)之外,定义其中的symbols.filter方法还会用来过滤获选的Hook对象。这个过滤方法的签名(Context)=>boolean,实施过滤时将会使用Hook的ctx字段作为参数。scopeTarget函数正是利用了这一原理创建了一个对象,并利用当前Scope定义了这个过滤函数实现了事件向上传递的特性。如下面的代码所示,scopeTarget函数提供了两个参数。base参数提供的对象可以利用自身的symbols.filter方法实施前置过滤,key参数则提供一个ScopeKey表示触发事件所在的Scope。scopeTarget函数最终返回一个只包含symbols.filter方法的对象。exportfunctionscopeTarget<Textendsobject>(base:T,key:ScopeKey|undefined):Scoped<T>{constbaseFilter=(baseas{[CordisContext.filter]?:(ctx:Context)=>boolean})[CordisContext.filter]constcarrier={[CordisContext.filter](ctx:Context):boolean{if(baseFilter!==undefined&&!baseFilter.call(base,ctx))returnfalseconsttag=scopeOf(ctx)if(tag===undefined)returntruefor(letcursor=key;cursor!==undefined;cursor=scopeParents.get(cursor)){if(cursor===tag)returntrue}returnfalse},}carrierKeys.set(carrier,key)returncarrierasunknownasScoped<T>}exportfunctionscopeOf(ctx:Context):ScopeKey|undefined{return(ctxasContext&{[kScope]?:ScopeKey})[kScope]}constcarrierKeys=newWeakMap<object,ScopeKey|undefined>()返回对象的过滤方法具有这样的逻辑:如果base参数指定的对象定义了过滤方法,并且返回false,则直接返回false;调用scopeOf函数提取Context携带的Scope标识,如果不存在,意味Context并没有被限定为某个Scope,直接返回true;根据参数key指定的Scope作为起点在Scope树(通过scopeParents表示)中不断向上追溯,只要遇到一个Scope与当前Context携带的Scope一致,这意味着Hook对应的Context在触发事件源Scope的向上继承链路上,符合事件向上传递的法则,返回true;不满足以上条件,即表明Hook并不在监听的目标范围内,返回false阻止它的执行。scopeTarget函数返回的对象被视为一个携带过滤方法的对象会被添加到carrierKeys这个全局字典中,用来存储这样的对象与对应ScopeKey的映射。scopeTarget函数的返回类型被声明为Scoped<T>,定义如下。exporttypeScoped<Textendsobject>=object&{readonly[ScopedBrand]:T}declareconstScopedBrand:uniquesymbol4. 基于ScopedLayers的注册表Scope树具有两种消费形式,一种就是利用scopeTarget函数构建一个主体对象来过滤事件监听器,另一种就是利用ScopedLayers<L>构建一个具有层级继承关系的注册表。我们可以将ScopedLayers<L>视为多层注册表的集合,而作为泛型参数的L表示具体某个单层的注册表。L派生于接口ScopeLayer,后者仅仅定义了唯一的isEmpty方法来确定注册表是否为空。exportclassScopedLayers<LextendsScopeLayer>{readonlyglobal:Lprivatereadonlyscoped=newMap<ScopeKey,L>()constructor(privatereadonlycreateLayer:(scope:ScopeKey|undefined)=>L,privatereadonlyonChange:()=>void){this.global=createLayer(undefined)}}exportinterfaceScopeLayer{isEmpty():boolean}如ScopedLayers<L>的定义可知,它利用global字段返回的ScopeLayer来表示全局共享的注册表,而scoped字段返回的字典则用来表示基于某个Scope的注册表。构造函数提供如下两个参数(同时绑定为同名的字段):createLayer: 用来创建ScopeLayer的工厂函数;onChange: 注册表发生更新后的回调函数。当ScopedLayers被构造的时候,由参数createLayer指定的工厂函数会被用来创建一个ScopeLayer对象,并将其作为全局注册表赋值给global字段。如下定义的effect方法是ScopedLayers最为核心的方法,因为我们会调用此方法利用作为参数的action函数来完成具体的注册操作。除了action参数,该方法提供的ctx参数体现了注册操作针对的Scope。如果没有绑定具体的Scope,意味着直接注册到全局注册表中。该方法最终返回Fiber同名方法的调用,options参数的label最终会作为调用该方法的同名参数,notify字段表示注册成功或者解除注册时是否调用onChange函数来发送通知。exportclassScopedLayers<LextendsScopeLayer>{effect(ctx:Context,action:(layer:L)=>()=>void,options:{label:string;notify?:boolean},):()=>void{constscope=scopeOf(ctx)constnotify=options.notify??trueconstdispose=ctx.effect(function*(this:ScopedLayers<L>){letlayer:Lletcreated=falseif(scope===undefined){layer=this.global}else{constexisting=this.scoped.get(scope)if(existing===undefined){layer=this.createLayer(scope)this.scoped.set(scope,layer)created=true}else{layer=existing}}letundo:()=>voidtry{undo=action(layer)}catch(error){if(scope!==undefined&&created&&layer.isEmpty())this.scoped.delete(scope)throwerror}yield()=>{undo()if(scope!==undefined&&layer.isEmpty())this.scoped.delete(scope)if(notify)this.onChange()}if(notify)this.onChange()}.bind(this),options.label)returndispose}}如上述的代码所示,在将指定Context绑定的Scope提取出来后,方法调用Context对象effect方法(其实就是Fiber的effect方法),并利用设置的生成器函数执行如下的操作:确定操作的ScopeLayer:如果当前Context没有绑定一个具体的Scope,直接使用global字段返回的作为全局注册表的ScopeLayer对象;否则从scoped字典提出对应的ScopeLayer对象,如果不存在就调用createLayer函数创建一个新的ScopeLayer对象并添加到scoped字典中。将上面解析出来的ScopeLayer作为参数调用action函数实施注册操作,并得到返回的清理函数(比如解除注册操作就可以实现在这里);如果开启通知,则调用onChange函数;yield语句会保证执行返回的函数会:执行action函数返回的清理函数;如果ScopeLayer为空,且不是全局注册表,则将其从scoped字典中移除。ScopedLayers还定义了如下三个方法:peek:从scoped字典中提取指定Scope的ScopeLayer;chainLayers:提取指定Scope及其上级的所有ScopeLayer(不包含global),按照Scope从高到低排序;merge<V>: 将指定Scope及其上级的所有ScopeLayer(包含global)注册的条目合并成字典(Map<string, V>)输出。exportclassScopedLayers<LextendsScopeLayer>{peek(scope:ScopeKey|undefined):L|undefined{if(scope===undefined)returnundefinedreturnthis.scoped.get(scope)}chainLayers(scope:ScopeKey|undefined):L[]{constlayers:L[]=[]for(constkeyofscopeChainOf(scope).reverse()){constlayer=this.scoped.get(key)if(layer!==undefined)layers.push(layer)}returnlayers}merge<V>(scope:ScopeKey|undefined,pick:(layer:L)=>NamedEntries<V>):Map<string,V>{constmerged=newMap(pick(this.global).entries())for(constlayerofthis.chainLayers(scope)){for(const[name,value]ofpick(layer).entries())merged.set(name,value)}returnmerged}}exportfunctionscopeChainOf(key:ScopeKey|undefined):ScopeKey[]{constchain:ScopeKey[]=[]for(letcursor=key;cursor!==undefined;cursor=scopeParents.get(cursor))chain.push(cursor)returnchain}merge<V>方法除了传入标识Scope的ScopeKey之外,还需要提供一个函数将ScopeLayer转换成NamedEntries<V>。我们自定义的ScopeLayer一般使用NamedEntries<V>来存储注册的需要命名的条目。与之对应的AnonymousEntries<V>,它们都派生自EntryValues<V>接口。interfaceEntryValues<V>{values():IterableIterator<V>isEmpty():boolean}exportclassNamedEntries<V>implementsEntryValues<V>{privatedata=newMap<string,V>()constructor(privatereadonlyduplicateError:(name:string)=>Error)insert(name:string,value:V):()=>voidget(name:string):V|undefinedhas(name:string):booleankeys():IterableIterator<string>entries():IterableIterator<[string,V]>values():IterableIterator<V>isEmpty():boolean}exportclassAnonymousEntries<V>implementsEntryValues<V>{privatedata=newMap<symbol,V>()append(value:V):()=>voidvalues():IterableIterator<V>isEmpty():boolean}