- 前端
- UI组件
【免费下载链接】fast
The adaptive interface system for modern web experiences.
导读
本文围绕 @microsoft/fast-element 1.x 响应式系统中的核心数据结构ObservationRecord及其propertyName属性展开,说明该属性在可观察属性访问跟踪机制中的定位、类型定义与内部实现,并基于仓库源码揭示它如何被用于依赖收集、订阅管理和双向绑定等场景。读完本文,你将理解 fast-element 观察系统中"属性访问记录"的完整生命周期,并掌握通过ExpressionNotifier.records()读取propertyName排查绑定依赖的实战方法。
ObservationRecord 与 propertyName 的定义
在 observable.ts 中,ObservationRecord被定义为一个公开接口,描述"一次可观察属性的访问记录":
export interface ObservationRecord { /** * The source object with an observable property that was accessed. */ propertySource: any; /** * The name of the observable property on ObservationRecord.propertySource that was accessed. */ propertyName: string; }该接口仅包含两个字段:
| 属性 | 类型 | 说明 |
|---|---|---|
propertySource | any | 被访问的可观察属性所在的源对象 |
propertyName | string | 在propertySource上被访问的那个可观察属性的名称 |
其中propertyName的类型是string(对应 API 文档 fast-element.observationrecord.propertyname.md 中propertyName: string;的签名),它并不保存属性的值,也不保存访问上下文,而是以"字符串键"的形式精确标识"哪个对象上的哪个属性被读取了"。这正是 fast-element 依赖收集(dependency tracking)的最小粒度单元:一条ObservationRecord就代表"绑定表达式求值期间访问了一个可观察属性"这一事实。
propertyName 的生成:DefaultObservableAccessor 的 watch 调用
propertyName的值来自可观察属性访问器在 getter 阶段对观察器的上报。在 observable.ts 中,DefaultObservableAccessor是Observable.defineProperty为普通属性生成的默认访问器实现:
class DefaultObservableAccessor implements Accessor { private field: string; private callback: string; constructor(public name: string) { this.field = `_${name}`; this.callback = `${name}Changed`; } getValue(source: any): any { if (watcher !== void 0) { watcher.watch(source, this.name); } return source[this.field]; } setValue(source: any, newValue: any): void { const field = this.field; const oldValue = source[field]; if (oldValue !== newValue) { source[field] = newValue; const callback = source[this.callback]; if (isFunction(callback)) { callback.call(source, oldValue, newValue); } getNotifier(source).notify(this.name); } } }关键链路如下:
- getter 触发上报:当绑定表达式求值(调用
getValue)时,如果当前存在活动的watcher(即正在观察该表达式的ExpressionNotifierImplementation实例),就会调用watcher.watch(source, this.name),其中this.name就是属性名,随后被写入记录的propertyName。 - 内部字段约定:访问器把值实际存储在
_${name}前缀字段中,回调方法约定为${name}Changed,因此propertyName记录的是对外暴露的属性名(如value),而非内部存储字段名(如_value)。 - setter 通知:属性值变更时,
getNotifier(source).notify(this.name)以属性名作为参数通知订阅者,与propertyName的语义保持一致。
propertyName 的存储:watch 方法与 SubscriptionRecord
propertyName实际写入记录的过程发生在ExpressionNotifierImplementation.watch方法中(observable.ts):
public watch(propertySource: unknown, propertyName: string): void { const prev = this.last; const notifier = getNotifier(propertySource); const current: SubscriptionRecord = prev === null ? this.first : ({} as any); current.propertySource = propertySource; current.propertyName = propertyName; current.notifier = notifier; notifier.subscribe(this, propertyName); ... }观察器内部实际维护的是SubscriptionRecord,它是ObservationRecord的扩展(observable.ts):
interface SubscriptionRecord extends ObservationRecord { notifier: Notifier; next: SubscriptionRecord | undefined; }也就是说,propertyName不仅在公开的只读记录中存在,还在观察器内部充当订阅键:notifier.subscribe(this, propertyName)以属性名注册订阅,属性变更时由 notifier 按名匹配通知对应观察器。this.first/this.last/next把这些记录串成单向链表,完整刻画了一次绑定表达式求值中访问过的全部(源对象, 属性名)依赖集合。
读取 propertyName:ExpressionNotifier.records()
对开发者而言,最常用的读取入口是ExpressionNotifier.records()(observable.ts):
public *records(): IterableIterator<ObservationRecord> { let next = this.first; while (next !== void 0) { yield next; next = next.next!; } }该方法返回IterableIterator<ObservationRecord>,按访问顺序依次 yield 每条记录,因此通过record.propertyName即可拿到绑定表达式所依赖的属性名。API 文档中该能力定义于ExpressionNotifier.records()(见 fast-element.bindingobserver.records.md 相关章节)。
实战:遍历绑定依赖的属性名
仓库测试 observable.pw.spec.ts 给出了标准的遍历用法:先定义三个带可观察属性value的对象,再创建一个同时读取三者value的绑定表达式,观察后通过records()逐条读取:
const observed = [{}, {}, {}].map((x: any, i) => { Observable.defineProperty(x, "value"); x.value = i; return x; }); function binding() { return observed[0].value + observed[1].value + observed[2].value; } const bindingObserver = Observable.binding(binding); bindingObserver.observe({}, Fake.executionContext()); for (const record of bindingObserver.records()) { // record.propertyName === "value" // record.propertySource === observed[i] if (record.propertySource !== observed[i]) { allSourcesMatch = false; } }测试断言recordCount === 3,验证了记录条数与访问的属性数一一对应;而propertyName字段则可用于检查依赖是否落在预期属性上(例如确认绑定没有意外依赖到无关属性,排查模板更新不及时的问题)。
propertyName 在双向绑定中的实际应用
propertyName不只是诊断工具,它还直接参与双向绑定(two-way binding)的数据写回。在 two-way.ts 中,当视图侧触发change事件时,TwoWayObserver.handleEvent会读取观察器内部维护的最后一条记录,并借助propertyName把视图值写回模型:
handleEvent(event: Event): void { const bindingSource = this.directive; const target = event.currentTarget as HTMLElement; const notifier = this.notifier; const last = (notifier as any).last as ObservationRecord; // using internal API!!! if (!last) { FAST.warn(Message.twoWayBindingRequiresObservables); return; } ... last.propertySource[last.propertyName] = this.dataBinding.options.fromView!(value); }注意两点实现细节:
- 身份映射:
last.propertyName与last.propertySource配合,定位"哪个对象的哪个属性"需要被赋值,等价于source[propertyName] = value,是字符串键动态赋值的关键。 - 依赖可观察性:若最后一条记录不存在(例如绑定目标不是可观察属性),框架会通过
Message.twoWayBindingRequiresObservables给出警告,提醒双向绑定必须建立在可观察属性之上。
这也印证了propertyName的职责:它既是依赖收集阶段的标识符,也是订阅与派发阶段的路由键,还是双向绑定写回阶段的赋值目标名。
与 propertySource 的关系及 v1 API 上下文
propertyName必须与propertySource成对解读才有意义:propertySource指明对象身份(支持任意类型,接口签名为any),propertyName指明该对象上的属性键(string)。二者的组合构成依赖的完整坐标(source, propertyName),因此:
- 同一
propertyName出现在不同源对象上,会被记为两条独立记录,records()会分别返回它们; - 源对象是数组时(如
repeat场景),propertySource为数组本身,propertyName则为被访问的数组索引或数组相关属性名,getNotifier会走createArrayObserver分支(见 observable.ts)。
从 API 文档结构看,ObservationRecord.propertyName属于 1.x 公开 API 面(@public),与其并列的propertySource文档见 fast-element.observationrecord.propertysource.md,接口总览见 fast-element.observationrecord.md。该接口由 index.ts 对外导出,开发者可直接以类型形式导入使用。
小结
propertyName是ObservationRecord中类型为string的属性,表示在propertySource上被访问的可观察属性的名称;- 它由
DefaultObservableAccessor.getValue在 getter 求值时通过watcher.watch(source, name)写入,并在notifier.subscribe(this, propertyName)中充当订阅键; - 通过
ExpressionNotifier.records()可遍历读取绑定依赖的所有记录,propertyName与propertySource共同构成(源对象, 属性名)依赖坐标; - 双向绑定在
handleEvent中直接以last.propertySource[last.propertyName]完成视图到模型的写回,体现了propertyName在运行时路由中的实际价值。
掌握propertyName的生成、存储与消费链路,就能清晰理解 fast-element 观察系统的依赖跟踪机制,并为诊断模板更新与绑定行为提供直接的检查抓手。
- 前端
- UI组件
【免费下载链接】fast
The adaptive interface system for modern web experiences.
相关推荐
fast-element ObservationRecord 接口解析:可观察属性访问记录在 FAST 观测系统中的作用
fast element ObservationRecord 接口解析:可观察属性访问记录在 FAST 观测系统中的作用 ObservationRecord 是
前端UI组件FAST Element 观察记录中的 propertySource:理解 Observable 属性访问的来源对象追踪机制
FAST Element 观察记录中的 propertySource:理解 Observable 属性访问的来源对象追踪机制 导读 ObservationRec
前端UI组件Paddle-Lite x86 JIT Kernel 架构解析:从函数模板到运行时生成的端侧高性能算子体系
Paddle Lite x86 JIT Kernel 架构解析:从函数模板到运行时生成的端侧高性能算子体系 Paddle Lite 的 lite/backend
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考