跳到主要内容

ArkTS运行时常见问题

正则运算与预期输出结果不一致场景

如果使用正则运算时结果与期望不符,请检查以下场景。

正则运算对于\b处理与预期不一致

let str = '\u2642';
let res = str.replace(/\b/g, '/');
console.info('res = ' + res);
// 期望输出: res = ♂
// 实际输出: res = /♂/

规避方案:暂无。

正则匹配\b(单词边界)遇到某些ASCII编码之外的字符时,会将其当成ASCII字符来处理,从而将不是单词边界匹配识别成单词边界。

正则运算对于先行断言((?=pattern)或(?!pattern)) 嵌套在后行断言((?<=pattern)或(?<!pattern))内部的场景与预期不一致

console.info(`res:${'abcdef'.match(/(?<=ab(?=c)cd)ef/)}`);
// 期望输出: res:ef
// 实际输出: res:null

规避方案:使用/(?<=abcd)ef/代替/(?<=ab(?=c)cd)ef/。

正则运算对于大小写的处理与预期不一致

let res = /\u{10400}/ui.test('\u{10428}');
console.info('res = ' + res);
// 期望输出: res = true
// 实际输出: res = false

规避方案:暂无。

正则运算/()/ug匹配时lastIndex与预期不一致

let L = '\ud800';
let T = '\udc00';
let u = /()/ug;
u.lastIndex = 1;
u.exec(L + T + L + T);
console.info('u.lastIndex = ' + u.lastIndex);
// 期望输出: u.lastIndex = 0
// 实际输出: u.lastIndex = 1

规避方案:暂无。

正则运算[]内部使用'-'与预期不一致

let str = 'a-b';
let reg = /[+-\s]/;
console.info('reg.exec(str) = ' + reg.exec(str));
// 期望输出: reg.exec(str) = -
// 实际输出: reg.exec(str) = null

规避方案:使用转义后的"-"。

let str = 'a-b';
let reg = /[+\-\s]/;
console.info('reg.exec(str) = ' + reg.exec(str));

正则运算具名捕获组获取与预期不一致

let reg = new RegExp('(a)(?<b>b)');
let res = reg.exec('ab');
console.info('JSON.stringify(res?.groups) = ' + JSON.stringify(res?.groups));
// 期望输出: JSON.stringify(res?.groups) = {'b':'b'}
// 实际输出: JSON.stringify(res?.groups) = {'b':'a'}

规避方案:计算具名捕获组位置获取具名捕获组匹配的内容。

let reg = new RegExp('(a)(?<b>b)');
let res = reg.exec('ab') as Array<string>;
console.info('JSON.stringify(res?.groups) = {\'b\':' + JSON.stringify(res[2]) + '}');

正则匹配使用'|'与预期不一致

在使用正则匹配时,如果'|'前是一个空匹配,会导致'|'后的匹配不成功。

let reg = /a(?:|x)$/;
let res = reg.exec('ax');
console.info('JSON.stringify(res) = ' + JSON.stringify(res));
// 期望输出: JSON.stringify(res) = ['ax']
// 实际输出: JSON.stringify(res) = null

规避方案:使用reg2或reg3替换reg1。

let reg1 = /a(?:|x)$/;
let reg2 = /a(?:x)?$/;
let reg3 = /a(?:x){0,1}$/;

TypedArray.prototype.map触发内联缓存优化后,在回调中将数值number转为浮点数number与期望不一致

for(let i = 0; i < 1000; i++) {} // 触发内联缓存优化

let arr = new Int32Array([1, 2, 3, 4, 5]);
let result = arr.map(val => {
let res = (Math.pow(val, 1)) * 100;
return res;
})

console.info('result[0]:', result[0]);
// 期望输出: result[0]:100
// 实际输出: result[0]:104

规避方案:使用Array.from将TypedArray先转换为普通Array,再处理number。

let arr = new Int32Array([1, 2, 3, 4, 5]);

let normalArr = Array.from(arr);
let result = normalArr.map(val => {
let res = (Math.pow(val, 1)) * 100;
return res;
});

console.info('result[0]:', result[0]);
// 输出: result[0]:100

Number.parseFloat解析浮点数number类型非规格化数值与期望不一致

parseFloat接口不支持对非规格化数进行解析。当输入字符串表示一个浮点数number类型的非规格化数,一律输出0。

let result = parseFloat('5e-324');
console.info('testcase: ', result);
// 期望输出: testcase: 5e-324
// 实际输出: testcase: 0

规避方案:暂无,开发者应避免使用parseFloat接口对非规格化数进行解析。

Set constructor入参为多维数组的解析与期望不一致

const arr1: number[] = [1, 2];
const arr2: number[] = [3, 4];
const set = new Set<number[]>([arr1, arr2]);
let result = JSON.stringify(Array.from(set));
console.info('res: ', result);
// 期望输出: res: [[1,2],[3,4]]
// 实际输出: res: [2,4]

规避方案:暂无,开发者应避免构造set时入参为多维数组。

Object.entries处理Uint8Array与Uint16Array数组结果与期望不一致

// TestArray.js
const typedArr = new Uint8Array([10, 20, 30]);
try {
const result = Object.entries(typedArr);
console.info('no error throw');
} catch(e) {
console.info(e);
}
// 期望输出:no error throw
// 实际输出: RangeError: object entries is not supported IsJSUint8Array or IsJSUint16Array
// TestArrayExt.js
const typedArr = new Uint16Array([10, 20, 30]);
try {
const result = Object.entries(typedArr);
console.info('no error throw');
} catch (e) {
console.info(e);
}
// 期望输出:no error throw
// 实际输出: RangeError: object entries is not supported IsJSUint8Array or IsJSUint16Array

规避方案:使用Array.from将TypedArray先转换为普通Array,再使用Object.entries。

// TestArray.js
const typedArr = new Uint8Array([10, 20, 30]);
try {
const normalArr1 = Array.from(typedArr);
const result = Object.entries(normalArr1);
console.info('no error throw');
} catch(e) {
console.info(e);
}
// 输出:no error throw

字符串 replace 接口对于第一个参数为空字符串的场景与预期不一致

在使用字符串replace接口时,如果第一个参数是空字符串,则直接返回原始字符串。

let str = 'dddd';
let res = str.replace('', 'abc');
console.info('res = ' + res);
// 期望输出: res = abcdddd
// 实际输出: res = dddd

规避方案:使用正则表达式 /^/ 表示字符串起始符,作为第一个参数。

let str = 'dddd';
let res = str.replace(/^/, 'abc');

Async函数内部异常的处理机制

场景

如果在Async函数内部产生了异常,且没有使用catch捕获该异常,在ArkTS中不会导致进程退出。其本质是Async函数返回了一个rejected状态的Promise对象,没有被处理,使得Promise的rejected状态没有被捕获。Async函数内部的异常会变成 unhandledRejection,表现为异常未抛出。

Async函数内部异常的捕获方式

  1. 使用errorManager.on()捕获到Async函数产生的unhandledrejection事件,再通过编写errorManager.on()注册的回调函数,来进行异常处理操作。

    import { errorManager } from '@kit.AbilityKit';
    // ...
    errorManager.on('unhandledRejection', (a:ESObject, b:Promise<ESObject>) => {
    console.info('Async test', a);
    // ...
    })
  2. 在Async函数内部,针对可能发生异常的代码块添加try-catch逻辑,直接捕获可能出现的异常。

注意必须在Async函数内部,外部无法捕获Async函数内部的异常,外部只能通过errorManager.on()监听。

查看Async函数内部是否有异常的方式

若开发者仅需要查看Async函数内部是否产生异常,首先需要在DevEco Studio终端执行以下hilog命令开启debug级别日志打印:

hilog -b D

然后点击DevEco Studio下方HiLog选项卡,输入过滤条件“Throw error:”,即可查看到Async函数内产生的异常信息。

Array.flatMap()接口常见问题

Array.flatMap()接口在处理包含Proxy的Array时,未正确展平嵌套的Proxy Array,导致返回结果与预期不一致。

ArkTS使用场景

let arr1 = [0, 1];
let arr2 = [2, 3];
const emptyHandler = {} as ProxyHandler<number[]>;
let proxy1 = new Proxy(arr1, emptyHandler);
let proxy2 = new Proxy(arr2, emptyHandler);
let arr3 = [proxy1, proxy2];
let res = arr3.flatMap(x => x);

console.info('res length:', res.length.toString());
// 期望输出: res length: 4
// 实际输出: res length: 2
console.info('res[0] is: ', res[0].toString());
// 期望输出: res[0] is: 0
// 实际输出: res[0] is: 0,1

ArkUI使用场景

ArkUI状态管理框架会为使用状态变量装饰器(如@State、@Trace、@Local)装饰的Array添加一层代理,用于观测API调用产生的变化。如果状态装饰器与Array组合,并且调用Array.flatMap,会出现如下问题。

以状态管理V2为例:

@Entry
@ComponentV2
struct Index {
@Local p: number[] = [0, 1];
@Local q: number[] = [2, 3];
c: number[][] = [this.p, this.q];
d: number[] = [];

aboutToAppear(): void {
this.d = this.c.flatMap(it => it);
}

build() {
Column() {
Text(`${this.d[0]}`); // 预期显示:0; 实际显示:0,1
}
}
}

Array.flatMap规避方案

避免使用Array.flatMap()接口,改为调用Array.map()接口后再调用深度为1的Array.flat()接口。以上文ArkTS使用场景为例:

// 使用规避方案前
let res = arr3.flatMap(x => x);
// ...
// 使用规避方案后
let res = arr3.map(x => x).flat();

Proxy的handler对象中key类型与EcmaScript规范定义不一致

在Proxy对象的handler函数中,对于数字类型的key,ArkTS当前实现是采用保持数字类型不变,但是按照EcmaScript规范,应当转为string类型。

// TestArray.js
{
let handler = {
get(target, key) {
console.info('get', key, typeof key);
return Reflect.get(target, key);
},
set(target, key, value) {
console.info('set', key, typeof key);
return Reflect.set(target, key, value);
},
deleteProperty(target, key) {
console.info('delete', key, typeof key);
return Reflect.deleteProperty(target, key);
},
has(target, key) {
console.info('has', key, typeof key);
return Reflect.has(target, key);
}
}
let obj = {};
let px = new Proxy(obj, handler);
px[1];
// 实际输出:get 1 number
px[2] = 2;
// 实际输出:set 2 number
3 in px;
// 实际输出:has 3 number
delete px[2];
// 实际输出:delete 2 number
}

规避方案:若业务逻辑依赖于key必须为string类型,可在handler函数内部对数字类型的key进行显式转换。示例如下:

// TestArray.js
{
let handler = {
get(target, key) {
if (typeof key === 'number') {
key = String(key);
}
console.info('get', key, typeof key);
return Reflect.get(target, key);
},
set(target, key, value) {
if (typeof key === 'number') {
key = String(key);
}
console.info('set', key, typeof key);
return Reflect.set(target, key, value);
},
deleteProperty(target, key) {
if (typeof key === 'number') {
key = String(key);
}
console.info('delete', key, typeof key);
return Reflect.deleteProperty(target, key);
},
has(target, key) {
if (typeof key === 'number') {
key = String(key);
}
console.info('has', key, typeof key);
return Reflect.has(target, key);
}
}
let obj = {};
let px = new Proxy(obj, handler);
px[1];
// 实际输出:get 1 string
px[2] = 2;
// 实际输出:set 2 string
3 in px;
// 实际输出:has 3 string
delete px[2];
// 实际输出:delete 2 string
}

上述demo中部分语法,如 "3 in px", "delete px[2]", "Reflect.deleteProperty",在ets文件中不可用。

JSON.stringify的replacer函数中数组索引的key类型与EcmaScript规范定义不一致

JSON.stringify的replacer函数中,对于数组索引key的类型,ArkTS当前实现是采用保持数字类型不变,但是按照EcmaScript规范,应当转为string类型。

// TestArray.js
{
let arr = [10, 20, 30, 40];
function replacer(key, value) {
if (key === '2') {
return value * 2;
}
return value;
}
console.info(JSON.stringify(arr, replacer));
// 实际输出:[10,20,30,40]
}

规避方案:若业务逻辑依赖于key必须为string类型,可在replacer函数内部对数字类型的key进行显式转换。示例如下:

// TestArray.js
{
let arr = [10, 20, 30, 40];
function replacer(key, value) {
if (typeof key === 'number') {
key = String(key);
}
if (key === '2') {
return value * 2;
}
return value;
}
console.info(JSON.stringify(arr, replacer));
// 实际输出:[10,20,60,40]
}