☰
fast-element 1.x 的 ObservationRecord.propertyName:可观察属性访问记录中的属性名详解
2026/9/29 3:02:21 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】fast

The adaptive interface system for modern web experiences.

项目地址:https://gitcode.com/gh_mirrors/fa/fast
点击查看免费下载

导读

本文围绕 @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; }

该接口仅包含两个字段:

属性类型说明
propertySourceany被访问的可观察属性所在的源对象
propertyNamestring在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); } } }

关键链路如下:

  1. getter 触发上报:当绑定表达式求值(调用getValue)时,如果当前存在活动的watcher(即正在观察该表达式的ExpressionNotifierImplementation实例),就会调用watcher.watch(source, this.name),其中this.name就是属性名,随后被写入记录的propertyName。
  2. 内部字段约定:访问器把值实际存储在_${name}前缀字段中,回调方法约定为${name}Changed,因此propertyName记录的是对外暴露的属性名(如value),而非内部存储字段名(如_value)。
  3. 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); }

注意两点实现细节:

  1. 身份映射:last.propertyName与last.propertySource配合,定位"哪个对象的哪个属性"需要被赋值,等价于source[propertyName] = value,是字符串键动态赋值的关键。
  2. 依赖可观察性:若最后一条记录不存在(例如绑定目标不是可观察属性),框架会通过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.

项目地址:https://gitcode.com/gh_mirrors/fa/fast
点击查看免费下载
上一篇:GTA5线上小助手:免费终极工具完整使用指南
下一篇:如何用NBTExplorer轻松编辑Minecraft游戏数据:终极免费工具指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询