【HarmonyOS NEXT】ArkTs 数据类型解析与使用
1. 背景
为什么设计 ArkTS?
1.1 其它语言有版权【Java?Kotlin?】以及历史问题【Java 内存?】
1.2 生态,可复用前端生态的三方库,兼容 JS/TS 语言生态
ArkTs 解决了 JS/TS 中的哪些问题?
2.1 程序健壮性: JS 是动态类型【运行期间才去做数据类型检查,且类型可以随便改变】,不利于程序的健壮性。
2.2 性能问题: TS 虽然是静态类型,但是它的类型检查可配置可关闭,而且编译后类型信息丢失,会增加运行的时编译和执行字节码耗时。ArkCompiler利用 ArkTS 的静态类型信息,进行类型推导并生成对象描述和内联缓存,**加速运行时对字节码的解释执行;**AOT(Ahead-of-Time)Compiler 利用静态类型信息直接将字节码编译生成优化机器码,让应用启动即可运行高性能代码,提升应用启动和运行性能。
2.3 JS 运行流程: 解析源码>编译字节码>执行字节码>获取 Profile 信息>编译优化机器码>执行优化机器码
2.4 ArkTS 编译流程: 解析源码>编译字节码>获取 Profile 信息>编译优化机器码>打包字节码和优化字节码
2.5 ArkTS 运行流程: 执行优化机器码>执行字节码
2.6 安全: ArkCompiler 会把 ArkTS/TS/JS 编译为方舟字节码,运行时直接运行方舟字节码。并且 ArkCompiler 使用多种混淆技术提供更高强度的混淆与保护,使得 HarmonyOS 应用包中装载的是多重混淆后的字节码,有效提高了应用代码安全的强度。
2.7 并发: JS/TS 中并发的 API 不够简洁,而且支撑不了复杂业务的开发场景,而 ArkTS 中的 TaskPool 会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容),可支撑各种业务场景
2. 前言
无论是 Android 还是 iOS 开发,都提供了多种数据类型用于常见的业务开发,和 Java/Objective-C 一样 ArkTs 也有很多和 Java/Objective-C 中相同的数据类型,当然也有不同的。
ArkTS 和 JS\TS 之间的关系
关于 ES 是什么,可以看我这边整理的一篇文章 Javascript[ECMAScript] ES6、ES7、ES8、ES9、ES10、ES11、ES12、ES13、ES14[2023]新特性
ArkTS 是TypeScript的超集,其数据类型也是基于TypeScript而来,除了原始 5 种数据类型之外,还有一些 ECMAScript 中的新类型,以及包含常见的枚举、任意类型等等,大概有十多种,但常用的就那么几种。
数据类型汇总如下:
为了保证开发正确性和性能,ArkTS 中取消了 JS 中的 symbol 类型,以及 TS 中的unknown 和any类型
3. 类型声明
3.1 变量声明
以关键字 let 开头的声明引入变量,该变量在程序执行期间可以具有不同的值。【注意,这里不能使用 JS/TS 中的 var,因为 let 关键字可以在块级作用域中声明变量,帮助程序员避免错误。因此,ArkTS 不支持 var,请使用 let 声明变量。】
这里的 let 类似于 kotlin 中的 var
3.2 常量声明
以关键字 const 开头的声明引入只读常量,该常量只能被赋值一次。对常量重新赋值会造成编译时错误。
这里的 const 类似于 kotlin 中的 val
3.3 自动类型推断
由于 ArkTS 是一种静态类型语言,所有数据的类型都必须在编译时确定。但是,如果一个变量或常量的声明包含了初始值,那么开发者就不需要显式指定其类型。ArkTS 规范中列举了所有允许自动推断类型的场景。
以下示例中,两条声明语句都是有效的,两个变量都是 string 类型
4. 基础数据类型
JavaScript 中存在两套类型系统分别为原生类型(Base types)和对象类型(Object types)【类似于 Java 中装箱和拆箱】,ArkTS 也继承了这一特性。对象类型和原生类型相比,会提供一些额外的方法,例如 string 和 String
4.1 Number 类型
凡是表示数值的,不管是二进制还是八进制,还是其他进制,长整数,小数,负数等等,只有一个类型表示,那就是 number。局部声明须带关键字
4.1.1 原生 number
4.1.2 对象 Number
4.1.3 上限
在 ArkTS 中,Number 类型的上限由 JavaScript 的 Number 类型的上限决定,因为 ArkTS 的 Number 类型是基于 JavaScript 的 Number 类型。
JavaScript 中的 Number 类型是基于 IEEE 754 标准的双精度浮点数表示,它的上限由标准规定为 1.7976931348623157e+308。这意味着 JavaScript 中的 Number 类型可以表示的最大值是约 1.8 x 10^308。
【必看】注意注意:
因为 ArkTS 的 Number 类型是基于 JavaScript 的 Number 类型。那么在 JS 中著名精度丢失问题,在 ArkTS 中也依旧存在,这里想起一张图。。。此处先只解决图里面精度丢失的问题,其它问题后续会出详细的文档来说明和解决。
**解决办法:**可以用成熟的三方库来解决:https://ohpm.openharmony.cn/#/cn/detail/bignumber.js
【鸿蒙版的 bigNumber】
**问题原因:**可看这个https://juejin.cn/post/7216917459009536060
4.3 String 类型
string 代表字符序列;可以使用转义字符来表示字符。
字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。
4.4 Object 类型
Object 类型是所有引用类型的基类型。任何值,包括基本类型的值(它们会被自动装箱),都可以直接被赋给 Object 类型的变量。
在 ArkTs 中,不管你是一个普通的对象,还是一个数组,元组,集合等等,都是一个对象类型。
4.5 Array 类型
array,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。数组可由数组复合字面量(即用方括号括起来的零个或多个表达式的列表,其中每个表达式为数组中的一个元素)来赋值。数组的长度由数组中元素的个数来确定。数组中第一个元素的索引为 0。数组有两种声明方式,一种是使用 Array 对象,一种直接使用中括号[]。
4.6 Tuple 类型
Tuple Type(中文翻译:元组类型),可以认为是一个有顺序的数组类型。有以下特点:
可以明确知道包含了多少元素(这里的元素是类型)
可以明确知道每个类型所在的位置
长度固定,元组类型的变量需要为每一个位置定义对应类型的值
4.7 Enum 类型
enum 类型,又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。使用枚举常量时必须以枚举类型名称为前缀。默认情况下,从 0 开始为元素编号。当然也可以更改默认的值。如下红绿蓝
常量表达式可以用于显式设置枚举常量的值。
4.8 Union 类型
union 类型,即联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。
4.9 Undefined 类型
undefined 类型只包含一个值 undefined,表示未定义(即还未给出定义,以后可能会有定义)。
4.10 Null 类型
null 类型也只包含一个值 null,表示为空(即此处没有值)。
4.11 Aliases 类型[类型别名]
Aliases 类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。
4.12 BigInt 类型
Number 类型表示的是双精度浮点数,在处理大数时可能会出现精度丢失的问题。为了解决这个问题,ES11(2020)推出了BitInt类型,它可以表示大于 2^53 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。
可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()(但不包含 new 运算符)并传递一个整数值或字符串值。
4.13 Void 类型
void 类型用于指定函数没有返回值。
此类型只有一个值,同样是 void。由于 void 是引用类型,因此它可以用于泛型类型参数。
声明一个 void 类型的变量没有什么大用,因为在 ArkTS 你只能为它赋予 undefined
4.14 Never 类型
never 类型表示的是那些永不存在的值的类型。 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never 类型,当它们被永不为真的类型保护所约束时。
5. 运算符、语句
ArkTS 中的运算符和语句和主流的语言类似,这里不再赘述,详情见:运算符、语句
6. 集合类型
6.1 Array
用于存储有序的一组相同类型的元素。
6.1.1 基本 API
concat() 用于合并两个或多个数组。
slice() 返回一个新数组,包含原数组中指定范围内的元素。
splice() 用于添加或删除数组中的元素。
push() 将一个或多个元素添加到数组的末尾,并返回新的长度。
pop() 从数组中删除最后一个元素,并返回该元素的值。
shift() 从数组中删除第一个元素,并返回该元素的值。
unshift() 将一个或多个元素添加到数组的开头,并返回新的长度。
reverse() 颠倒数组中元素的顺序。
sort() 对数组元素进行排序。
map() 创建一个新数组,其结果是该数组中的每个元素都调用了提供的函数后的返回值。
filter() 创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
reduce() 将数组元素计算为一个值(从左到右)。
forEach() 对数组的每个元素执行一次提供的函数。
indexOf() 返回在数组中可以找到给定元素的第一个索引,如果不存在则返回 -1。
lastIndexOf() 返回指定元素在数组中的最后一个索引,如果不存在则返回 -1。
includes() 判断数组是否包含指定的值。
join() 将数组中的所有元素连接成一个字符串。
entries() 返回一个数组迭代器对象,该对象包含数组的键值对。
find() 返回满足提供的测试函数的第一个元素的值。
findIndex() 返回满足提供的测试函数的第一个元素的索引。
.............常用的例子可以参考这篇文章:JavaScript Array 奇技淫巧
6.2 ArrayList
ArkTS 独有,参考 Java 实现的,使用前需要导包
ArrayList 是一种线性数据结构,底层基于数组实现。ArrayList 会根据实际需要动态调整容量,每次扩容增加 50%。
ArrayList 和Vector相似,都是基于数组实现。它们都可以动态调整容量,但 Vector 每次扩容增加 1 倍。
ArrayList 和 LinkedList 相比,ArrayList 的随机访问效率更高。但由于 ArrayList 的增删操作会影响数组内其他元素的移动,LinkedList的增加和删除操作效率更高。
推荐使用场景: 当需要频繁读取集合中的元素时,推荐使用 ArrayList。
6.2.1 常见 API
和 Java 类似,具体见 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V1/js-apis-arraylist-0000001630146253-V1
6.3 Map
ES6 之前一般用 Object 来实现键/值存储。Map 是 ES6 新增特性,是一种新的集合类型,为这门语言带来了真正的键/值存储机制。ArkTS 中也可使用该类型。
6.3.1 基本 API
创建映射,new Map(),Map 构造函数也可接收一个可选的可迭代对象
size,使用 Map 实例的 size 属性获取映射中键值对的数量
set(),使用 Map 实例的 set() 方法可以向应设置再添加键/值对,支持链式调用,因为 set() 方法返回 Map 实例
has(),查询映射是否存在指定键
get(),从映射中获取指定键的值
delete(),从映射中删除指定的键/值对
clear(),清除映射中的所有键/值对
6.4 Record
泛型 Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值
使用场景:
如果你需要表示一个具有固定结构的数据,例如一个学生的记录,其中包含姓名、年龄、成绩等字段,那么使用 Record 是合适的选择。
如果你需要表示一个动态的、可变的数据集合,并且需要根据键来查找、插入或删除值,那么使用 Map 是更好的选择。例如,你可以使用 Map 来存储用户的偏好设置,其中键是设置的名称,值是设置的值。
6.4.1 常见 API
6.5 HashMap
ArkTS 独有,参考 Java 实现的,使用前需要导包
HashMap 底层使用数组+链表+红黑树的方式实现,查询、插入和删除的效率都很高。HashMap 存储内容基于 key-value 的键值对映射,不能有重复的 key,且一个 key 只能对应一个 value。
HashMap 和TreeMap相比,HashMap 依据键的 hashCode 存取数据,访问速度较快。而 TreeMap 是有序存取,效率较低。
HashSet基于 HashMap 实现。HashMap 的输入参数由 key、value 两个值组成。在 HashSet 中,只对 value 对象进行处理。
推荐使用场景: 需要快速存取、删除以及插入键值对数据时,推荐使用 HashMap。
6.5.1 常见 API
和 Java 类似,具体见 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/nonlinear-container-V5#hashset
6.6 WeakMap
WeakMap(弱映射)是 ECMAScript 6 新增的集合类型,它是一种增强的键值对存储机制。是 Map 的“兄弟”类型,其 API 也是 Map 的子集。WeakMap 中的 “weak(弱)” 描述的是 JavaScript 垃圾回收程序对待“弱映射”中的键的处理方式。
6.6.1 弱键
WeakMap 中的 "weak" 表示弱映射中的键是 “弱弱的拿着”。意思就是说,这些键不属于正式的引用,不会组织垃圾回收。当引用释放后对应的键值很快就会被垃圾回收。这点和 Map 不同,Map 中的对应项会编程无法直接访问的数据。
6.6.2 API
WeakMap 的 API 是 Map 的一个子集,除了以下内容之外 和 Map 没有任何区别:
WeakMap 的键必须是引用类型,如果提供非引用的类型的键会导致整个初始化失败
WeakMap 不可迭代,因为 WeakMap 中的键值对在任何时候都可能被销毁,所以没必要提供迭代能力。当然,clear() 也不存在。因为不可迭代,所以如果没有键的引用,则无法从弱映射中取的对应的值,即便代码可以访问 WeakMap 实例,也没办法取得其中的内容(Map 可以通过 迭代的方式查看)。
set(),使用 Map 实例的 set() 方法可以向应设置再添加键/值对,支持链式调用,因为 set() 方法返回 Map 实例
has(),查询映射是否存在指定键
get(),从映射中获取指定键的值
delete(),从映射中删除指定的键/值对
6.7 Set
Set(集合)是 ECMAScript 6 新的一种集合类型,一种新的集合数据结构。用于存储唯一值的集合。
Set 会维护值插入时的顺序,所以支持按顺序迭代。可以通过集合实例上的迭代器来按顺序迭代集合中的内容。
**注意:**在 TypeScript 中的 Set 集合里面, keys() 和 values() 方法返回的实际上是同一个迭代器。这是因为 Set 是一个存储唯一值的数据结构,所以键和值是同一个值。所以遍历的时候建议使用 values()
6.7.1 基本 API
new Set(),创建集合,接收一个可选的可迭代对象作为构造函数的参数
ins.size,获取集合中元素的个数
ins.add(xx),向集合中添加新元素,返回集合实例(支持链式调用)
ins.has(xx),判断集合中是否存在指定元素
ins.delete(xx),从集合中删除指定元素,返回一个布尔值,表示集合中是否存在要删除的值
ins.clear(),清空集合
ins.keys() 方法返回一个新的迭代器对象,它包含 Set 对象中所有元素的值作为键。
ins.values() 方法也返回一个新的迭代器对象,它包含 Set 对象中所有元素的值。
6.8 HashSet
ArkTS 独有,参考 Java 实现的,使用前需要导包
HashSet 基于HashMap实现。在 HashSet 中,只对 value 对象进行处理。
HashSet 和TreeSet相比,HashSet 中的数据无序存放,即存放元素的顺序和取出的顺序不一致,而 TreeSet 是有序存放。它们集合中的元素都不允许重复,但 HashSet 允许放入 null 值,TreeSet 不建议插入空值,可能会影响排序结果。
推荐使用场景: 可以利用 HashSet 不重复的特性,当需要不重复的集合或需要去重某个集合的时候使用。
6.8.1 基本 API
和 Java 类似,具体见 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/nonlinear-container-V5#hashset
6.9 WeakSet
WeakSet(弱映射)是 ECMAScript 6 新增的集合类型,它是一种增强的键值对存储机制。是 Set 的“兄弟”类型,其 API 也是 Set 的子集。WeakMap 中的 “weak(弱)” 描述的是 JavaScript 垃圾回收程序对待“弱映射”中的键的处理方式。
6.9.1 基本 API
add(value):向集合中添加一个值。
delete(value):从集合中删除一个值。
has(value):检查集合中是否存在一个值。
6.10 更多集合类型
还有一些不常用的集合类型,这里就不做过多说明,详情可点下方链接
7. 空安全
默认情况下,ArkTS 中的所有类型都是不可为空的,因此类型的值不能为空。这类似于 TypeScript 的严格空值检查模式(strictNullChecks),但规则更严格。在下面的示例中,所有行都会导致编译时错误:
可以为空值的变量定义为联合类型(Union)T | null。
例子 2
// 如果 obj 或 obj.foo 或 obj.foo.bar 为 null 或 undefined,value 将为 undefined,否则为 42
7.1.1 非空断言运算符 !
后缀运算符!可用于断言其操作数为非空。
应用于空值时,运算符将抛出错误。否则,值的类型将从 T | null 更改为 T:
7.1.2 空值合并运算符 ??
空值合并二元运算符??用于检查左侧表达式的求值是否等于 null。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
换句话说,a ?? b 等价于三元运算符 a != null ? a : b。
在以下示例中,getNick 方法如果设置了昵称,则返回昵称;否则,返回空字符串:
7.1.3 空值合并赋值运算符 (??=)
用于将默认值赋给一个变量,当该变量的值为 null 或 undefined 时。如果一个变量的值为 null 或 undefined,可以使用 ??= 运算符将一个默认值赋给该变量。
**??= **运算符在日常开发中有多种使用场景:
7.1.3.1 默认参数赋值
在函数中可以使用 ??= 来为可能未传入的参数提供默认值:
版权声明: 本文为 InfoQ 作者【冉冉同学】的原创文章。
原文链接:【http://xie.infoq.cn/article/7c8efde951aad86a438879ae1】。文章转载请联系作者。
评论