OpenHarmony开发者论坛

标题: 从ECMA规范到ArkTS接口(一)--typedArray.slice接口 [打印本页]

作者: 深开鸿_王清    时间: 2024-3-26 15:33
标题: 从ECMA规范到ArkTS接口(一)--typedArray.slice接口
[md]**在深入理解和使用ArkTS中的数组操作之前,我们有必要先了解ECMAScript(ECMA)规范中的规定。ECMA规范是JavaScript语言的官方标准,而ArkTS作为JavaScript的超集,也遵守了这些规定。本系列文章旨在探讨ECMA规范如何定义接口,以及如何在ArkTS该接口如何使用。**

## ECMA介绍

**ECMAScript是由Ecma国际(前身为欧洲计算机制造商协会ECMA)制定的语言标准,目标是标准化JavaScript语言。JavaScript通常用于网页和服务器端编程,而ECMAScript则为这些使用情境提供了一套规范。**

**ECMAScript(ES)的标准自2015年以来进入了年度发布周期,意味着每年都会发布一个新版本。这个变化始于ECMAScript 6(ES6)或ECMAScript 2015的发布,之后的每个版本通常被称为ECMAScript 2016(ES7)、ECMAScript 2017(ES8)、以此类推,直到最新的版本。**

**这种年度发布计划允许JavaScript语言以小的、可管理的步骤来引入新特性,使得开发者更容易跟上语言的变化,同时也使得浏览器和环境开发者更容易实现和支持新标准。每个版本的新特性通常在年初被确定,并在年中的ECMA General Assembly会议上正式批准。**

**截至本文发布,当前最新版本为ECMAScript 2023,也称为ES14。**

## ECMA对typedArray.slice接口的定义

**在ECMA规范中,**`typedArray` 是一个泛型对象,表示一个固定长度的二进制数据缓冲区,并且可以用来创建多种类型的视图,比如 `Uint8Array`、`Int16Array`、`Float32Array` 等。而其中的 `.slice()` 方法,用于浅复制 `typedArray` 的一部分到一个新的 `typedArray` 实例。

**根据ECMAScript规范,**`typedArray.slice(begin, end)` 方法接受两个参数:`begin` 表示起始位置,`end` 表示结束位置(不包含)。如果不提供 `end` 参数,`slice` 方法会复制从 `begin` 到原数组末尾的所有元素。如果 `begin` 或 `end` 是负数,则表示从数组末尾开始的偏移量。返回值是一个新的 `typedArray` 实例,包含指定范围的元素。

**我们可以直接查看ECMA规范中对该函数的描述:**

```
23.2.3.27 %TypedArray%.prototype.slice ( start, end )
The interpretation and use of the arguments of this method are the same as for Array.prototype.slice as defined in 23.1.3.28.

This method performs the following steps when called:
1. Let O be the this value.
2. Perform ? ValidateTypedArray(O).
3. Let len be O.[[ArrayLength]].
4. Let relativeStart be ? ToIntegerOrInfinity(start).
5. If relativeStart = -∞, let k be 0.
6. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
7. Else, let k be min(relativeStart, len).
8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
9. If relativeEnd = -∞, let final be 0.
10. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
11. Else, let final be min(relativeEnd, len).
12. Let count be max(final - k, 0).
13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »).
14. If count > 0, then
   a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception.
   b. Let srcType be TypedArrayElementType(O).
   c. Let targetType be TypedArrayElementType(A).
   d. If srcType is targetType, then
       i. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data.
       ii. Let srcBuffer be O.[[ViewedArrayBuffer]].
       iii. Let targetBuffer be A.[[ViewedArrayBuffer]].
       iv. Let elementSize be TypedArrayElementSize(O).
       v. Let srcByteOffset be O.[[ByteOffset]].
       vi. Let srcByteIndex be (k × elementSize) + srcByteOffset.
       vii. Let targetByteIndex be A.[[ByteOffset]].
       viii. Let limit be targetByteIndex + count × elementSize.
       ix. Repeat, while targetByteIndex < limit,
           1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered).
           2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered).
           3. Set srcByteIndex to srcByteIndex + 1.
           4. Set targetByteIndex to targetByteIndex + 1.
    e. Else,
       i. Let n be 0.
       ii. Repeat, while k < final,
           1.Let Pk be ! ToString(𝔽(k)).
           2.Let kValue be ! Get(O, Pk).
           3.Perform ! Set(A, ! ToString(𝔽(n)), kValue, true).
           4.Set k to k + 1.
           5.Set n to n + 1.
15. Return A.
This method is not generic. The this value must be an object with a [[TypedArrayName]] internal slot.
```

**大家可以看到,ECMA规范中对接口的描述非常详细,对各种参数的输入和输出都做非常细致的说明,无论是符合标准的语言接口的实现,还是**

## ArkTS对slice的接口描述

**在ArkTS中,接口(Interfaces)是一个非常强大的特性,允许开发者定义对象的类型。为了实现一个符合ECMA规范的 **`slice` 方法,ArkTS对该方法的定义声明如下:

**以\\static\_core\\plugins\\ets\\stdlib\\escompat\\TypedUArrays.ets的Uint8Array定义为例:**

```
/**
    * Creates a slice of current Uint8Array using range [begin, end)
    *
    * @param begin start index to be taken into slice
    *
    * @param end last index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin;end) where end index is excluded
    *
    * @link https://developer.mozilla.org/en ... ts/TypedArray/slice
    */
   public slice(begin?: Number, end?: Number): Uint8Array {
       return this.slice(asIntOrDefault(begin, 0 as int), asIntOrDefault(end, this.lengthInt))
   }

   /**
    * Creates a slice of current Uint8Array using range [begin, end)
    *
    * @param begin start index to be taken into slice
    *
    * @param end last index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin;end) where end index is excluded
    *
    * @link https://developer.mozilla.org/en ... ts/TypedArray/slice
    */
   public slice(begin: number, end: number): Uint8Array {
       return this.slice(begin as int, end as int)
   }

   /**
    * Creates a slice of current Uint8Array using range [begin, end)
    *
    * @param begin start index to be taken into slice
    *
    * @param end last index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin;end) where end index is excluded
    *
    * @link https://developer.mozilla.org/en ... ts/TypedArray/slice
    */
   public slice(begin: number, end: int): Uint8Array {
       return this.slice(begin as int, end as int)
   }

   /**
    * Creates a slice of current Uint8Array using range [begin, end)
    *
    * @param begin start index to be taken into slice
    *
    * @param end last index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin;end) where end index is excluded
    *
    * @link https://developer.mozilla.org/en ... ts/TypedArray/slice
    */
   public slice(begin: int, end: number): Uint8Array {
       return this.slice(begin as int, end as int)
   }

   /**
    * Creates a slice of current Uint8Array using range [begin, end)
    *
    * @param begin start index to be taken into slice
    *
    * @param end last index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin;end) where end index is excluded
    *
    * @link https://developer.mozilla.org/en ... ts/TypedArray/slice
    */
   public slice(begin: int, end: int): Uint8Array {
       const len: int = this.lengthInt
       const relStart = normalizeIndex(begin, len)
       const relEnd = normalizeIndex(end, len)
       let count = relEnd - relStart
       if (count < 0) {
           count = 0
       }
       if (this.buffer instanceof ArrayBuffer) {
           let buf = (this.buffer as ArrayBuffer).slice(relStart * Uint8Array.BYTES_PER_ELEMENT as int, relEnd * Uint8Array.BYTES_PER_ELEMENT as int) as ArrayBuffer
           return new Uint8Array(buf)
       } else if (this.buffer instanceof SharedArrayBuffer) {
           let buf = (this.buffer as SharedArrayBuffer).slice(relStart * Uint8Array.BYTES_PER_ELEMENT as int, relEnd * Uint8Array.BYTES_PER_ELEMENT as int) as SharedArrayBuffer
           return new Uint8Array(buf)
       } else {
           throw new Error("unexpected type of buffer")
       }
   }

   /**
    * Creates a slice of current Uint8Array using range [begin, this.lengthInt).
    *
    * @param begin start index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin, this.lengthInt)
    */
   public slice(begin: number): Uint8Array {
       return this.slice(begin as int)
   }

   /**
    * Creates a slice of current Uint8Array using range [begin, this.lengthInt).
    *
    * @param begin start index to be taken into slice
    *
    * @returns a new Uint8Array with elements of current Uint8Array[begin, this.lengthInt)
    */
   public slice(begin: int): Uint8Array {
       return this.slice(begin, this.lengthInt)
   }

```

**在这个例子中,我们创建了一个 **`Int32ArrayWithSlice` 类,它实现了 `TypedArray` 接口,包含了符合ECMA规范的 `slice` 方法。这个方法返回的是 `Int32ArrayWithSlice` 的新实例,包含了切片后的元素。

## 测试用例

**要验证我们的 **`slice` 方法实现是否正确,我们可以编写单元测试用例。这里我们使用了ArkTS和Jest测试框架,来确保我们的实现与ECMA规范一致。

```
const success = 0;
const fail = 1;
function testSliceWithOutParam(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
   origin = new Uint8Array(ss);
   origin.set(source);
  } catch(e) {
   console.log(e);
   return fail;
  }


  let target: Uint8Array;

  try {
   target = origin.slice();
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != origin.length as int) {
   console.log("Array length mismatch on slice");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = 0; i< origin.length as int; i++) {
   let tv = target as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  origin= new Uint8Array(0);
  if(origin.length as int != 0){
   return fail;
  }

  try {
   target = origin.slice();
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != 0){
   return fail;
  }
  return success;
}

function testSliceOneParam(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
   origin = new Uint8Array(ss);
   origin.set(source);
  } catch(e) {
   console.log(e);
   return fail;
  }

  let sliceStart: int = 1;
  let sliceEnd: int = origin.length as int;

  let target: Uint8Array;

  try {
   target = origin.slice(sliceStart);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != origin.length as int - sliceStart) {
   console.log("Array length mismatch on slice One Params" + target.length);
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  sliceStart = 0;
  try {
   target = origin.slice(undefined);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != origin.length as int) {
   console.log("Array length mismatch on slice One Params" + target.length);
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  return success;
}

function testSliceTwoParams(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60, 70, 80];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
   origin = new Uint8Array(ss);
   origin.set(source);
  } catch(e) {
   console.log(e);
   return fail;
  }

  let sliceStart: int = 2;
  let sliceEnd: int = 4;

  let target: Uint8Array;

  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != sliceEnd - sliceStart) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  sliceStart = 0;
  sliceEnd = origin.length as int;
  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != sliceEnd - sliceStart) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  try {
   target = origin.slice(new Number(sliceStart), undefined);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != sliceEnd - sliceStart) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  try {
   target = origin.slice(undefined, undefined);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != sliceEnd - sliceStart) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  try {
   target = origin.slice(undefined, new Number(sliceEnd));
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != sliceEnd - sliceStart) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = sliceStart; i< sliceEnd; i++) {
   let tv = target[i - sliceStart] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  try {
   target = origin.slice(0, 0);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != 0) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  return success;
}

function testSliceTwoParamsWithOtherNumber(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60, 70, 80];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
   origin = new Uint8Array(ss);
   origin.set(source);
  } catch(e) {
   console.log(e);
   return fail;
  }

  let sliceStart: int = 4;
  let sliceEnd: int = 2;

  let target: Uint8Array;

  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   return fail;
  }

  if(target.length as int != 0) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  sliceStart = -1;
  sliceEnd = origin.length as int;
  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   return fail;
  }

  if(target.length as int != sliceEnd - (origin.length + sliceStart)) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  //Check all the data copied;
  for(let i:int = (origin.length + sliceStart) as int; i< sliceEnd; i++) {
   let tv = target[i - (origin.length + sliceStart)] as number;
   let ov = origin as number;
   console.log(source + "->" + tv + "->" + ov);
   if(tv != ov) {
     console.log("Array data mismatch");
     return fail;
   }
  }

  sliceStart = 0;
  sliceEnd = -origin.length as int;
  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   console.log(e);
   return fail;
  }

  if(target.length as int != (origin.length + sliceEnd) - sliceStart) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  return success;
}

function testSliceOneLengthTwoParams(): int {
  let source: number[] = [10];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
   origin = new Uint8Array(ss);
   origin.set(source);
  } catch(e) {
   console.log(e);
   return fail;
  }
 
  let sliceStart: int = 4;
  let sliceEnd: int = 2;

  let target: Uint8Array;

  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   return fail;
  }

  if(target.length as int != 0) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  sliceStart = 2;
  sliceEnd = 4;
  try {
   target = origin.slice(sliceStart, sliceEnd);
  } catch(e) {
   return fail;
  }

  if(target.length as int != 0) {
   console.log("Array length mismatch on slice2");
   return fail;
  }

  return success;
}

```

**这些测试用例检查了 **`slice` 方法在正常条件下和非正常条件下的行为。通过运行这些测试,我们可以确认我们的实现是否符合接口定义预期和ECMA规范。

**总结来说,理解ECMA规范中的接口是任何ArkTS开发者的基本功,而且通过单元测试可以保证实现的准确性和可靠性。通过逐步实现这些接口并验证它们的行为,我们可以确保我们的ArkTS代码既强大又稳健。**

## 参考文献:

1. [ECMA262-14th规范](https://262.ecma-international.org/14.0)
2. [方舟运行时公共组件](https://gitee.com/openharmony/arkcompiler_runtime_core)
[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5