ArkTS基础语法 |(2)函数

6 阅读8分钟

ArkTS基础语法 |(2)函数

在学习HarmonyOS开发的核心语言ArkTS时,整理了一份基础语法笔记,方便日后回顾。

函数能够封装重复的业务逻辑(代码块),遵循先定义、后调用的基本原则,大幅提升代码的复用率和可维护性。

一、函数声明

ArkTS中声明函数需明确函数名、参数列表、返回类型(可选)和函数体

  • 声明函数的核心规则:
  1. 形参必须显式标记类型,实参的数量、类型需与形参一一对应。

  2. 仅定义函数不调用,函数体代码不会执行。

  3. 支持无参无返回值、带参带返回值等多种形式。

  4. 可通过return关键字返回处理结果,无返回值时可省略return

1. 无参无返回值函数

最基础的函数形式,无参数传入,也无结果返回,适用于执行简单的固定逻辑。

// 定义语法
function 函数名() {
  函数体
}
// 调用语法
函数名()

// 示例
function sayHello() {
  console.log('Hello ArkTS!');   // 执行简单的打印逻辑
}
sayHello();   // 调用后输出:Hello ArkTS!
2. 带参带返回值函数

适用于需要传入参数并返回处理结果的场景,需指定形参类型函数返回类型

  • 形参:函数定义时的参数,必须指定类型。

  • 实参:函数调用时传入的参数,需与形参数量、类型严格匹配。

  • return :用于返回处理结果,执行到return后函数体立即终止。

// 示例:定义求和函数,接收两个number类型参数,返回number类型结果。
function sum(num1: number, num2: number): number {
  return num1 + num2;   // 返回两数之和
}
// 调用函数,接收返回值并打印。
let result: number = sum(10, 20);
console.log(result);   // 输出:30

二、函数参数

ArkTS对函数参数做了灵活扩展,支持必选参数、可选参数、默认值参数、rest参数,其中可选参数、默认值参数、rest参数均需遵循特定的语法规则,且只能出现在必选参数之后

1. 可选参数

调用函数时可省略的参数 语法:参数名?: 类型 省略时参数值为undefined,需在函数体内做判空处理。

  • 注意:可选参数必须跟在必选参数后面,不能放在必选参数之前。
// 示例
function hello(name?: string) {
  if (name == undefined) {
    console.info('Hello!');   // 省略参数时执行
  } else {
    console.info(`Hello, ${name}!`);   // 传入参数时执行
  }
}
hello();   // 省略参数 输出:Hello!
hello('ArkTS');   // 传入参数 输出:Hello, ArkTS!
2. 默认值参数

可选参数的另一种形式,为参数设置默认值,调用时若省略该参数,会自动使用默认值作为实参. 语法:参数名: 类型 = 默认值

  • 注意:默认值参数也需跟在必选参数之后,若调用时传入新值,会覆盖默认值。
// 示例:定义乘法函数,系数coeff设置默认值2。
function multiply(n: number, coeff: number = 2): number {
  return n * coeff;
}
multiply(2);   // 省略coeff,使用默认值2,返回:4。
multiply(2, 3);   // 传入coeff=3,覆盖默认值,返回:6。
3. rest参数

用于处理不定数量的参数输入,允许函数接收一个不定长的数组。 语法...rest参数名: 类型[] 核心规则:

  1. rest参数只能是函数的最后一个参数
  2. rest参数的类型必须是数组类型
  3. 调用时可传入0个、1个或多个同类型参数,均会被封装为数组。
// 示例:定义求和函数,支持传入任意多个number类型参数.
function sum(...numbers: number[]): number {
  let res = 0;
  // 遍历rest参数数组,累加求和
  for (let n of numbers) {
    res += n;
  }
  return res;
}
sum();   // 传入0个参数 返回:0
sum(1, 2, 3);   // 传入3个参数 返回:6
sum(10, 20, 30, 40);   // 传入4个参数 返回:100

三、函数返回类型

ArkTS中函数的返回类型支持显式指定自动推断,无返回值时可指定为void类型. 核心规则:

  1. 若函数体的返回结果可被编译器推断,可省略返回类型标注。
  2. 无返回值的函数,可显式指定void或省略返回类型。
  3. 显式指定返回类型后,函数返回的结果必须与该类型匹配,否则编译报错。
1. 显式指定返回类型

适用于需要明确函数返回值类型的场景,提升代码的可读性和规范性。

// 显式指定返回string类型
function foo(): string { 
  return 'foo'; 
}
// 显式指定返回number类型
function getNum(): number {
  return 100;
}
2. 自动推断返回类型

编译器可根据函数体的return语句自动推断返回类型,可省略返回类型标注,简化代码。

// 自动推断返回类型为string
function goo() { 
  return 'goo'; 
}
// 自动推断返回类型为boolean
function isTrue() {
  return 1 > 0;
}
3. 无返回值(void类型)

表示函数无任何返回结果,即使写return也不能携带值,两种声明方式均可,效果一致。

// 方式1:省略返回类型标注(简洁写法)
function hi1() { 
  console.info('hi'); 
}

// 方式2:显式指定void类型(规范写法)
function hi2(): void { 
  console.info('hi'); 
  // 无返回值的函数可写空return,不可写return 123(编译报错)。
  return;
}
4. 拓展:never类型

表示函数永远不会执行完成(如函数内抛出异常、无限循环),是比void更严格的返回类型,基础开发中使用较少,做简单了解即可。

function errorFunc(): never {
  throw new Error('程序执行异常');
}

四、函数的作用域

ArkTS中函数遵循块级作用域规则,函数内定义的变量/实例为局部变量,函数外定义的为全局变量。 核心规则:

  1. 局部变量仅能在函数内部访问,外部无法访问。
  2. 若函数内的局部变量与全局变量同名,局部变量会覆盖全局变量(函数内优先使用局部变量)。
  3. 函数内定义变量时,必须使用let/const,否则会被提升为全局变量。
// 定义全局变量
let outerVar = 'I am outer ';
function func() {
    // 定义同名局部变量,覆盖全局变量。
    let outerVar = 'I am inside';
    console.info(outerVar);   // 输出:I am inside(使用局部变量)
}
func();
console.info(outerVar);   // 输出:I am outer(全局变量未被修改)

// 易错点:函数内未用let/const定义的变量,会成为全局变量。
function badFunc() {
  testVar = '全局变量';   // 未用let/const,提升为全局变量
}
badFunc();
console.info(testVar);   // 输出:全局变量

五、函数的调用

函数定义后,需通过 函数名(实参) 的方式调用,才能执行函数体代码。 核心规则:

  1. 实参的数量、类型需与形参严格匹配(可选参数、rest参数除外)。
  2. 函数调用时,实参会按顺序赋值给形参。
  3. 带返回值的函数,调用时可通过变量接收返回结果,也可直接调用(忽略返回结果)。
// 定义函数:拼接两个字符串
function join(x: string, y: string): string {
  let z: string = `${x} ${y}`;
  return z;
}

// 方式1:接收返回值(推荐)
let res = join('hello', 'world');
console.info(res);   // 输出: hello world

// 方式2:直接调用,忽略返回结果。
join('Hi', 'ArkTS');
拓展:参数的传递方式

ArkTS中参数传递分为值传递引用传递,取决于参数的类型。

  • 值传递:适用于基本类型(number、string、boolean、undefined、null),传递的是变量的副本,函数内修改副本不会影响原变量。
  • 引用传递:适用于引用类型(对象、数组),传递的是变量的内存地址,函数内修改对象/数组的属性,会影响原变量。
// 值传递(基本类型)
function changeNum(num: number) {
  num = 100;
}
let a = 10;
changeNum(a);
console.log(a);   // 输出:10(原变量未被修改)

// 引用传递(引用类型)
function changeObj(obj: {name: string}) {
  obj.name = 'ArkTS';
}
let person = {name: '鸿蒙'};
changeObj(person);
console.log(person.name);   // 输出:ArkTS(原对象被修改)

六、函数类型

ArkTS中函数也是一种数据类型,可通过type关键字定义函数类型,用于约束函数的参数列表返回类型最常用的场景是定义回调函数,让代码的类型约束更严格。

函数类型的定义语法
type 函数类型名 = (参数1: 类型1, 参数2: 类型2, ...) => 返回类型;   // 语法格式
示例:定义回调函数
// 定义函数类型:接收2个number类型参数,返回number类型结果。
type AddFunc = (a: number, b: number) => number;

// 定义函数,参数是AddFunc类型的回调函数。
function calculate(a: number, b: number, back: AddFunc) {
  return back(a, b);   // 调用回调函数 传入a和b
}

// 传入符合AddFunc类型的函数作为实参(求和)
const add = (x: number, y: number) => x + y;
console.log(calculate(2, 3, add));   // 输出:5(符合规矩 正常执行)

// 若传不符合规矩的函数(比如返回string),编译直接报错(这就是函数类型的作用!)。
const badAdd = (x: number, y: number) => '${x+y}';
calculate(2,3, badAdd);   // 报错:返回类型string不符合AddFunc的number类型
函数类型的赋值

定义函数类型后,可将符合该类型的函数赋值给变量,约束变量的函数形态。

// 定义函数类型
type AddFunc = (a: number, b: number) => number;

// 定义符合该类型的函数
let add: AddFunc = (x, y) => x + y;

// 调用
console.log(add(1,2));   // 输出:3

七、箭头函数(Lambda函数)

箭头函数是普通函数的简洁写法,语法更精炼、代码更简洁,适合编写短小的函数逻辑,也是鸿蒙开发中高频使用的写法。

  • 核心特点:无自己的this(继承外层作用域的this)
1. 基本语法
// 完整语法
let 函数名 = (形参1: 类型1, 形参2: 类型2) => {
  函数体;
  return 结果;
}

简写规则:

  1. 单表达式函数体,可省略大括号{}return
  2. 单个形参,可省略小括号()
  3. 无参或多个形参,必须保留小括号()
  4. 返回类型可省略,由编译器自动推断。
2. 常见使用形式
(1)无参箭头函数
// 完整写法
let sayHi = () => {
  console.log('Hi, 箭头函数');
};
// 调用
sayHi();   // 输出:Hi,箭头函数

// 无参+单表达式(若有返回值)
let getStr = () => 'Hello Arrow Function';
console.log(getStr());   // 输出:Hello Arrow Function
(2)带参箭头函数
// 多个形参完整写法
let multiply = (num1: number, num2: number): number => {
  return num1 * num2;
};
let res = multiply(5, 6);
console.log(res);   // 输出:30

// 单个形参简写(省略小括号、大括号、return)
let double = (n: number) => n * 2;   // 标准简写(推荐写法)
let double2 = n => n * 2;   // 不推荐写法

// 多个形参 单表达式 简写
let sum = (a: number, b: number) => a + b;
console.log(sum(3,4));   // 输出:7
3. 箭头函数的核心特性

箭头函数的核心差异是无自身的this,鸿蒙开发中在组件生命周期、事件回调中使用时需特别注意:

  1. 箭头函数的 this 继承自外层作用域,不会随调用方式改变。(外层无目标属性则输出undefined)
  2. 普通函数的 this 指向调用者,调用方式不同,this 指向不同。(调用者无目标属性也可能输出undefined)
  3. 箭头函数不能作为构造函数,不能使用new关键字调用。
// 示例:箭头函数继承外层this
let obj = {
  name: 'ArkTS',
  fn1: function() {
    // 普通函数:this指向obj
    console.log(this.name);
  },
  fn2: () => {
    // 箭头函数:this继承外层(全局作用域)(无name属性)
    console.log(this.name);
  }
};
obj.fn1();   // 调用普通函数fn1  输出:ArkTS
obj.fn2();   // 调用箭头函数fn2  输出:undefined

八、闭包

闭包是由函数声明该函数的环境组合而成的整体,该环境包含了闭包创建时作用域内的所有局部变量。

  • 核心特性:闭包可以访问并保留外层函数的局部变量,即使外层函数执行完毕,局部变量也不会被销毁
1. 闭包的实操示例
// 外层函数f
function f(): () => number {
  let count = 0;   // 外层函数的局部变量
  // 内层箭头函数g 形成闭包
  let g = (): number => { 
    count++;   // 访问外层函数的局部变量count
    return count; 
  };
  return g;   // 返回内层函数g
}

let z = f();   // 外层函数执行完毕 返回内层函数g
// 多次调用z 闭包保留count的状态 持续递增
console.log(z());   // 返回:1
console.log(z());   // 返回:2
console.log(z());   // 返回:3
2. 闭包的使用场景
  1. 封装私有变量:隐藏内部变量,仅通过闭包暴露操作方法,避免全局变量污染。
  2. 保留函数执行状态:如闭包实操示例中的计数器,多次调用可保留上一次的执行结果。
  3. 鸿蒙开发中:组件事件回调、定时器中保留上下文状态。
3. 闭包的注意事项

闭包会保留外层函数的变量,导致变量不会被垃圾回收,若过度使用或不当使用,会造成内存泄漏。 注意事项:

  1. 避免在循环中创建闭包。
  2. 不需要使用闭包时,及时解除引用(如将闭包变量赋值为null)。
4. 闭包封装私有变量示例
// 封装计数器 仅暴露增/减方法 隐藏count变量
function createCounter() {
  let count = 0;   // 私有变量 外部无法访问
  return {
    add: () => count++,   // 闭包:增加计数
    sub: () => count--,   // 闭包:减少计数
    get: () => count      // 闭包:获取计数
  };
}

let counter = createCounter();
counter.add();
counter.add();
console.log(counter.get());   // 输出:2
counter.sub();
console.log(counter.get());   // 输出:1
// 外部无法直接修改count 避免变量污染

九、函数重载

函数重载指为同一个函数定义多个不同的签名(参数列表/返回类型不同),让同一个函数支持多种调用方式。 在ArkTS中编译器会根据实参的类型/数量,匹配对应的重载签名执行。

1. 函数重载的语法规则
  1. 先编写多个重载声明(同名、不同签名、无函数体)
  2. 最后编写一个函数实现(必须包含所有重载声明的参数/返回类型,用联合类型表示)。
  3. 重载声明的参数列表不能相同,否则编译报错。
  4. 函数实现的参数类型需覆盖所有重载声明的参数类型,返回类型也需匹配。
2. 函数重载的实操示例
// 重载声明1:接收一个number类型参数 无返回值
function foo(x: number): void;

// 重载声明2:接收一个string类型参数 无返回值
function foo(x: string): void;

// 函数实现:参数类型为 “ number | string ”  覆盖所有重载声明
function foo(x: number | string): void {
  if (typeof x === 'number') {
    console.log('数字类型:', x);
  } else {
    console.log('字符串类型:', x);
  }
}

// 调用:编译器自动匹配对应的重载声明
foo(123);    // 匹配重载1  输出: 数字类型: 123
foo('aa');   // 匹配重载2  输出: 字符串类型: aa
3. 带返回值的函数重载示例
// 重载声明1:两个number参数 返回number
function calc(a: number, b: number): number;

// 重载声明2:两个string参数 返回string
function calc(a: string, b: string): string;

// 函数实现
function calc(a: number | string, b: number | string): number | string {
  if (typeof a === 'number' && typeof b === 'number') {
    return a + b;
  } else {
    return `${a}${b}`;
  }
}

console.log(calc(1,2));   // 输出:3
console.log(calc('a','b'));   // 输出:ab
4. 函数重载的注意事项
  1. 函数重载仅在编译阶段生效,用于做类型校验,运行时只有一个函数实现。
  2. 不能只有重载声明而无函数实现,否则编译报错。
  3. 鸿蒙开发中,函数重载常用于封装通用方法,让方法支持多种参数类型,提升代码的灵活性。