uni-app UTS 可迭代协议(Iterable)全解析:从 for..of 到 UTSValueIterable 自定义迭代
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
UTS 是 uni-app 在 App 端使用的、可编译到 Android(Kotlin)与 iOS(Swift)的类 TypeScript 语言。本文围绕 docs/uts/iterable.md 展开,系统讲解 UTS 中"可迭代协议(Iterable)"的完整能力:HBuilderX 4.41 之后 for..of 如何实现全端行为与 Web 标准一致、内置对象(Array/Set/Map/String)的遍历规则,以及如何通过UTSValueIterable接口为自定义 Class 定制迭代逻辑。读完本文,你将能够在 UTS 中正确使用 for..of、规避跨端遍历陷阱,并为业务对象实现可复用的自定义迭代器。
一、背景:HBuilderX 4.41 是 UTS 可迭代能力的分水岭
在 HBuilderX 4.41 版本之前,UTS 并未对for..of做特殊处理:开发者在 UTS 代码中调用for..of会直接编译报错,无法使用。
从 HBuilderX 4.41 开始,UTS 语言正式支持可迭代协议,并以 Web 标准的可迭代协议为基准,保证了for..of行为在 Android、iOS、Web 等全端的一致,同时开放了可迭代协议的扩展能力。也就是说,在 HBuilderX 4.41 之后的版本中,UTS 内置的for..of语法执行效果与 Web 标准一致。
一个最直观的例子:对混合类型数组执行for..of遍历——
let a1 = [111,"222",false] for(perItem of a1){ console.log(perItem) }这段代码在 HBuilderX 4.41 之前的 Android / iOS 端会编译报错;而在 HBuilderX 4.41 之后,全端执行效果统一为:
111 "222" false可迭代协议最直接的应用场景就是for..of运算符,UTS 中for..of的完整语法说明详见 docs/uts/loops.md#forof。
二、内置对象的 for..of 支持规则
在 UTS 中,for..of循环按顺序逐个处理从可迭代对象获取的值,每次迭代都会执行可能引用当前序列值的语句。根据 docs/uts/loops.md#forof 的说明,HBuilderX 4.41 之后内置对象对for..of的支持情况如下。
Set / Array:遍历元素值
类似 Set、Array 这样的一维数据容器,会遍历内部属性值(即元素本身):
let u1 = new Set<any>() u1.add("111") u1.add("222") for (item of u1) { console.log("item",item) }执行结果:
> item 111 > item 222Map:遍历为键值对数组
Map 的遍历会将其 key、value 封装为一个新的 Array 数组:
let u1 = new Map<string,any>() u1.put("aaa","111") u1.put("bbb","222") for (item of u1) { console.log("item",item) }执行结果:
> [Array] [ "aaa", "111" ] > [Array] [ "bbb", "222" ]String:逐字符遍历
遍历 String 的每一个字符:
let a = "123456" for(item of a ){ console.log(item) }执行结果:
> "1" > "2" > "3" > "4" > "5" > "6"不支持的遍历对象
number、Date、RegExp、Error、UTSJSONObject、自定义的 type 类型、自定义 class,均不支持for..of,直接使用会编译报错:
For-loop range must have an 'iterator()' method需要特别说明的是,一般情况下的自定义 type 类型 / 自定义 class 直接使用for..of会报错;但有些场景需要赋予自定义 class 循环访问自身的能力,甚至定制迭代访问的内容——这正是下一节UTSValueIterable接口要解决的问题。
补充:除了
for..of,部分内置对象还支持forEach方法以更简单地遍历,可参见 docs/uts/buildin-object-api/array.md#foreach 与 docs/uts/buildin-object-api/set.md#foreach。
三、定制可迭代对象:UTSValueIterable 接口
在 Web 开发中,可迭代协议并不仅仅是简单对应for..of语法,更是对遍历对象内部元素的一种约定。UTS 同样支持了这种能力。
在 HBuilderX 4.41 之后的版本中,UTS 语言内置了UTSValueIterable接口,用来支持自定义对象的可迭代规则。当开发者手动实现UTSValueIterable接口时,就可以定制其实例对象在for..of运算下的规则。
3.1 默认不可迭代:先看报错
首先看一个默认场景:下面的 Class 实例并不支持for..of语法:
class TestClass { holderArray: (any | null)[] = [11, 22, null, 33, 44, null] } let test = new TestClass() for (item of test) { console.log("item",item) }如果直接运行,会触发编译报错:
error: For-loop range must have an 'iterator()' method
3.2 实现 UTSValueIterable 接口
为了让对象支持for..of,可以让 Class 实现UTSValueIterable接口,并补全唯一的待实现函数valueIterator:
class TestClass implements UTSValueIterable<any | null> { holderArray: (any | null)[] = [11, 22, null, 33, 44, null] valueIterator(): UTSIterator<any | null> { let holderIndex = 0; let obj: UTSIterator<any | null> = { next: () : UTSIteratorResult<any | null> => { const done = holderIndex == this.holderArray.length return { done, value: done ? null : this.holderArray[holderIndex++], } as UTSIteratorResult<any | null> } } return obj } }执行结果(注意 null 值也会被遍历出来):
item [number] 11 item [number] 22 item null item [number] 33 item [number] 44 item null3.3 三个关键类型:UTSValueIterable / UTSIterator / UTSIteratorResult
理解这段代码需要掌握三个核心类型:
UTSValueIterable:自定义可迭代对象需要实现的接口,它仅存在一个待实现函数valueIterator。UTSIterator:valueIterator函数的返回值类型,与 Web 中的 Iterator 类似,开发者需要关心其中next函数的实现。UTSIteratorResult:next函数返回的数据结构,包含done与value两个字段。
与 Web 的迭代器协议一样,该数据结构中的done决定了是否可以继续迭代(true表示迭代结束),value对应当前迭代返回的具体数值。当done为true时,示例代码中返回的value为null,该值不会对遍历结果产生实际影响。
3.4 实战:过滤空值的自定义迭代
如果我们希望使用者在for..of时仅能遍历出非空的数值,可以在valueIterator内部先对数据进行过滤:
class TestClass implements UTSValueIterable<any | null> { holderArray: (any | null)[] = [11, 22, null, 33, 44, null] valueIterator(): UTSIterator<any | null> { let holderIndex = 0; let arr = this.holderArray.filter((value) => { return value != null }) let obj: UTSIterator<any | null> = { next: () : UTSIteratorResult<any | null> => { const done = holderIndex == arr.length return { done, value: done ? null : arr[holderIndex++], } as UTSIteratorResult<any | null> } } return obj } }执行结果(两个null被过滤掉):
item [number] 11 item [number] 22 item [number] 33 item [number] 44从源码结构看,next函数内部采用闭包变量holderIndex记录迭代游标,配合done判断是否到达集合末尾,这是实现一个"有状态迭代器"的通用模式:迭代器不依赖外部重置,每次调用next自动推进游标。
四、源码佐证:仓库中的迭代器测试用例
上述文档示例并非孤立存在。在 examples/hello-uts/uni_modules/uts-tests/utssdk/iterator.uts 中,仓库提供了对应的自动化测试用例,完整覆盖了UTSValueIterable的多种实现形态:
TestChild:实现UTSValueIterable<any | null>,同时定义了ignoredKeys()方法(被注释的UTSKeyIterable相关代码表明该测试文件曾探索过按键迭代的能力)。TestClass:holderArray全部为[null, null],经过过滤后遍历结果应为空数组。TestClass2:过滤空值后遍历结果为[11, 22, 33, 44]。TestClass3:不做过滤,遍历结果为[11, 22, null, 33, 44, null]。
测试函数testIterator()使用describe/test/expect断言框架,验证了以下行为(对应test('for...of')用例):
// 数组遍历 let arr = ["a", "b", "c", "d"] for (let item of arr) { result1.push(item) } expect(result1).toEqual(["a", "b", "c", "d"]) // Map 遍历为键值对数组(APP-ANDROID 下) expect(result2).toEqual([["a", 1], ["b", 2], ["c", 3]]) // Set 遍历元素值 expect(result3).toEqual(["a", "b", "c"]) // 实现 UTSValueIterable 的类:按 valueIterator 规则遍历 expect(result4).toEqual([11, 22, null, 33, 44, null]) expect(forofRet).toEqual([]) // 全 null 过滤后为空 expect(forofRet2).toEqual([11, 22, 33, 44]) // 过滤 null expect(forofRet3).toEqual([11, 22, null, 33, 44, null]) // 不过滤 null这份测试用例是理解可迭代协议行为的最佳"可运行证据":文档描述的每一条遍历规则,都可以在这里找到对应的断言验证,开发者在实现自定义迭代器后也可以参考该文件组织自己的单元测试。
五、可迭代与可枚举:两种遍历协议的区别与选择
5.1 概念差异
- 可枚举(for...in):ECMAScript 1999 开始支持,主要用于遍历对象的属性,迭代的是对象的所有可枚举属性(包括原型链中的属性)。
- 可迭代(for...of):ECMAScript 2015 开始支持,主要用于遍历可迭代的数据结构(如数组、字符串、Set、Map 等),迭代的是集合中每一个元素的值,不会遍历对象的属性。
UTS 语言目前不支持对可枚举协议进行自定义(详见 docs/uts/enumerability.md),而可迭代协议则可以通过UTSValueIterable完全自定义。
5.2 各平台支持矩阵
在原生开发场景下,Kotlin 和 Swift 均只支持for..in语法、不支持for..of语法;但原生for..in的用法与 Web 存在差异,实际上对应的是 Web 中的可迭代概念。完整的支持情况见 docs/uts/enumerability_iterable.md:
各平台支持信息:
| 平台 | 可迭代 | 可枚举 |
|---|---|---|
| web | 支持(ECMAScript-2015) | 支持(ECMAScript-1999) |
| kotlin | 支持 | 不支持 |
| swift | 支持 | 不支持 |
各平台实现关键字:
| 平台 | 可迭代 | 可枚举 |
|---|---|---|
| web | for..of | for..in |
| kotlin | for..in | 不支持 |
| swift | for..in | 不支持 |
5.3 for..in 在平台间的行为差异(典型陷阱)
看下面这段代码:
let a1 = [111,"222",false] for(perItem in a1){ console.log("perItem",perItem) }- Web 执行结果(遍历的是下标):
"perItem" "0" "perItem" "1" "perItem" "2"- Android 执行结果(遍历的是元素值):
perItem [number] 111 perItem 222 perItem [boolean] false这正是 docs/uts/loops.md#forin 中特别提醒的坑:数组遍历不推荐使用for..in,因为 TypeScript/Web 中for..in遍历的是数组下标,而 Swift 和 Kotlin 中遍历的是数组元素,存在行为不一致。UTS 内置对象对for..in的支持差异汇总如下:
| 类型 | Web | Android | ios |
|---|---|---|---|
| Array | 返回索引 | 返回数值 | 返回数值 |
| Set | 不可枚举 | 返回数值 | 返回数值 |
| Map | 不可枚举 | 返回键值对 | 返回键值对 |
| String | 返回索引 | 返回字符 | 返回字符 |
| 自定义 type | 返回属性名 | 返回属性名 | 返回属性名 |
| UTSJSONObject | 返回属性名 | 返回属性名 | 返回属性名 |
5.4 官方使用建议
对于可枚举协议(for..in),出于历史代码兼容性原因,HBuilderX 4.41 之后与之前版本保持一致,各平台的运行依然存在差异。因此:
- 针对 Array/Set/Map 等集合类型以及 String 的遍历,建议使用 for...of,不建议使用 for...in,以保持与 Web 行为的一致性;
- 单从访问对象内部属性的角度,可迭代是一种更现代化的语法,官方建议开发者通过
for..of实现对对象内部属性的遍历; - 当内置集合无法满足需求(如需要过滤、转换、合并多个数据源后遍历)时,使用
UTSValueIterable自定义迭代规则。
六、小结
UTS 的可迭代能力围绕一条主线展开:以 Web 标准为基准,统一全端 for..of 行为,并开放自定义扩展。开发者只需记住三个要点:
- 内置集合(Array/Set/Map/String)在 HBuilderX 4.41 之后可直接使用
for..of,Map 遍历得到的是键值对数组; - 自定义 Class通过实现
UTSValueIterable接口并实现valueIterator返回UTSIterator,即可定制for..of的遍历规则,next返回的UTSIteratorResult中done控制终止、value控制取值; - 规避 for..in:由于 Web 与 Kotlin/Swift 对
for..in的解释存在根本差异,集合与字符串遍历应统一走for..of。
仓库中的 iterator.uts 测试用例 是验证上述全部行为的最佳参照,配合 loops.md、enumerability_iterable.md、enumerability.md 三篇文档,可以完整掌握 UTS 遍历体系的正确用法。
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考