English
函数是编程语言常见的功能,它可以封装一批代码,对外接收参数,然后返回值。被封装的逻辑,可以被不同的其他代码调用,达到共同复用逻辑的目的。
函数用 function
关键字定义,后面跟着函数名和圆括号。
同时注意,定义函数涉及作用域。
函数可以通过函数声明和匿名函数表达式两种方式进行定义。这两种方式在语法上有所不同,但都可以用来创建函数。
一个函数定义(也称为函数声明,或函数语句)由一系列在 function 关键字后的内容组成,依次为:
{}
括起来。例如,以下的代码定义了一个简单的函数。函数名为 add,有2个参数 x 和 y,都是 string类型,函数的返回值类型也是 string。
函数的内容是将入参 x 和 y 相加,赋值给变量z,然后通过 return关键字返回z。
function add(x :string, y :string) :string {
let z : string = x + " " + y
return z;
}
虽然上面的函数声明在语法上是一个语句,但函数也可以由函数表达式创建。这样的函数可以是匿名的。
例如,函数 add 也可这样来定义:
const add = function (x: string, y: string): string {
return x + " " + y
}
UTS 中不存在变量提升,在函数表达式中不可以访问未声明的变量(包括自身)。
const fn = function () {
console.log(fn) // 编译报错,此时 fn 还未声明
}
fn()
const fn: (() => void) | null = null
fn = function () {
console.log(fn) // 此时 fn 可以正常访问
}
fn()
箭头函数表达式(也称胖箭头函数)相比函数表达式具有较短的语法。箭头函数总是匿名的,也就是不需要函数名。
// 以下是不使用箭头函数的写法
const arr = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
const a2 = arr.map(function (s): number {
return s.length;
});
console.log(a2); // logs [ 8, 6, 7, 9 ]
// 以下是使用箭头函数的写法,逻辑是等价的
const a3 = arr.map((s): number => s.length);
console.log(a3); // logs [ 8, 6, 7, 9 ]
如果这个函数不需要返回值,可以省略返回值类型也可以使用 void 关键字进行显式声明,同时函数内部末尾也可以省略 return。
function add(x: string, y: string): void {
let z: string = x + " " + y
console.log(z)
// return
}
使用 async 关键字来声明一个异步函数,异步函数返回一个 Promise 对象。
注意:在 HBuilderX 3.93 以下的版本或者 iOS 平台,异步函数返回的不是 Promise 对象,请分别参考:安卓 异步函数、iOS 异步函数。
注意:异步函数在底层使用协程实现,异步函数内与异步函数外同时操作同一个对象时,由于其能并发执行,其操作顺序可能与预期不一致,会产生竞态条件与线程安全性问题。
async function foo(): Promise<void> {
// ...
}
foo().then(function() {
console.log('done')
})
async 函数可能包含 0 个或者多个 await await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 async / await 关键字就可以在异步代码中使用普通的 try / catch 代码块。
async function foo(): Promise<void> {
try {
await aPromise
} catch (error) {
console.log(error)
}
}
async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
async function foo(): Promise<number> {
return 1
}
定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。调用函数才会以给定的参数真正执行这些动作。
定义了函数 add 后,你可以如下这样调用它:
function add(x :string, y :string) :string {
let z :string = x + " " + y
return z;
}
add("hello", "world"); // 调用add函数
上述语句通过提供参数 "hello" 和 "world" 来调用函数。
虽然调用了add函数,但并没有获取到返回值。如需要获取返回值,需要再赋值:
function add(x :string, y :string) :string {
let z :string = x + " " + y
return z;
}
let s :string = add("hello", "world");
console.log(s) // hello world
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。
const hello :string = "hello";
const world :string = "world";
function add(): string {
let s1 :string = "123";
return hello + world; // 可以访问到 hello 和 world
}
你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数)。
既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
可以总结如下:
举例:
function addSquares(a: number, b: number): number {
function square(x: number): number {
return x * x;
}
return square(a) + square(b);
}
addSquares(2, 3); // returns 13
addSquares(3, 4); // returns 25
addSquares(4, 5); // returns 41
当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。
举例:
function outside(): (x: number) => number {
let x = 5;
const inside = function (x: number): number {
return x * 2;
};
return inside;
}
outside()(10); // 返回值为 20 而不是 10
命名冲突发生在 return x
上,inside 的参数 x 和 outside 变量 x 发生了冲突。这里的作用链域是 {inside, outside}
。因此 inside 的 x 具有最高优先权,返回了 20(inside 的 x)而不是 10(outside 的 x)。
uts 允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。
但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。
此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
举例:
const pet = function (name: string): () => string {
//外部函数定义了一个变量"name"
const getName = function (): string {
//内部函数可以访问 外部函数定义的"name"
return name;
};
//返回这个内部函数,从而将其暴露在外部函数作用域
return getName;
};
const myPet = pet("Vivie");
myPet(); // 返回结果 "Vivie"
使用函数表达式(Function Expression)的创建的函数可以作为值进行传递,使用函数声明(Function Declaration)创建的函数不能作为一个值进行传递。
function fn1() {
}
const fn2 = function () {
}
const fn3 = fn2 // 正确,值为函数表达式
const fn4 = fn1 // 错误,值为函数声明
console.log(fn1) // 错误,值为函数声明
console.log(fn2) // 正确,值为函数表达式
console.log(function () {
}) // 正确,值为函数声明
函数表达式也拥有类型,其类型表达式为 (参数名:参数类型)=>返回值类型
,如:
const fn: (x: string) => void
函数表达式必须与其类型定义中的参数类型、参数个数以及返回值类型严格匹配
fn = function (x: string) { } // 正确,类型匹配
fn = function (x: string, y: string) { } // 错误,参数类型不匹配
fn = function (x: string): string { return x } // 错误,返回类型不匹配
函数参数可以设默认值,当省略相应的参数时使用默认值。此时该参数也就成了可选参数。
function multiply(a:number, b:number = 1):number {
return a*b;
}
multiply(5); // a为5,未传b的值,b默认为1,结果为5
可以在常规的函数定义的场景下使用默认参数,例如:
function print(msg:string = "") {
console.log(msg)
}
print(); // ""
class Persion {
test(msg: string | null = null) { // 默认值可以为null
}
}
通过函数表达式定义的函数不支持使用默认参数
// 该变量会被编译成Swift或者Kottlin的闭包表达式,其不支持使用默认参数。
const test = function(msg: string | null) { }
因为需要作为值进行传递,对象字面量中定义的方法会自动转换为函数表达式,所以也不支持默认参数
// 该属性会被编译成Swift或者Kottlin的闭包表达式,其不支持使用默认参数。
export {
data: {
test(msg: string | null) {
}
}
}
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
使用 ...
操作符定义函数的最后一个参数为剩余参数。
function fn(str: string, ...args: string[]) {
console.log(str, args)
}
在这种情况下可以传递可变数量的参数给函数:
fn('a') // 'a' []
fn('a', 'b', 'c') // 'a' ['b', 'c']
也可以使用展开语法传递一个数组值给函数的剩余参数(转到 swift 时未支持):
fn('a', ...['b', 'c']) // 'a' ['b', 'c']