TypeScript 函数

函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义 行为 的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。

在 TypeScript 中,函数是用于执行特定任务并可能返回结果的代码块。

函数类型

可以为函数定义类型来明确函数的参数类型和返回值类型。

// 有名字的函数
function sum(a: number, b: number): number {
  return a + b;
}
// 匿名函数
let mySum = function(x: number, y: number): number { return x + y; };

这里明确了 sum 函数接受两个 number 类型的参数 ab,并返回一个 number 类型的值。

书写完整函数类型

函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的。

在函数和返回值类型之前使用( =>)符号。

返回值类型是函数类型的必要部分,如果函数没有返回任何值,必须指定返回值类型为 void 而不能留空。

let mySum: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };

在这段代码中:

  • let mySum: (x: number, y: number) => number 这部分定义了一个变量 mySum,并指定了它的类型为接受两个 number 类型参数并返回 number 类型值的函数类型。
  • 然后通过 function(x: number, y: number): number { return x + y; }mySum 变量赋值了一个具体的函数实现。

以参数列表的形式写出参数类型,为每个参数指定一个名字和类型:

let mySum: (a: number, b: number) => number =
    function(x: number, y: number): number { return x + y; };

在这段代码中,虽然函数定义中的参数名 ab 与函数实现中的参数名 xy 不同,但它们的类型都是 number,这是匹配的。

TypeScript 在类型检查时关注的是参数的类型,而不是参数的名称。只要参数的类型符合函数类型定义的要求,代码就可以通过类型检查。

在定义函数的类型时,只考虑参数的类型和返回值的类型。

函数中使用的捕获变量不会体现在类型里。 实际上,这些变量是函数的隐藏状态并不是组成API的一部分。
这是因为这些捕获变量属于函数的内部实现细节,对于使用该函数的外部代码来说,并不需要知道这些内部的状态。函数的 API 主要是通过其公开的参数和返回值来与外部进行交互和通信。
示例:

let outerValue = 10;

function myFunction(x: number): number {
    let innerValue = 5;
    return x + outerValue + innerValue;
}

在上述 myFunction 函数中,outerValue 是捕获的外部变量,innerValue 是函数内部的变量,它们都不会体现在函数的类型 (x: number) => number 中。

推断类型

如果在赋值语句的一边指定了类型,但是另一边没有类型的话,TypeScript编译器会自动识别出类型:

let mySum = function(x: number, y: number): number { return x + y; };

在这个例子中,mySum 的类型会被推断为 (x: number, y: number) => number
在这里插入图片描述

可选参数和默认参数

JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined

TypeScript里的每个函数参数都是必须的。传递给一个函数的参数个数必须与函数期望的参数个数一致。

在 TypeScript 中,可选参数和默认参数为函数提供了更灵活的参数设置方式。

可选参数

通过在参数名后面添加 ? 来标记参数为可选参数。调用函数时,可以选择不传递该参数。
示例:

function printInfo(name: string, age?: number) {
    console.log(`Name: ${name}, Age: ${age ?? 'no age'}`);
}

printInfo("张三");      // "Name: 张三, Age: no age"
printInfo("李四", 25);  // "Name: 李四, Age: 25" 
printInfo("王二", 0);   // "Name: 王二, Age: 0" 

可选参数必须跟在必须参数后面。
这是为了确保在调用函数时,必需的参数能够按照正确的顺序被传递,而不会因为可选参数的存在导致参数传递的混乱。

默认参数

可以为参数指定一个默认值,如果调用函数时没有传递该参数或传递的值是undefined时,就会使用默认值。

function printInfo(name: string, age: number = 18) {
    console.log(`Name: ${name}, Age: ${age}`);
}
printInfo("张三");      // "Name: 张三, Age: 18" 
printInfo("李四", 25);  // "Name: 李四, Age: 25" 
printInfo("王二", undefined);   // "Name: 王二, Age: 18" 

当一个参数在所有必需参数之后并且有默认初始化值时,它就具有了可选的性质。
printInfo 函数中,age 参数具有默认值 18 ,所以在调用函数时可以省略它,就像可选参数一样。

与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值。
示例:

function printInfo(name: string = "张三", age: number) {
    console.log(`Name: ${name}, Age: ${age}`);
}
printInfo(undefined, 18);   // "Name: 张三, Age: 18" 
printInfo("李四", 25);      // "Name: 李四, Age: 25" 

// 假如只传入 age 
printInfo(25); // Error: Expected 2 arguments, but got 1.

剩余参数

必要参数、默认参数、可选参数有个共同点:它们表示某一个参数。
有时,想同时操作多个参数,或者并不知道会有多少参数传递进来。 在JavaScript里,你可以使用 arguments来访问所有传入的参数。

在 TypeScript 中,剩余参数用于表示不确定数量的参数。它使用 ... 语法来定义。
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。

function sumAll(numbers: number[], ...restNum: number[]) {
  let total = 0;
  for (const num of numbers) {
    total += num;
  }
  for (const num of restNum) {
    total += num;
  }
  return total;
}

// 没有剩余参数
console.log(sumAll([1, 2, 3])); // 6

// 有 3 个剩余参数
console.log(sumAll([1, 2, 3], 4, 5, 6));  // 21

在为带有剩余参数的函数定义类型时,也需要正确地表示剩余参数的部分。
实现 sumAll 函数的类型定义:

let myFunction: (numbers: number[], ...restNum: number[]) => number = sumAll;

// 参数名字可以不一样,但是参数的类型必须一样
let myFunc: (nums: number[], ...rest: number[]) => number = sumAll;

this和箭头函数

在普通函数中,this 的值取决于函数的调用方式。如果是作为对象的方法被调用,this 指向该对象;如果是独立调用,非严格模式下,this 通常指向全局对象(在浏览器环境中是 window,在 Node.js 环境中是 global)。严格模式下, thisundefined

非严格模式下:

// 作为对象的方法被调用
let obj = {
  method: function() {
    console.log(this);  // 这里的 this 指向 obj 对象
  }
};
obj.method();

// 独立调用
function standalone() {
  console.log(this);  // 在浏览器中,这里的 this 指向 window 对象
}
standalone();

严格模式下:

// 作为对象的方法被调用
let strictObj = {
  strictMethod: function() {
    "use strict";
    console.log(this);  // 这里的 this 指向 strictObj 对象
  }
};
strictObj.strictMethod();

// 独立调用
function strictStandalone() {
  "use strict";
  console.log(this);  // 这里的 this 是 undefined
}
strictStandalone();

可以在浏览器中执行代码来验证结果。

箭头函数中的 this 继承自其定义时所在的上下文,不会因为调用方式的不同而改变。

let obj = {
  method: () => {
  	// "use strict";
    console.log(this); // 在浏览器中,这里的 this 指向 window 对象
  }
};
obj.method();

这段代码中,无论是严格模式还是非严格模式,this 都指向 window 对象。
箭头函数 method 是在对象 obj 中定义的,由于是箭头函数,this 并不指向 obj ,而是继承了外部(即全局)的 this 值。在浏览器环境中,全局的 this 就是 window 对象。

使 this 指向obj

let obj = {
  width: 10,
  height: 10,
  method() {
    return () => {
      return console.log(this)
    }
  }
};
obj.method()();

// 把obj.method()赋值给method变量
let method = obj.method();
method()

在这里插入图片描述
由上图运行结果可知,箭头函数里的 this 指向obj。此时,this 的 类型 是 any

this 参数

在 TypeScript 中,this 可以被显式地作为参数添加到函数的参数列表中,用于更明确地控制和处理函数内部的 this 指向。

提供一个显式的 this 参数, 让this 的类型不是any

interface OBJ {
  width: number;
  height: number;
  method(this: OBJ): () => number;
}

let obj: OBJ = {
  width: 10,
  height: 10,
  method(this: OBJ) {
    return () => {
      return this.width + this.height
    }
  }
};
console.log(obj.method()()); // 20

let method = obj.method(); 
console.log(method); // 20

这段代码中,thisOBJ 类型的。不论如何调用method()this始终指向OBJ

this参数在回调函数里

当把一个函数作为回调传递给库函数,并且在回调函数中使用了 this 时,确实经常会遇到 this 指向错误或为 undefined 的问题。
这是因为函数的调用方式会影响 this 的指向。如果回调函数被库函数以普通函数的方式调用,而不是作为对象的方法调用,那么 this 就不会指向预期的对象,通常会是 undefined
例如,假设有一个库函数 doAsyncOperation ,它接受一个回调函数:

function doAsyncOperation(callback: () => void) {
  // 模拟异步操作完成后调用回调
  setTimeout(callback, 1000);
}

然后有一个类 MyClass ,其中有一个方法想要在回调中使用 this

class MyClass {
  data: string = "Some data";

  performOperation() {
    doAsyncOperation(this.someCallback);
  }

  someCallback() {
    console.log(this.data);  // undefined
  }
}
let myC = new MyClass();
myC.performOperation();

执行myC.performOperation()语句, doAsyncOperation 内部以普通函数的方式调用 callback ,当 doAsyncOperation 调用回调函数时,this 不是指向 MyClass 的实例,而是 undefined ,导致无法访问 data 属性。回调函数的输出结果为undefined

因为在 setTimeout 内部,如果传递的回调函数是一个普通函数,那么 this 的指向取决于运行环境和调用方式。

在非严格模式下,如果是在浏览器环境中,this 通常指向 window 对象;在 Node.js 环境中,this 通常指向 global 对象。
在严格模式下,this 的值会是 undefined

someCallback 改为箭头函数:

class MyClass {
  data: string = "Some data";

  performOperation() {
    doAsyncOperation(this.someCallback);
  }

  someCallback = () => {
    console.log(this.data);  // "Some data"
  }
}
let myC = new MyClass();
myC.performOperation();

在上述代码中,someCallback 被定义为箭头函数。
箭头函数不会创建自己的 this 上下文,而是继承外层(即定义它的 MyClass 实例)的 this 上下文。
doAsyncOperation 函数中的异步操作完成并调用 callback (即 this.someCallback )时,由于 someCallback 是箭头函数,它所使用的 this 仍然是其定义时所在的 MyClass 实例的 this
所以,在 someCallback 函数内部,能够正确访问到 MyClass 实例的属性 data ,并将其打印出来,即输出结果为 “Some data” 。

或者:

class MyClass {
  data: string = "Some data";

  performOperation() {
    doAsyncOperation(() => this.someCallback());
  }

  someCallback() {
    console.log(this.data);  // "Some data"
  }
}
let myC = new MyClass();
myC.performOperation();

doAsyncOperation(() => this.someCallback()); 中,使用了一个立即执行的箭头函数。
箭头函数不会创建自己的 this 上下文,而是继承外层(即定义它的 MyClass 实例)的 this 上下文。
在这个箭头函数内部,this 正确地指向了 MyClass 的实例。

上面这个例子比较简单,再举一个复杂一点的例子。
例如,假设有一个库函数 UIElement ,库函数的作者要指定 this的类型:

interface UIElement {
  addClickListener(onclick: (this: void, e: CustomEvent,  params?: {[propName: string]: any}) => void): void;
}

有一个Handler类,处理自定义事件以及在不同类型的回调函数中处理数据和 this 的指向。

class Handler {
  info: string = "";

  onClickGood(this: void, e: CustomEvent) {
    // 通过指定 this: void ,表明该回调函数在被调用时不期望或不依赖于特定的 this 上下文。
    console.log(this); // undefined
    console.log("clicked!");
  }

  onClickGood2 = (e: CustomEvent) => {
    // 箭头函数,this 指向 Handler
    this.info = e.detail.message;
  };

  doOther (this: void, e: CustomEvent, params?: {[propName: string]: any}) {
    // 在这里根据传入的 params 进行相应的处理
    // 如果要操作this,就使用箭头函数。
    console.log(e.detail.message);
    console.log(params);
  };
}

let h = new Handler();
let uiElement: UIElement = {
  addClickListener(onclick) {
    const event = new CustomEvent("click", { detail: { message: "Hello" } });
    onclick(event);
  }
};

uiElement.addClickListener(h.onClickGood);
uiElement.addClickListener(h.onClickGood2);


let uiElementDo: UIElement = {
  addClickListener(onclick) {
    const event = new CustomEvent("click", { detail: { message: "输出用户的输入" } });
    let word = prompt("输入你想说的话", "");
    let params = {
      word: word
    }
    onclick(event, params);
  }
};

uiElementDo.addClickListener(h.doOther)

函数重载

在 TypeScript 中,函数重载是指可以为同一个函数定义多个不同的签名(参数类型和返回类型的组合)。
示例:

function add(a: number, b: number): number;
function add(a: string, b: string): string;

function add(a: any, b: any): any {
  if (typeof a === 'number' && typeof b === 'number') {
    return a + b;
  }
  if (typeof a ==='string' && typeof b ==='string') {
    return `${a} ${b}`;
  }
  throw new Error('Invalid parameters');
}

console.log(add(1, 2));  // 3
console.log(add('hello', 'world')); // "hello world" 

在上述示例中,add 函数有两个不同的函数签名:一个接受两个数字参数并返回一个数字,另一个接受两个字符串参数并返回一个字符串。

函数的实现部分根据传入参数的类型来决定执行不同的逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值