写点什么

OpenHarmony 应用 TS&JS 编程指南

作者:鸿蒙之旅
  • 2023-05-02
    广东
  • 本文字数:8021 字

    阅读完需:约 26 分钟

OpenHarmony 应用 TS&JS 编程指南

概述

目标和适用范围

本指南适用于在 OpenHarmony 上使用 TypeScript 和 JavaScript 进行系统开发或应用开发编写代码的场景。


本文参考业界标准及实践,结合语言引擎技术特点以及 OpenHarmony 技术特点,为提高代码的规范、安全、性能提供编码建议。

规则来源

本指南筛选自 OpenHarmony JS 通用编程规范,结合可发挥 OpenHarmony 技术特点并且不违反业界认知的规则,包括 ESLint、TSC 配置等。


ESLint 相关规则中的正例反例代码来源于对应 ESLint Rules[2],标注“参见 @typescript-eslint”。

章节概览

OpenHarmony 应用环境限制

OpenHarmony 应用因安全因素的使用限制,均为强制要求。

语言特性:声明与初始化、数据类型、运算与表达式、函数、类与对象、异常、异步、类型

约定

规则 :编程时必须遵守的约定


建议 :编程时必须加以考虑的约定


无论是“规则”还是“建议”,都必须理解该条目这么规定的原因,并努力遵守。

OpenHarmony 应用环境限制

使用严格模式

【级别】规则


【描述】


严格模式是采用具有限制性 JavaScript 变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式”(sloppy)。


  1. 严格模式通过抛出错误来消除了一些原有静默错误

  2. 严格模式修复了一些导致 JavaScript 引擎难以执行优化的缺陷——有时候,相同的代码,严格模式可以比非严格模式下运行得更快

  3. 严格模式禁用了在 ECMAScript 的未来版本中可能会定义的一些语法。


注:OpenHarmony 上方舟编译运行时目前只支持严格模式的 TS/JS 代码

禁止使用 eval()

【级别】规则


【描述】


使用 eval()会让程序比较混乱,导致可读性较差。


【反例】


console.log(eval({ a:2 })); // 输出[object Object] console.log(eval('"a" + 2')); // 输出'a2' console.log(eval('{ a: 2 }')); // 输出2 console.log(eval('let value = 1 + 1;')); // 输出undefined
复制代码

禁止使用 with() {}

【级别】规则


【描述】


使用 with 让代码在语义上变得不清晰,因为 with 的对象,可能会与局部变量产生冲突,从而改变程序原本的用义。


【反例】


const foo = { x: 5 };with (foo) {  let x = 3;  console.log(x);  // x = 3}console.log(foo.x);  // x = 3
复制代码

不要动态创建函数

【级别】规则


【描述】


定义函数的方法包括 3 种:函数声明、Function 构造函数和函数表达式。不管用哪种方法定义函数,它们都是 Function 对象的实例,并将继承 Function 对象所有默认或自定义的方法和属性。以函数构造器创建函数的方式类似于函数 eval(),可以接受任何字符串形式作为它的函数体,这就会有安全漏洞的风险。


【反例】


let add = new Function('a','b','return a + b');// Function构造函数也可以只有一个参数,该参数可以为任意的字符串:let dd = new Function('alert("hello")');
复制代码


【正例】


// 函数声明function add(a,b){  return a+b;}// 函数表达式let add = function(a,b){  return a+b;}
复制代码

声明与初始化

声明变量时要求使用 const 或 let 而不是 var

【级别】规则


【描述】


在 ECMAScript 6 允许程序员使用 let 和 const 关键字在块级作用域而非函数作用域下声明变量。块级作用域在很多其他编程语言中很普遍,能帮助程序员避免错误。只读变量用 const 定义,其它变量用 let 定义。


【反例】


var number = 1;var count = 1;if (isOK) {  count += 1;}
复制代码


【正例】


const number = 1;let count = 1;if (isOK) {  count += 1;}
复制代码

数据类型

不要省略浮点数小数点前后的 0

【级别】规则


【描述】


在 JavaScript 中,浮点值会包含一个小数点,没有要求小数点之前或之后必须有一个数字。虽然不是一个语法错误,但这种格式的数字使真正的小数和点操作符变的难以区分。由于这个原因,必须在小数点前面和后面有一个数字,以明确表明是要创建一个小数。


【反例】


const num = .5;const num = 2.;const num = -.7;
复制代码


【正例】


const num = 0.5;const num = 2.0;const num = -0.7;
复制代码

判断变量是否为 NaN 时必须使用 isNaN()方法

【级别】规则


【描述】


在 JavaScript 中,NaN 是 Number 类型的一个特殊值。它被用来表示非数值,这里的数值是指在 IEEE 浮点数算术标准中定义的双精度 64 位格式的值。 因为在 JavaScript 中 NaN 独特之处在于它不等于任何值,包括它本身,与 NaN 进行比较的结果是令人困惑:NaN !== NaN or NaN != NaN 的值都是 true。 因此,必须使用 Number.isNaN()或全局的 isNaN()函数来测试一个值是否是 NaN。


【反例】


if (foo == NaN) {  // ...}if (foo != NaN) {  // ...}
复制代码


【正例】


if (isNaN(foo)) {  // ...}if (!isNaN(foo)) {  // ...}
复制代码

浮点型数据判断相等不要直接使用==或===

【级别】规则


【描述】


由于浮点数在计算机表示中存在精度的问题,数学上相等的数字,经过运算后,其浮点数表示可能不再相等,因而不能使用相等运算符==或===判断浮点数是否相等。


【反例】


0.1 + 0.2 == 0.3; // false0.1 + 0.2 === 0.3; // false
复制代码


【正例】


const EPSILON = 1e-6;const num1 = 0.1;const num2 = 0.2;const sum = 0.3;if(Math.abs(num1 + num2 - sum) < EPSILON) {  ...}
复制代码

不要在数组上定义或使用非数字属性(length 除外)

【级别】规则


【描述】


在 JavaScript 中,数组也是对象,可以往数组上添加属性,但为了处理方便和避免出错,数组只应该用来存储有序(即索引连续)的一组数据。必须要添加属性时,考虑用 Map 或者 Object 替代。


【反例】


const myHash = [];myHash['key1'] = 'val1';myHash['key2'] = 'val2';myHash[0] = '222';for (const key in myHash) {  // key的值为 0 key1 key2  console.log(key);}console.log(myHash.length); // 数组长度为1
复制代码


【正例】


非数字属性时使用 Map、Object


const map = new Map();map.set('key1', 'val1');map.set('key2', 'val2');for(const [key, value] of map) {  console.log('属性:' + key + ', 值:' + value);}
复制代码

数组遍历优先使用 Array 对象方法

【级别】规则


【描述】


对于数组的遍历处理,应该优先使用 Array 对象方法,如:forEach()、map()、every()、filter()、find()、findIndex()、reduce()、some()。 注意:不能使用 for-in 遍历数组。


【反例】


const numbers = [1, 2, 3, 4, 5];let sum = 0;// 使用for in遍历数组for (const num in numbers) {  console.log(num);  sum += num;}// 依赖已有数组来创建新的数组时,通过for遍历,生成一个新数组const increasedByOne = [];for (let i = 0; i < numbers.length; i++) {  increasedByOne.push(numbers[i] + 1);}
复制代码


【正例】


const numbers = [1, 2, 3, 4, 5];// 使用for of遍历求和let sum = 0;for (const num of numbers) {  sum += num;}// 使用forEach遍历求和let sum = 0;numbers.forEach(num => sum += num);// better: 使用reduce方法实现求和,是更好的方式const sum = numbers.reduce((total, num) => total + num, 0);// 依赖已有数组来创建新的数组,可使用forEach遍历,生成一个新数组const increasedByOne = [];numbers.forEach(num => increasedByOne.push(num + 1));// better: 使用map方法是更好的方式const increasedByOne = numbers.map(num => num + 1);
复制代码

运算与表达式

判断相等时应使用===和!== ,而不是==和!=

【级别】规则


【描述】


JavaScript 中使用双等==做相等判断时会自动做类型转换,如:[] == false、[] == ![]、3 == '03'都是 true,当类型确定时使用全等===做比较可以提高效率。


【反例】


age == beefoo == truebananas != 1value == undefinedtypeof foo == 'undefined''hello' != 'world'0 == 0true == true
复制代码


【正例】


age === beefoo === truebananas !== 1value === undefinedtypeof foo === 'undefined''hello' !== 'world'0 === 0true === true
复制代码


【例外】


//当判断对象是否是null的时候,可直接使用如下形式:obj == nullobj != null
复制代码

不要在控制性条件表达式中执行赋值操作

【级别】规则


【描述】


控制性条件表达式常用于 if、while、for、?:等条件判断中。 在控制性条件表达式中执行赋值,常常导致意料之外的行为,且代码的可读性非常差。


【反例】


// 在控制性判断中赋值不易理解  if (isFoo = false) {  ...}
复制代码


【正例】


const isFoo = someBoolean; // 在上面赋值,if条件判断中直接使用if (isFoo) {  ...}
复制代码

函数

必须使用一致的 return 语句

【级别】规则


【描述】


不像静态类型语言强制要求函数返回一个指定类型的值,JavaScript 允许在一个函数中不同的代码路径返回不同类型的值。


而 JavaScript 在以下情况下函数会返回 undefined:


  1. 在退出之前没有执行 return 语句

  2. 执行 return 语句,但没有显式地指定一个值

  3. 执行 return undefined

  4. 执行 return void,其后跟着一个表达式 (例如,一个函数调用)

  5. 执行 return,其后跟着其它等于 undefined 的表达式


在一个函数中,如果任何代码路径显式的返回一个值,但一些代码路径不显式返回一个值,那么这种情况可能是个书写错误,尤其是在一个较大的函数里。因此,函数内,应使用一致的 return 语句。


【反例】


function doSomething(condition) {  if (condition) {    ...    return true;  } else {    ...    return;  }}function doSomething(condition) {  if (condition) {    ...    return true;  }}
复制代码


【正例】


// 保证所有路径都以相同的方式返回值function doSomething(condition) {  if (condition) {    ...    return true;  } else {    ...    return false;  }}
function doSomething(condition) { if (condition) { ... return true; } ... return false;}
复制代码

不要使用 arguments,可以选择 rest 语法替代

【级别】规则


【描述】


rest 参数是一个真正的数组,也就是说能够在它上面直接使用所有的数组方法,比如 sort,map,forEach 或 pop,而 arguments 是一个类数组。因此,应选择使用 rest 语法替代 arguments。另外,rest 参数必须是列表中的最后一个参数。


【反例】


function concatenateAll() {  // 因为arguments是类数组,不能直接使用join方法,需要先转换为真正的数组  const args = Array.prototype.slice.call(arguments);     return args.join('');}
复制代码


【正例】


function concatenateAll(...args) {  return args.join('');}
复制代码

不要将 This 赋值给一个变量,约束 This 在一个 Scope 内使用

【级别】规则


【描述】


箭头函数提供了更简洁的语法,并且箭头函数中的 this 对象指向是不变的,this 绑定到定义时所在的对象,有更好的代码可读性。而保存 this 引用的方式,容易让开发人员搞混。


【反例】


function foo() {  const self = this;  return function() {    console.log(self);  };}
复制代码


【正例】


function foo() {  return () => {    console.log(this);  };}
复制代码


参见:@typescript-eslint/no-this-alias


ESLint 的描述更加严苛,我们认为 this 不应该在任务情况下赋值给一个变量。

类与对象

使用点号来访问对象的属性,只有计算属性使用[]

【级别】规则


【描述】


在 JavaScript 中,可以使用点号 (foo.bar) 或者方括号 (foo['bar'])来访问属性。然而,点号通常是首选,因为它更加易读,简洁,也更适于 JavaScript 压缩。


【正例】


const name = obj.name;const key = getKeyFromDB();const prop = obj[key]; // 属性名是变量时才使用[]
复制代码

不要修改内置对象的原型,或向原型添加方法

【级别】规则


【描述】


内置对象作为一套公共接口,具有约定俗成的行为方式,若修改其原型,可能破坏接口语义。因此,永远不要修改内置对象的原型,或向原型添加方法。


【反例】


Array.prototype.indexOf = function () {  return -1;}// 其它地方使用的时候const arr = [1, 1, 1, 1, 1, 2, 1, 1, 1];console.log(arr.indexOf(2)); // 输出-1
复制代码

不要删除对象的可计算属性

【级别】规则


【描述】


delete 会改变对象的布局,而 delete 对象的可计算属性会非常危险,而且会大幅约束语言运行时的优化从而影响执行性能。


注意:建议不删除对象的任何属性,如果有需要,建议用 map 和 set。


【反例】


// Can be replaced with the constant equivalents, such as container.aaadelete container['aaa'];
// Dynamic, difficult-to-reason-about lookupsconst name = 'name';delete container[name];delete container[name.toUpperCase()];
复制代码


【正例】


// 一定程度也会影响优化性能,但比删除可计算属性好一些。delete container.aaa;
delete container[7];
复制代码


参见:@typescript-eslint/no-dynamic-delete

异常

不要使用 return、break、continue 或抛出异常使 finally 块非正常结束

【级别】规则


【描述】


在 finally 代码块中,直接使用 return、break、continue、throw 语句,或由于调用方法的异常未处理,会导致 finally 代码块无法正常结束。非正常结束的 finally 代码块会影响 try 或 catch 代码块中异常的抛出,也可能会影响方法的返回值。所以要保证 finally 代码块正常结束。


【反例】


function foo() {  try {    ...    return 1;  } catch(err) {    ...    return 2;  } finally {    return 3; }}
复制代码


【正例】


function foo() {  try {    ...    return 1;  } catch(err) {    ...    return 2;  } finally {    console.log('XXX!');  }}
复制代码

异步

禁用不必要的 return await

【级别】规则


【描述】


因为 async function 的返回值总是封装在 Promise.resolve,return await 实际上并没有做任何事情,只是在 Promise resolve 或 reject 之前增加了额外的时间。唯一有效是,如果 try/catch 语句中使用 return await 来捕获另一个基于 Promise 的函数的错误,则会出现异常。


【反例】


async function foo() {  return await bar();}
复制代码


【正例】


async function foo() {  return bar();}async function foo() {  await bar();  return;}async function foo() {  const baz = await bar();  return baz;}async function foo() {  try {    return await bar();  } catch (error) {    // here can be executed, go on  }}
复制代码

不允许等待非 Thenable 的值

【级别】规则


【描述】


如果 await 一个非 Thenable 的值,await 会把该值转换为已正常处理的 Promise,然后等待其处理结果。此时 await 反而会影响代码性能。


【反例】


async function f3() {  const y = await 20;  console.log(y); // 20}
f3();console.log(30);
// output// 30// 20
复制代码


【正例】


async function f3() {  const y = 20;  console.log(y); // 20}
f3();console.log(30);
// output// 20// 30
复制代码


参见:@typescript-eslint/await-thenable

类型

强制将 Null 和 Undefined 作为独立类型标注

【级别】规则


【描述】


Null 和 Undefined 作为独立类型标注,可以提高代码的安全性,避免空指针异常。


【反例】


let userName: string;userName = 'hello';userName = undefined;
复制代码


【正例】


let userName: string | undefined;userName = 'hello';userName = undefined;
复制代码

必须显式声明函数及类方法的返回值类型

【级别】规则


【描述】


显式声明返回类型,这可确保返回值被分配给正确类型的变量;或者在没有返回值的情况下,调用代码不会尝试把 undefined 分配给变量。


【反例】


// 没有返回值时,没有声明返回值类型为voidfunction test() {  return;}// 没有声明返回值类型为numberfunction fn() {  return 1;};// 没有声明返回值类型为stringlet arrowFn = () => 'test';class Test {  // 没有返回值时,没有声明返回值类型为void  method() {    return;  }}
复制代码


【正例】


// 函数没有返回值时,显式声明返回值类型为voidfunction test(): void {  return;}// 显式声明返回值类型为numberfunction fn(): number {  return 1;};// 显式声明返回值类型为 stringlet arrowFn = (): string => 'test';class Test {  // 没有返回值时,显式声明返回值类型为void  method(): void {    return;  }}
复制代码


参见:@typescript-eslint/explicit-function-return-type

强制使用类型导出的一致性

【级别】规则


【描述】


如果导出类型(type),将导出类型和导出其他对象分开写。


【反例】


interface ButtonProps {  onClick: () => void;}class Button implements ButtonProps {  onClick() {    console.log('button!');  }}export { Button, ButtonProps };
复制代码


【正例】


interface ButtonProps {  onClick: () => void;}class Button implements ButtonProps {  onClick() {    console.log('button!');  }}export { Button };export type { ButtonProps };
复制代码


参见:@typescript-eslint/consistent-type-exports

强制使用类型导入的一致性

【级别】规则


【描述】


如果导入类型(type),将导入类型和导出其他对象分开写。


【反例】


import { Foo } from 'Foo';import Bar from 'Bar';type T = Foo;const x: Bar = 1;
复制代码


【正例】


import type { Foo } from 'Foo';import type Bar from 'Bar';type T = Foo;const x: Bar = 1;
复制代码


参见:@typescript-eslint/consistent-type-imports

避免使用 any

【级别】规则


【描述】


使用了any类型会使所有编译时的类型检查被忽略。一般来说,这个行为不是必需的,也不符合期望。如果类型未知,要求使用unknown 。 当引入的三方件不是使用 TS 语言或者没有提供 TS 类型声明时,可以使用any来声明相关的三方件对象。

不允许定义 any 类型

【级别】规则


【描述】


不允许定义 any 类型。它的目的是为了让类型在 TS 中尽量明确,帮助语言运行时优化。


【反例】


const age: any = 'seventeen';function greet(): any {}function greet(param: Array<any>): string {}
复制代码


【正例】


const age: number = 17;function greet(): string {}function greet(param: Array<string>): string {}
复制代码


参见:@typescript-eslint/no-explicit-any


不允许使用 any 作为参数传递

【级别】规则


【反例】


declare function foo(arg1: string, arg2: number, arg3: string): void;
const anyTyped = 1 as any;
foo(...anyTyped);foo(anyTyped, 1, 'a');
const tuple1 = ['a', anyTyped, 'b'] as const;foo(...tuple1);
复制代码


【正例】


declare function foo(arg1: string, arg2: number, arg3: string): void;
foo('a', 1, 'b');
const tuple1 = ['a', 1, 'b'] as const;foo(...tuple1);
复制代码


参见:@typescript-eslint/no-unsafe-argument

不允许在赋值中使用 any

【级别】规则


【反例】


const x = 1 as any,
const x: Set<string> = new Set<any>();
复制代码


【正例】


const x = 1;
const x: Set<string> = new Set<string>();
复制代码


参见:@typescript-eslint/no-unsafe-assignment

不允许 call 类型为 any 的变量

【级别】规则


【反例】


declare const anyVar: any;declare const nestedAny: { prop: any };
anyVar();anyVar.a.b();
nestedAny.prop();nestedAny.prop['a']();
复制代码


【正例】


declare const typedVar: () => void;declare const typedNested: { prop: { a: () => void } };
typedVar();typedNested.prop.a();
复制代码


参见:@typescript-eslint/no-unsafe-call

不允许访问类型为 any 的对象的成员

【级别】规则


【反例】


declare const anyVar: any;declare const nestedAny: { prop: any };
anyVar.a;anyVar.a.b;
nestedAny.prop.a;nestedAny.prop['a'];
复制代码


【正例】


declare const properlyTyped: { prop: { a: string } };
properlyTyped.prop.a;properlyTyped.prop['a'];
复制代码


参见:@typescript-eslint/no-unsafe-member-access

不允许声明函数返回值类型为 any 或者 any[]

【级别】规则


【反例】


function foo1() {  return 1 as any;}
复制代码


【正例】


function foo1() : number {  return 1;}
复制代码


参见:@typescript-eslint/no-unsafe-return

参考

  1. 《OpenHarmony JS 通用编程规范》:https://gitee.com/openharmony/docs/blob/master/zh-cn/contribute/OpenHarmony-JavaScript-coding-style-guide.md

  2. ESLint Rules:https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules

  3. 《高性能 JavaScript》

发布于: 2023-05-02阅读数: 108
用户头像

鸿蒙之旅

关注

还未添加个人签名 2022-10-26 加入

还未添加个人简介

评论

发布
暂无评论
OpenHarmony应用TS&JS编程指南_OpenHarmony_鸿蒙之旅_InfoQ写作社区