当前位置: 首页 > java >正文

初探函数使用

文章目录

  • 一、函数基础
    • (一)函数类型定义
      • 1. 函数类型的基础定义方式
        • (1)函数变量添加类型注解
        • (2)使用接口来定义函数类型
        • (3)使用类型别名定义函数类型
      • 2. 参数类型的详细定义
        • (1)必需参数
        • (2)可选参数
        • (3)默认参数
        • (4)剩余参数
      • 3. 返回值类型的定义
        • (1)显式指定返回值类型
        • (2)void 返回值类型
        • (3)never 返回值类型
      • 4. 函数类型的应用场景
        • (1)作为参数类型
        • (2)作为返回值类型
    • (二)函数重载
      • 1. 为什么需要函数重载
      • 2. 函数重载的语法
      • 3. 重载签名 vs 实现签名
      • 4. 重载签名的匹配规则
      • 5. 常见应用场景
        • 5.1 参数可选 / 数量不同
        • 5.2 返回不同类型
      • 6. 与联合类型的对比
      • 7. 注意事项
    • (三)泛型函数
      • 1. 泛型函数的基本概念
        • (1)基础语法
        • (2)调用方式
      • 2. 泛型类型变量的命名规范
      • 3. 泛型约束
        • (1)使用接口实现约束
        • (2)多个类型变量的约束
      • 4. 泛型函数与重载
        • 2. **选择策略**
      • 5. 泛型函数与非泛型函数的对比
        • (1)非泛型函数
          • 类型安全失效
        • (2)泛型函数
          • 保留类型信息
          • 支持任意类型
          • 灵活应用
      • 6. 泛型默认类型参数
      • 7. 注意事项
        • (1)避免过度使用泛型
        • (2)确保类型变量使用正确
    • (四)高阶函数
      • 1. 高阶函数的类型定义
        • (1)函数作为参数
        • (2)函数作为返回值
      • 2. 泛型高阶函数
        • (1)泛型函数作为参数
        • (2)泛型函数作为返回值
      • 3. 高阶函数的应用场景
        • (1)数组处理
        • (2)异步操作
        • (3)函数装饰器
        • (4)柯里化
      • 4. 高阶函数的类型约束
      • 5. 高阶函数的实践
        • (1)明确类型定义
        • (2)使用泛型提高复用性
        • (3)避免过度使用
    • (五)this参数
      • 1. 全局作用域中的 `this`(普通函数调用)
      • 2. 方法调用
        • **2.1 实例方法中的 `this`**
        • **2.2 静态方法中的 `this`**
      • 3. `call`、`apply` 和 `bind` 方法
        • (1)显式声明 `this` 类型
        • (2)使用箭头函数
        • (3)使用类语法
      • 4.函数参数的`this`
        • **4.1类型为`void `的`this`**
        • **4.2类型为非`void `的`this`**
      • 5. 箭头函数
        • **5.1实例方法中的 `this`**
        • **5.2对象中的 `this`**
      • 6. `this` 的类型注解与常见问题
        • 6.1 类方法的 `this` 类型
      • 7. 实践
        • 7.1 使用箭头函数保留上下文
        • 7.2 在构造函数中绑定方法
      • **5. 常见陷阱与解决方案**
        • **5.1 回调函数中的 `this` 丢失**
          • **1. 问题示例:`this` 丢失**
          • **2. 解决方案一:使用箭头函数**
          • **3. 解决方案二:使用 `bind` 方法**
          • **4. 在构造函数中绑定**
        • **5.2 静态方法误用 `this`**
    • (六)回调函数
      • **1. 核心定义与特点**
        • **定义**
        • **本质**
      • **2. 回调函数的常见形式**
        • **2.1 同步回调**
        • **2.2 异步回调**
      • 3. 回调函数的参数类型
        • (1)可选参数
        • (2)泛型回调
      • 4. 回调函数的返回值类型
        • (1)void 返回值
        • (2)非 void 返回值
      • 5. 回调函数的实际应用
        • (1)数组方法中的回调
        • **(2)异步编程**
        • **(3)函数组合与抽象**
      • 6. 回调函数的最佳实践
    • (七)函数的作用域
      • 1. 全局作用域(Global Scope)
        • 示例:
      • 2. 函数作用域(Function Scope)
        • 示例:
      • 3. 块级作用域(Block Scope)
        • 示例:
      • 4. 闭包(Closure)
        • 示例:
      • 5. 词法作用域(Lexical Scope)
        • 示例:
      • 6. 箭头函数与作用域
        • 示例:
      • 7. 作用域链(Scope Chain)
      • 8. 变量提升(Hoisting)
      • 9. TypeScript 特有的作用域规则
        • (1)模块作用域
        • (2)命名空间(Namespace)
    • (八)函数的闭包
      • 1. 闭包的基本概念
      • 2. 闭包的作用
        • (1)数据私有化
        • (2)记忆函数
      • 3. 闭包与箭头函数
      • 4. 闭包与作用域链
      • 5. 闭包的注意事项
        • (1)内存管理
        • (2)作用域混淆

一、函数基础

(一)函数类型定义

​ 在 TypeScript 里,函数类型定义能让你清晰地规定函数的参数类型以及返回值类型,进而增强代码的可靠性与可维护性。

1. 函数类型的基础定义方式

(1)函数变量添加类型注解

这段代码定义了一个函数类型的变量 add,它接收两个 number 类型的参数,返回值也是 number 类型。随后,将一个符合该类型定义的函数赋值给 add 变量。

let add: (x: number, y: number) => number;add = function(a: number, b: number) {return a + b;
};
(2)使用接口来定义函数类型

借助接口,也能对函数类型进行定义:

interface AddFunction {(x: number, y: number): number;
}let add: AddFunction = function(a, b) {return a + b;
};

这里,AddFunction 接口把函数的形状给定义好了,要求函数必须接收两个 number 类型的参数,并且返回 number 类型的值。

(3)使用类型别名定义函数类型

类型别名同样可用于函数类型的定义:

type AddFunction = (x: number, y: number) => number;let add: AddFunction = (a, b) => a + b;

AddFunction 类型别名和前面接口的作用是一样的,都对函数的参数类型和返回值类型做了约束。

2. 参数类型的详细定义

(1)必需参数

在调用函数时,必须为这些参数提供值:

function greet(name: string): string {return `Hello, ${name}`;
}greet("Alice"); // 正确
// greet();     // 错误,缺少必需参数
(2)可选参数

在参数名后面加上 ?,将其设置为可选参数:

function greet(name?: string): string {return name ? `Hello, ${name}` : "Hello";
}greet();      // 正确,返回 "Hello"
greet("Bob"); // 正确,返回 "Hello, Bob"

需要注意的是,可选参数必须放在必需参数的后面

(3)默认参数

为参数设置默认值后,在调用函数时如果没有为该参数传值,就会使用这个默认值:

function greet(name: string = "Guest"): string {return `Hello, ${name}`;
}console.log(greet());      // 返回 "Hello, Guest"
console.log(greet("Eve")); // 返回 "Hello, Eve"
(4)剩余参数

剩余参数可以用 ...args: Type[] 的形式来表示,它允许函数接收任意数量的同类型参数:

function sum(...numbers: number[]): number {return numbers.reduce((ans, num) => ans + num, 0);
}sum(1, 2, 3);    // 返回 6
sum(1, 2, 3, 4); // 返回 10

3. 返回值类型的定义

(1)显式指定返回值类型

明确地为函数指定返回值类型:

function getFullName(firstName: string, lastName: string): string {return `${firstName} ${lastName}`;
}
(2)void 返回值类型

当函数没有返回值时,使用 void 类型:

function logMessage(message: string): void {console.log(message);
}
(3)never 返回值类型

如果函数永远不会正常结束执行,就使用 never 类型。比如抛出异常的函数:

function throwError(message: string): never {
throw new Error(message);
}

4. 函数类型的应用场景

(1)作为参数类型

函数类型可以作为其他函数的参数类型:

function applyOperation(a: number, b: number, operation: (x: number, y: number) => number): number {return operation(a, b);
}let result = applyOperation(3, 4, (x, y) => x * y); // 结果为 12console.log(result);
(2)作为返回值类型

函数类型也能作为其他函数的返回值类型:

function createAdder(base: number): (num: number) => number {return (nums: number): number => base + nums;
}let add5 = createAdder(5);
add5(3); // 结果为 8

(二)函数重载

​ 函数重载(Function Overloading)是 TypeScript 中允许一个函数接受不同参数类型和数量,并返回不同类型值的特性。它通过为同一个函数提供多个调用签名来实现类型安全的多态行为。

1. 为什么需要函数重载

  • 静态类型检查:TypeScript 要求函数参数和返回值类型明确。
  • 多态需求:同一个函数可能需要处理不同类型的输入。
  • 代码可读性:避免为相似功能创建多个函数名。

2. 函数重载的语法

TypeScript 的函数重载由两部分组成:

  1. 重载签名:定义多个函数类型(参数和返回值)。
  2. 实现签名:实际实现函数逻辑的单一版本。
// 重载签名(多个)
function add(a: number, b: number): number;
function add(a: string, b: string): string;// 实现签名(一个)
function add(a: number | string, b: number | string): number | string {if (typeof a === 'number' && typeof b === 'number') {return a + b; // 数字相加} else if (typeof a === 'string' && typeof b === 'string') {return a + b; // 字符串拼接}throw new Error("参数类型不匹配");
}// 使用示例
const numResult = add(1, 2); // 返回 number
const strResult = add("a", "b"); // 返回 string
// 函数重载的调用必须严格匹配重载签名(overload signatures)中的某一个,而不考虑函数的实现签名(implementation signature)。
// add(1, "b"); // 错误:重载签名不允许此调用

3. 重载签名 vs 实现签名

  • 重载签名
    • 只声明函数的参数和返回值类型。
    • 可以有多个,用于描述不同的调用方式。
    • 不包含函数体。
  • 实现签名
    • 必须兼容所有重载签名的类型。
    • 参数类型需为所有重载参数的联合类型(如 number | string)。
    • 返回值类型需为所有重载返回值的联合类型。

4. 重载签名的匹配规则

TypeScript 会按重载签名的声明顺序依次匹配:

split() 方法用于将字符串按指定的分隔符(separator)分割成子字符串数组。语法:

str.split(separator, limit);
  • separator:指定分割位置的字符串或正则表达式。
  • limit(可选):限制返回数组的最大长度。
分隔符示例结果
''"hello".split('')["h", "e", "l", "l", "o"]
','"a,b,c".split(',')["a", "b", "c"]
' '"hello world".split(' ')["hello", "world"]
''(含空格)"a b c".split(' ')["a", "", "b", "", "c"]
正则 /\s+/"a b\tc".split(/\s+/)["a", "b", "c"]
function reverse(str: string): string;
function reverse(arr: any[]): any[];
function reverse(arg: string | any[]): string | any[] {return typeof arg === 'string'? arg.split('').reverse().join('') // 反转字符串: arg.slice().reverse(); // 反转数组
}reverse("hello"); // 返回 "olleh"
reverse([1, 2, 3]); // 返回 [3, 2, 1]
// reverse(123); // 错误:没有匹配的重载签名

5. 常见应用场景

5.1 参数可选 / 数量不同
// 重载签名:0个参数(返回默认值)
function add(): number;
// 重载签名:1个参数(返回参数本身)
function add(a: number): number;
// 重载签名:2个参数(返回两数之和)
function add(a: number, b: number): number;
// 重载签名:3个参数(返回三数之和)
function add(a: number, b: number, c: number): number;// 实现签名(使用可选参数)
function add(a?: number, b?: number, c?: number): number {if (a === undefined) return 0; // 无参数if (b === undefined) return a; // 1个参数if (c === undefined) return a + b; // 2个参数return a + b + c; // 3个参数
}// 使用示例
console.log(add()); // 0
console.log(add(5)); // 5
console.log(add(3, 4)); // 7
console.log(add(1, 2, 3)); // 6
5.2 返回不同类型
function parseDate(input: string): Date;
function parseDate(input: number): Date;
function parseDate(input: string | number): Date {return typeof input === 'string'? new Date(input): new Date(input);
}

6. 与联合类型的对比

函数重载 vs 联合类型参数:

// 使用重载
function process(input: string): string;
function process(input: number): number;
function process(input: string | number): string | number {// 必须在函数体内手动区分类型return input;
}// 使用联合类型参数(非重载)
function process2(input: string | number): string | number {// 无需重载,但调用者无法获得精确类型提示return input;
}

选择原则

  • 当函数逻辑因参数类型而异时,使用重载。
  • 当函数逻辑不依赖参数类型时,使用联合类型。

​ 总的来说,重载适用于函数根据不同参数类型有不同的处理逻辑的情况,而联合类型适用于函数对不同类型参数可以进行统一处理的情况。

7. 注意事项

  1. 实现签名需兼容所有重载

    function fn(x: string): number;
    function fn(x: number): string;
    function fn(x: string | number): string | number {// 必须处理两种类型return typeof x === 'string' ? x.length : x.toString();
    }
    
  2. 重载签名顺序很重要

    function fn(x: any): any; // 错误:覆盖了其他重载,使其无法访问
    function fn(x: string): number;
    
  3. 箭头函数无法重载

    // 错误:箭头函数不能有重载签名
    const fn = (x: string) => x.length;
    

(三)泛型函数

​ 在 TypeScript 里,泛型函数是一种非常强大的特性,它能让函数在保持类型安全的同时,灵活处理多种数据类型。

1. 泛型函数的基本概念

泛型函数的核心在于使用类型变量(通常用 T 表示),这个类型变量可以捕获调用函数时传入的类型参数,从而让函数在不同的调用场景下处理不同的类型,同时保证类型的一致性。

(1)基础语法
function identity<T>(arg: T): T {return arg;
}

这里的 <T> 就是类型变量,它代表一个未知的类型。在函数内部,参数 arg 的类型是 T,返回值的类型同样是 T,这就保证了函数输入和输出的类型是一致的。

(2)调用方式
// 方式一:显式指定类型参数
let output1 = identity<string>("myString");// 方式二:利用类型推断自动确定类型参数
let output2 = identity(100);

2. 泛型类型变量的命名规范

虽然类型变量通常用 T 来表示,但你也可以使用更具描述性的名称,这样能让代码的含义更加清晰:

function loggingIdentity<Type>(arg: Type): Type {console.log(arg.length); // 这里会报错,因为并非所有类型都有 length 属性return arg;
}

如果要操作数组,可以这样写:

function loggingIdentity<Type>(arg: Type[]): Type[] {console.log(arg.length); // 数组肯定有 length 属性,所以不会报错return arg;
}

或用 TypeScript 中的泛型约束语法,用于限制泛型类型必须包含 length 属性(类型为 number)。

function loggingIdentity<Type extends  {length: number}>(arg: Type): Type {console.log(arg.length); // 这里会报错,因为并非所有类型都有 length 属性return arg;
}//console.log(loggingIdentity(1));//报错,1没有length属性

3. 泛型约束

有时候你可能需要对类型变量进行一些限制,确保它符合某些条件,这时候就需要用到泛型约束。

(1)使用接口实现约束
interface Lengthwise {length: number;
}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length); // 因为有约束,所以可以安全访问 length 属性return arg;
}loggingIdentity({ length: 10, value: 3 }); // 正确
loggingIdentity([1, 2, 3, 4, 5]);
// loggingIdentity(3); // 错误,数字类型没有 length 属性
(2)多个类型变量的约束
  • keyof T
    • 返回类型 T所有公共属性名组成的联合类型
    • 例如,若 T{ a: number; b: string },则 keyof T'a' | 'b'
  • K extends keyof T
    • 约束泛型 K 必须是 T 的某个属性名(即 K 必须属于 keyof T 的联合类型)。
    • 确保 key 参数是 obj 对象实际存在的键。
function getProperty<T, K extends keyof T>(obj: T, key: K) {return obj[key];
}let x = { a: 1, b: 2, c: 3, d: 4 };
console.log(getProperty(x, "a")); // 正确
// getProperty(x, "m"); // 错误,"m" 不是 x 对象的键const user = { name: "Alice", age: 30 };
console.log(getProperty(user, "name")); // ✅ 类型为 string
// const email = getProperty(user, "email"); // ❌ 类型错误

4. 泛型函数与重载

泛型函数适用于类型参数化的场景,而函数重载适用于固定类型的多种实现:

泛型实现

function identity<T>(arg: T): T {return arg;
}

重载实现

function identity(arg: string): string;
function identity(arg: number): number;
function identity(arg: any): any {return arg;
}
2. 选择策略
  • 当函数逻辑完全相同时,优先使用泛型。
  • 当函数针对不同类型有不同实现时,使用重载。

5. 泛型函数与非泛型函数的对比

(1)非泛型函数
function identity(arg: any): any {return arg;
}let output = identity("myString"); // 返回 any 类型,丢失了类型信息
//可能在后续代码中引入类型错误(如 output.toUpperCase() 可能意外调用不存在的方法)。
类型安全失效
let result = identity(123);
result.split(','); // ❌ 运行时错误(number 没有 split 方法),但 TypeScript 不会报错,any绕过了类型检查,允许任何操作。
(2)泛型函数
function identity<T>(arg: T): T {return arg;
}let output = identity("myString"); // 返回 string 类型,保留了类型信息
保留类型信息
let num = identity(123); // num 类型为 number
num.toFixed(2); // ✅ 类型安全
// num.split(','); // ❌ TypeScript 报错:number 没有 split 方法
支持任意类型
identity(true);      // boolean 类型
identity([1, 2, 3]); // number[] 类型
identity({ id: 1 }); // { id: number } 类型
灵活应用
// 手动指定类型(通常不需要)
identity<Date>(new Date()); // 等价于 identity(new Date())

6. 泛型默认类型参数

你可以为泛型类型参数设置默认值:

function createArray<T = string>(length: number, value: T): T[] {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
}let arr = createArray(3, "x"); // 默认类型为 string
let arr2 = createArray(3, 1);
console.log(arr);
console.log(arr2);

7. 注意事项

(1)避免过度使用泛型

如果一个函数只处理单一的具体类型,就没有必要使用泛型:

// 这种情况不需要泛型
function greet(name: string): string {return `Hello, ${name}`;
}
(2)确保类型变量使用正确

要保证类型变量在函数中被正确使用,避免出现无意义的泛型:

// 无意义的泛型,T 没有被使用
function printHello<T>(name: string): void {console.log("Hello, " + name);
}

(四)高阶函数

​ 在 TypeScript 中,高阶函数(Higher-Order Function)的概念与 JavaScript 一致,但 TypeScript 为其提供了更严格的类型系统,使高阶函数的使用更加安全和可靠。

在编程中,高阶函数(Higher-Order Function) 是指满足以下任意一个条件的函数:

  1. 接受一个或多个函数作为参数
  2. 返回一个函数作为结果

高阶函数的核心是将函数视为一等公民(First-Class Citizen),允许它们像普通数据一样被传递和操作。这种特性是 函数式编程 的基础,在 JavaScript、TypeScript、Python 等多种语言中广泛应用。

高阶函数的作用:

  • 抽象和复用逻辑:将通用行为封装在高阶函数中。
  • 创建可配置的函数:通过参数定制函数行为。
  • 实现异步控制流:如回调、Promise、async/await。
  • 简化代码:减少重复,提高可读性。

1. 高阶函数的类型定义

(1)函数作为参数
// 定义一个接受函数作为参数的高阶函数
function applyOperation(a: number, b: number, operation: (x: number, y: number) => number): number {return operation(a, b);
}// 使用示例
const sum = applyOperation(3, 4, (x, y) => x + y); // 类型为 number
const product = applyOperation(3, 4, (x, y) => x * y); // 类型为 number
(2)函数作为返回值
// 定义一个返回函数的高阶函数
function createAdder(base: number): (num: number) => number {return (num: number) => base + num;
}// 使用示例
const add5 = createAdder(5);
console.log(add5(3)); // 输出 8,类型为 number

2. 泛型高阶函数

泛型可以让高阶函数处理多种类型的数据,同时保持类型安全:

(1)泛型函数作为参数
function processItems<T>(items: T[], callback: (item: T) => void): void {
//array.forEach(callback(currentValue, index, array), thisArg);items.forEach(callback);
}// 使用示例
processItems([1, 2, 3], (num) => console.log(num.toFixed(2))); // 处理数字
processItems(["a", "b"], (str) => console.log(str.toUpperCase())); // 处理字符串
(2)泛型函数作为返回值
function createMapper<T, U>(mapper: (item: T) => U): (items: T[]) => U[] {return (items: T[]) => items.map(mapper);
}// 使用示例
const stringToLength = createMapper((str: string) => str.length);
console.log(stringToLength(["apple", "banana"])); // 输出 [5, 6],类型为 number[]

3. 高阶函数的应用场景

(1)数组处理

TypeScript 的数组方法(如 mapfilterreduce)都是泛型高阶函数:

高阶函数在数组处理中广泛应用,通过将逻辑抽象为参数化函数,实现代码复用和声明式编程。常见应用包括:

  • 映射(Map):将数组每个元素通过函数转换为新值,生成新数组。例如:对所有元素乘以 2、提取对象特定属性。
  • 过滤(Filter):根据条件筛选元素,保留符合条件的元素。例如:筛选偶数、查找长度超过 5 的字符串。
  • 归约(Reduce):累积计算数组元素,将数组合并为单个值。例如:求和、查找最大值、对象数组属性合并。
  • 排序(Sort):通过自定义比较函数实现灵活排序。例如:按对象属性排序、倒序排列。

优势:避免手动循环,代码更简洁、易读,减少错误。

const numbers: number[] = [1, 2, 3, 4];// map 方法的类型定义
const doubled: number[] = numbers.map((num: number) => num * 2);// filter 方法的类型定义
const evens: number[] = numbers.filter((num: number) => num % 2 === 0);// reduce 方法的类型定义
const sum: number = numbers.reduce((acc: number, num: number) => acc + num, 0);
(2)异步操作

高阶函数是处理异步逻辑的关键工具,通过回调、Promise、async/await 等机制实现非阻塞编程:

  • 回调函数:将函数作为参数传递给异步操作,在操作完成后执行。例如:网络请求完成后处理数据、文件读取回调。
  • Promise 封装:高阶函数可封装异步操作并返回 Promise,支持链式调用。例如:封装 setTimeout 为 delay 函数。
  • 异步控制流:实现重试机制、并发限制、串行执行等复杂流程。例如:自动重试失败的请求、限制同时执行的任务数量。
  • 事件监听:将回调函数注册到事件,事件触发时执行。例如:DOM 事件处理、消息队列订阅。

优势:解决回调地狱问题,使异步代码更清晰、可维护。

(3)函数装饰器

函数装饰器是一种特殊的高阶函数,用于增强或修改函数行为,而不改变原函数代码:

  • 日志记录:自动记录函数调用时间、参数、返回值。例如:性能监控、操作审计。
  • 权限验证:在函数执行前检查权限,阻止未授权访问。例如:登录验证、角色权限检查。
  • 缓存机制:缓存函数结果,避免重复计算。例如:记忆化(Memoization)优化纯函数。
  • 错误处理:统一捕获函数异常,提供默认值或重试逻辑。例如:网络请求失败时自动重试。
  • 参数校验:在函数执行前验证参数合法性,提前抛出错误。

优势:实现横切关注点(如日志、权限)的复用,遵循单一职责原则。

(4)柯里化

柯里化是将多参数函数转换为一系列单参数函数的技术,每次调用只接收一个参数并返回新函数,直到所有参数被收集完毕:

  • 参数复用:固定部分参数,生成新函数。例如:创建专门处理美元的货币转换函数。
  • 延迟计算:分阶段收集参数,在需要时统一计算。例如:配置函数在最终调用时生效。
  • 动态生成函数:根据不同参数组合生成特定功能的函数。例如:根据环境配置生成不同的日志函数。
  • 函数复用:将多元函数转换为一元函数,便于复用和组合。

优势:提高函数灵活性和复用性,适配只接受单参数的场景(如事件处理、数组方法)。

4. 高阶函数的类型约束

你可以使用类型约束来限制高阶函数的参数类型:

interface Lengthwise {length: number;
}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length); // 因为有约束,所以可以安全访问 length 属性return arg;
}// 使用示例
loggingIdentity("hello"); // 正确,字符串有 length 属性
loggingIdentity([1, 2, 3]); // 正确,数组有 length 属性
// loggingIdentity(100); // 错误,数字没有 length 属性

5. 高阶函数的实践

(1)明确类型定义

始终为高阶函数的参数和返回值定义明确的类型:

// 好的做法
function filter<T>(array: T[], predicate: (item: T, index: number, array: T[]) => boolean): T[] {// ...
}// 不好的做法(缺少类型定义)
function filter(array, predicate) {// ...
}
(2)使用泛型提高复用性

泛型可以让高阶函数处理多种类型的数据:

// 泛型版本
function map<T, U>(array: T[], mapper: (item: T) => U): U[] {return array.map(mapper);
}// 非泛型版本(只能处理 number 类型)
function mapNumbers(array: number[], mapper: (item: number) => number): number[] {return array.map(mapper);
}
(3)避免过度使用

虽然高阶函数很强大,但过度使用可能会使代码变得复杂难懂。在使用时,要确保代码的可读性和可维护性。

(五)this参数

​ 在JavaScript 和 TypeScript 中,this 是一个特殊的关键字,它的值在函数调用时动态确定,指向调用函数的对象。this 的行为在不同的函数调用方式(普通函数、方法、构造函数等)下有所不同。

  • 全局对象:在全局作用域中,非严格模式下 this 指向全局对象(如浏览器中的 window)。
  • 类实例:在类的实例方法中,this 指向调用该方法的对象。
  • 构造函数:在构造函数中,this 指向新创建的实例。
  • 静态方法:在静态方法中,this 指向类本身。
  • 箭头函数:箭头函数不绑定自己的 this,而是继承外层作用域的 this

1. 全局作用域中的 this(普通函数调用)

在普通函数调用中,this 的值取决于函数的调用方式。在非严格模式下,this 指向全局对象(在浏览器中是 window,在 Node.js 中是 global);在严格模式下,this 的值为 undefined

  • ES5this 类型为 any,指向全局对象。
  • ES6+this 类型为 undefined(严格模式)。
function greet() {console.log(this);
}// 非严格模式
greet(); // 在浏览器中,输出 window 对象;在 Node.js 中,输出 global 对象// 严格模式
function strictGreet() {'use strict';console.log(this);
}strictGreet(); // 输出 undefined

2. 方法调用

2.1 实例方法中的 this

在类的实例方法中,this 自动绑定到调用该方法的对象实例。

class Person {name: string;constructor(name: string) {this.name = name;//构造函数中的 this 自动绑定到新创建的 Person 实例(即 alice)。}greet() {console.log(`Hello, ${this.name}`); // this 指向 Person 实例}
}const alice = new Person("Alice");
alice.greet(); // 输出: "Hello, Alice"const greetFn = alice.greet;
greetFn(); // 严格模式下:this → undefined(报错)// 非严格模式下:this → window(浏览器环境)
//当方法被赋值到变量并单独调用时,this的绑定会丢失。
2.2 静态方法中的 this

在静态方法中,this 指向类本身,可以访问类的静态属性和方法。

  • 静态属性(如 PI)是类本身的属性,而非类实例的属性。
  • 静态成员可直接通过类名访问(如 Calculator.PI),无需创建实例。
class Calculator {static PI = 3.14;static calculateCircleArea(radius: number) {return this.PI * radius * radius; // this 指向 Calculator 类}
}console.log(Calculator.calculateCircleArea(5)); 

与实例方法的对比

calculateCircleArea 为实例方法(非静态):

  • this 将指向调用该方法的实例(需先创建 new Calculator())。
  • 实例方法无法直接访问静态属性,需通过类名引用(如 Calculator.PI)。

3. callapplybind 方法

callapply 方法允许显式指定函数调用时的 this 值,它们的区别在于参数的传递方式。bind 方法创建一个新的函数,该函数在调用时 this 值固定。

(1)显式声明 this 类型
interface Context {x: number;
}function add(this: Context, a: number, b: number): number {return this.x + a + b;
}const obj = { x: 1 };//function.call(thisArg, arg1, arg2, ...);
//thisArg:函数执行时 this 指向的对象。
//arg1, arg2, ...:传递给函数的参数(逗号分隔)。
const result1 = add.call(obj, 2, 3); // 6
const result2 = add.apply(obj, [2, 3]); // 6
const result3 = add.bind(obj, 2, 3)(); // 6
function greet(this: any, message: string) {console.log(`${message}, ${this.name}`); 
}const person = { name: "Alice" };
greet.call(person, "Hello"); /
function calculate(this: any, a: number, b: number) {return this.operation + (a + b);
}const calculator = { operation: "Result:" };// 使用 apply 调用 calculate 函数,参数以数组形式传递
const result = calculate.apply(calculator, [3, 5]);
console.log(result); // 输出: "Result: 8"/
function sayHi() {console.log(`Hi, ${this.name}`);
}const user = { name: "Bob" };// 使用 bind 创建一个新函数,其 this 始终指向 user
const boundSayHi = sayHi.bind(user);// 调用绑定后的函数
boundSayHi(); // 输出: "Hi, Bob"
(2)使用箭头函数
const greetArrow = () => {console.log(`Hello, ${this.name}`);
};const obj = { name: "Charlie" };// 无效:箭头函数的 this 继承自全局作用域(非严格模式下为 window)
greetArrow.call(obj); // 输出: "Hello, undefined"
(3)使用类语法
  • this 的类型
    • 在类方法中,this 自动绑定为类的实例类型(即 Calculator)。
    • TypeScript 会确保 this.x 存在且类型正确。
class Animal {constructor(public name: string) {}speak(phrase: string) {console.log(`${this.name} says: ${phrase}`);}
}const cat = new Animal("Whiskers");
const dog = new Animal("Buddy");// 使用 call 让 dog 借用 cat 的 speak 方法
cat.speak.call(dog, "Woof!"); // 输出: "Buddy says: Woof!"

4.函数参数的this

TypeScript 允许通过 this 参数显式声明函数的 this 类型,增强类型安全。(仅用于类型检查,不影响实际参数)。

4.1类型为void this

​ 在 TypeScript 中,用于禁用函数内部对 this 的访问。这种设计主要用于回调函数或工具函数,确保它们不会意外依赖调用上下文的 this。

const button: UIElement = {// 定义一个计算器函数,禁止使用 this
function calculate(this: void, a: number, b: number): number {// this.a + this.b; // 错误:this 类型为 voidreturn a + b;
}// 安全调用
const result = calculate(1, 2); // 3// 尝试使用 this 调用(TypeScript 编译错误)
// calculate.call({ a: 1, b: 2 }, 1, 2); 
// 报错:"类型 '{ a: number; b: number; }' 的参数不能赋给类型 'void' 的参数"
4.2类型为非void this

通过 this 参数作为函数的第一个参数

interface Counter {value: number;increment(this: Counter): void;
}const counter: Counter = {value: 0,increment() {this.value++; // 确保 this 类型为 Counter}
};counter.increment();
console.log(counter.value); // 输出: 1

5. 箭头函数

箭头函数没有自己的 this,它的 this 继承自外层作用域。

5.1实例方法中的 this
class Timer {seconds = 0;start() {setInterval(() => {this.seconds++; // 箭头函数继承 start 方法的 this(Timer 实例)//start() 方法的 this 指向 Timer 实例(因为是通过实例调用的)。//箭头函数 () => { ... } 继承了 start() 方法的 this,因此 this.seconds 正确指向实例的 seconds 属性。console.log(this.seconds);}, 1000);}
}const timer = new Timer();
timer.start(); // 每秒递增 seconds
5.2对象中的 this

getName 方法中定义的箭头函数 innerFunction,其 this 指向 getName 方法的 this,而 getName 方法作为 person 对象的方法,this 指向 person 对象。所以 innerFunction 中的 this.name 实际上访问的是 person 对象的 name 属性。

const person = {name: 'Bob',getName() {// 箭头函数中的 this 继承自外层的 person 对象const innerFunction = () => {console.log(this.name);};innerFunction();}
};person.getName(); // 输出:Bob

6. this 的类型注解与常见问题

6.1 类方法的 this 类型

在类方法中,this 的类型默认是类实例类型,当一个方法返回 this 时,它返回的是当前对象实例,从而允许链式调用(当子类继承基类的方法时,返回的 this 会自动调整为子类类型)。

class Animal {move(distance: number) {console.log(`Animal moved ${distance}m.`);return this; // 返回 this 实现链式调用}
}class Dog extends Animal {bark() {console.log("Woof!");return this;}
}const dog = new Dog();
dog.move(10).bark(); // 正确:链式调用

7. 实践

7.1 使用箭头函数保留上下文

在需要传递回调函数的场景中,优先使用箭头函数确保 this 指向正确。

class Form {fields = ["name", "email"];//无论submit方法如何被调用,其内部的this始终指向Form实例。submit = () => {// 箭头函数继承 Form 实例的 thisthis.fields.forEach(field => {console.log(`Processing ${field} with ${this}`);});}
}
// 创建 Form 类的实例
const myForm = new Form();// 调用 submit 方法
myForm.submit();const a = myForm.submit();setTimeout(myForm.submit, 100);
7.2 在构造函数中绑定方法

对于需要传递的普通方法,在构造函数中使用 bind 方法绑定 this

  • this.log.bind(this)
    • 左边的 this.log:指向当前实例的 log 属性(初始为原型上的方法)。
    • 右边的 this.log:指向原型链上的原始 log 方法(Logger.prototype.log)。
    • bind(this):创建一个新的绑定函数,将 this 永久锁定为当前实例(即 logger),并将这个新函数赋值给实例的 log 属性。
  • 结果
    实例的 log 属性被覆盖为绑定后的新函数,而原型上的原始方法保持不变。
class Logger {prefix: string;constructor(prefix: string) {this.prefix = prefix;// 绑定 log 方法的 thisthis.log = this.log.bind(this);}log(message: string) {console.log(`${this.prefix}: ${message}`);}
}const logger = new Logger("DEBUG");
setTimeout(logger.log, 1000, "Logging..."); // 正确:输出 "DEBUG: Logging..."

5. 常见陷阱与解决方案

5.1 回调函数中的 this 丢失

问题:将方法作为回调传递时,this 指向全局对象或 undefined
解决方案:使用箭头函数或 bind 方法。

1. 问题示例:this 丢失
  • 方法分离:当你将logger.log作为参数传递时,它与logger实例的绑定被切断,此时this不再指向logger
  • 回调函数特性:回调函数通常在新的上下文中执行,this的指向由调用方式决定,而非定义位置。
class Logger {prefix = "DEBUG";log(message: string) {console.log(`${this.prefix}: ${message}`);}
}const logger = new Logger();// 错误:将 log 方法单独传递时,this 指向全局对象(非严格模式)或 undefined(严格模式)
//setTimeout接收的是log函数的引用,而非绑定到looger的方法。
setTimeout(logger.log, 1000, "Logging..."); 
// 输出: "undefined: Logging..."(严格模式)
2. 解决方案一:使用箭头函数

箭头函数继承外层作用域的 this,确保 this 指向 Logger 实例。

class Logger {prefix = "DEBUG";log(message: string) {console.log(`${this.prefix}: ${message}`);}
}const logger = new Logger();// 正确:箭头函数捕获并保留外层的 this(Logger 实例)
setTimeout(() => logger.log("Logging..."), 1000); 
// 输出: "DEBUG: Logging..."
3. 解决方案二:使用 bind 方法

通过 bind 方法创建一个新函数,永久绑定 thisLogger 实例。

class Logger {
prefix = "DEBUG";log(message: string) {console.log(`${this.prefix}: ${message}`);
}
}const logger = new Logger();// 正确:使用 bind 绑定 this 到 logger 实例
const boundLog = logger.log.bind(logger);
setTimeout(boundLog, 1000, "Logging..."); 
// 输出: "DEBUG: Logging..."
4. 在构造函数中绑定

在类的构造函数中使用 bind,确保所有实例方法的 this 始终指向实例。

class Logger {prefix = "DEBUG";constructor() {// 在构造函数中绑定 log 方法的 thisthis.log = this.log.bind(this);//一旦使用bind绑定了this,就无法再被覆盖,哪怕使用call、apply或者箭头函数也不行。}log(message: string) {console.log(`${this.prefix}: ${message}`);}
}const logger = new Logger();// 安全:无论 log 方法如何被传递,this 都指向 logger 实例
setTimeout(logger.log, 1000, "Logging..."); 
// 输出: "DEBUG: Logging..."

总结

  • this的动态性:TypeScript 中this的指向取决于函数的调用方式,而非定义位置。
  • 回调函数陷阱:将方法作为回调传递时,需显式绑定this或通过箭头函数捕获上下文。
5.2 静态方法误用 this

问题:在静态方法中使用 this 访问实例属性。
解决方案:静态方法只能访问类的静态成员。

class Person {name: string;gender: number;static age: number = 30;constructor(name: string) {this.name = name;}static printInfo() {// 错误:静态方法中不能访问实例属性//TS2564: Property 'gender' has no initializer and is not definitely assigned in the constructor.console.log(this.gender);// 只能访问静态成员,如 this.age 是可以的console.log(this.age);console.log(this.name);// 如果你的环境(如浏览器控制台或某些工具链)中// 类对象(Person)的 name 属性默认返回类的名称(即 "Person")// 则 this.name 会打印出 "Person"。console.log(Person.name); // 输出 "Person"(类对象的 name 属性)}
}Person.printInfo();
//输出:
//undefined
//Person
//30
//Person

(六)回调函数

​ 在 TypeScript 中,回调函数的概念与 JavaScript 一致,但 TypeScript 为其提供了更严格的类型系统,使回调函数的使用更加安全和可靠。回调函数是编程中的一个核心概念,它本质上是一种函数传递机制。具体来说,就是把一个函数作为参数传递给另一个函数,这个被传递的函数会在某个特定的时刻或者条件下被调用执行。

1. 核心定义与特点

定义

回调函数是一种函数传递模式,其核心特点是:

  • 作为参数传递:将一个函数作为参数传递给另一个函数(称为高阶函数)。
  • 延迟执行:回调函数不会立即执行,而是在某个条件满足或事件发生时被调用。
本质

回调函数本质上是将代码执行逻辑的控制权委托给另一个函数。常见场景包括:

  • 异步操作完成后(如网络请求、定时器)。
  • 事件触发时(如点击按钮、数据加载)。
  • 循环或递归中的每一步处理

2. 回调函数的常见形式

2.1 同步回调

在函数执行过程中立即调用的回调函数。

// 数组的 forEach 方法接收回调函数
const numbers = [1, 2, 3];
numbers.forEach((num) => {console.log(num); // 回调函数在遍历到每个元素时立即执行
});
2.2 异步回调

在异步操作完成后调用的回调函数(如定时器、Promise)。

// setTimeout 函数的基本语法是:setTimeout(callback, delay, arg1, arg2, ...),其中:
//callback(回调函数):在 delay 毫秒后要执行的函数。
//delay(延迟时间):指定多少毫秒后执行 callback 函数。
//arg1, arg2, ...(可选):要传递给 callback 函数的参数。
setTimeout(() => {console.log("2秒后执行"); // 回调函数在2秒后执行
}, 2000);

3. 回调函数的参数类型

(1)可选参数
function delayedAction(callback: (message?: string) => void) {setTimeout(() => callback("Action completed"), 1000);
}delayedAction((msg) => console.log(msg.toUpperCase())); // 正确
delayedAction(() => console.log("Done")); // 正确,message 是可选参数
(2)泛型回调
  1. 回调函数类型
    • callback: (item: T) => void 表示传入的回调函数接收一个 T 类型的参数
    • 回调函数的返回值类型为 void,意味着它不返回任何有意义的值(尽管实际上可以返回任意值,但会被忽略)
function processItems<T>(items: T[], callback: (item: T) => void) {items.forEach(callback);
}processItems([1, 2, 3], (num) => console.log(num.toFixed(2))); // 处理数字
processItems(["a", "b"], (str) => console.log(str.toUpperCase())); // 处理字符串

4. 回调函数的返回值类型

(1)void 返回值
function forEach(items: any[], callback: (item: any) => void) {for (let i = 0; i < items.length; i++) {callback(items[i]);}
}// 回调函数的返回值被忽略
forEach([1, 2, 3], (item) => {console.log(item);return item * 2; // 返回值被忽略 但 TypeScript 允许这样写
});

这种设计主要是为了提高代码的灵活性:

  • 兼容性:允许回调函数实现者返回有用的值(如中间结果),即使调用方不需要这些值。
  • 函数重载:同一个回调函数可能在不同场景下被不同函数调用,有些调用者可能使用返回值,而有些则忽略。
(2)非 void 返回值
function map<T, U>(items: T[], callback: (item: T) => U): U[] {const result: U[] = [];for (let i = 0; i < items.length; i++) {result.push(callback(items[i]));}return result;
}const squared = map([1, 2, 3], (num) => num * num); // 返回 number[]

5. 回调函数的实际应用

(1)数组方法中的回调
const numbers: number[] = [1, 2, 3, 4];// map 方法的回调
const doubled: number[] = numbers.map((num: number) => num * 2);// filter 方法的回调
const evens: number[] = numbers.filter((num: number) => num % 2 === 0);// reduce 方法的回调
const sum: number = numbers.reduce((acc: number, num: number) => acc + num, 0);
(2)异步编程
// 模拟异步请求
function fetchData(callback: (data: string) => void) {setTimeout(() => {callback("数据加载完成"); // 请求完成后调用回调函数}, 1000);
}fetchData((data) => {console.log(data); // 输出: "数据加载完成"
});
(3)函数组合与抽象

通过高阶函数实现代码复用。

// 自定义高阶函数
function processArray(arr: number[], callback: (num: number) => number) {return arr.map(callback);
}const result = processArray([1, 2, 3], (num) => num * 2);
console.log(result); // 输出: [2, 4, 6]

6. 回调函数的最佳实践

  1. 明确类型定义:始终为回调函数定义明确的参数类型和返回值类型。
  2. 使用泛型:当回调函数处理特定类型的数据时,使用泛型提高类型安全性。
  3. 注意 this 指向:使用箭头函数或显式绑定保持正确的 this 上下文。

回调函数中的 this 可能指向全局对象或 undefined

class Form {submit() {setTimeout(function() {console.log(this); // 错误:this 指向全局对象(非严格模式)或 undefined}, 1000);}
}

解决方案

  • 箭头函数:继承外层的this。

    setTimeout(() => {console.log(this); // 正确:this 指向 Form 实例
    }, 1000);
    
  • bind 方法:显式绑定this。

    setTimeout(function() {console.log(this); // 正确:this 指向 Form 实例
    }.bind(this), 1000);
    

(七)函数的作用域

在 TypeScript 中,函数的作用域(Scope)决定了变量和函数的可见性与生命周期。理解作用域是编写高质量、无错误代码的基础。以下是关于函数作用域的详细介绍:

1. 全局作用域(Global Scope)

  • 定义:在所有函数和类外部定义的变量和函数具有全局作用域。
  • 特点:
    • 可以在代码的任何位置访问。
    • 可能导致命名冲突和代码难以维护。
示例:
// 全局变量
const globalVar = 100;function globalFunction() {console.log("这是一个全局函数");
}// 在任何地方都可以访问
console.log(globalVar); // 输出: 100
globalFunction(); // 输出: "这是一个全局函数"

2. 函数作用域(Function Scope)

  • 定义:在函数内部定义的变量和函数只在该函数内部可见。
  • 特点:
    • 使用 var 声明的变量具有函数作用域(而非块级作用域)。
    • 不同函数内的同名变量相互独立。
示例:
function example() {var localVar = "局部变量";if (true) {var innerVar = "内部变量"; // 仍属于函数作用域}console.log(localVar); // 输出: "局部变量"console.log(innerVar); // 输出: "内部变量"
}console.log(localVar); // 错误:localVar 未定义

3. 块级作用域(Block Scope)

  • 定义:使用 letconst 在代码块(如 ifforwhile)内声明的变量具有块级作用域。
  • 特点:
    • 只能在声明它们的块内访问。
    • 解决了 var 带来的变量提升问题。
示例:
function blockExample() {if (true) {let blockVar = "块级变量";const constant = 42;}console.log(blockVar); // 错误:blockVar 未定义console.log(constant); // 错误:constant 未定义
}

4. 闭包(Closure)

  • 定义:函数及其捕获的外部变量的组合称为闭包。
  • 特点:
    • 即使外部函数执行完毕,闭包仍能访问外部变量。
    • 闭包会保留变量的引用,而非值的副本。这意味着闭包内部访问的变量会反映其外部环境的最新状态。
示例:
function outer() {let count = 0;function inner() {count++; // 闭包捕获了外部的 count 变量console.log(count);}return inner;
}const counter = outer();
counter(); // 输出: 1
counter(); // 输出: 2

5. 词法作用域(Lexical Scope)

  • 定义:作用域由代码的书写位置静态决定,而非运行时。
  • 特点:
    • 内部函数可以访问外部函数的变量,即使外部函数已执行完毕。
    • 与动态作用域(如 this 的绑定)形成对比。(有两个对象,通过call方法绑定不同的对象)
示例:
function outer() {const x = 10;//将其词法环境绑定到 outer 的作用域(即使 outer 执行完毕,inner 仍保留对 x 的引用)。function inner() {console.log(x); // 访问外部函数的变量 x}return inner;
}const closure = outer();
//closure 实际是 inner 函数,它仍然可以访问 outer 作用域中的 x。尽管 outer 已执行完毕,但由于 inner 函数形成了闭包(因为它引用了 outer 函数作用域内的变量 x),x 的值不会被立即销毁。
closure(); // 实际上是调用了 inner 函数,输出: 10(即使 outer 已执行完毕)

6. 箭头函数与作用域

  • 特点:
    • 箭头函数不创建自己的 this,而是捕获其定义时所在作用域的 this
    • 适合需要保留上下文的场景(如回调函数)。
示例:
class Example {private value = 42;getValue = () => {setTimeout(() => {console.log(this.value); // 箭头函数捕获了 Example 实例的 this}, 1000);};
}const instance = new Example();
instance.getValue(); // 输出: 42

7. 作用域链(Scope Chain)

  • 定义:当访问一个变量时,TypeScript 会先在当前作用域查找,若找不到则沿作用域链向上查找,直到全局作用域。

  • 示例:

    const globalVar = "全局变量";function outer() {const outerVar = "外部变量";function inner() {const innerVar = "内部变量";console.log(innerVar); // 直接找到console.log(outerVar); // 沿作用域链找到console.log(globalVar); // 沿作用域链找到}return inner;
    }const inner = outer();
    inner();
    

8. 变量提升(Hoisting)

  • 定义:使用 var 声明的变量会被提升到函数或全局作用域的顶部(这意味着无论 var 声明的变量在代码中的位置如何,JavaScript或TypeScript引擎在执行代码之前,会先将 var 声明的变量提升到其所在作用域的顶部),但赋值不会提升。

  • 示例

    function hoistExample() {console.log(x); // 输出: undefined(变量已提升但未赋值)var x = 10;
    }// 等价于:
    function hoistExample() {var x;console.log(x); // undefinedx = 10;
    }
    
  • 注意letconst 也会被提升,但在声明前访问会导致 ReferenceError(暂时性死区/引用错误)。

9. TypeScript 特有的作用域规则

(1)模块作用域
  • 每个 .ts 文件都是一个独立的模块,模块内的变量默认是私有的。

  • 使用export关键字暴露变量:

    // utils.ts
    export const PI = 3.14; // 导出的变量可被其他模块访问const secret = "私有变量"; // 模块内私有
    
(2)命名空间(Namespace)
  • 用于组织代码,避免全局命名冲突(namespace(命名空间)是一种用于组织代码、避免命名冲突的机制,它可以将相关的变量、函数、类等组织在一起):

    namespace MyNamespace {// 定义变量export const message = "Hello, namespace!";// 定义函数export function sayHello() {console.log(message);}// 定义类export class MyClass {constructor() {console.log("MyClass instantiated");}}// 私有成员(外部无法直接访问)const privateVariable = "This is private";function privateFunction() {console.log(privateVariable);}
    }MyNamespace.sayHello(); // 输出: Hello, namespace!
    const myObj = new MyNamespace.MyClass(); // 实例化 MyClass
    // 尝试访问私有成员会报错
    MyNamespace.privateFunction(); // 无法访问私有成员
    

(八)函数的闭包

在 TypeScript 中,闭包是一个非常重要的概念,它涉及到函数和其周围状态的关联。闭包允许函数记住并访问其词法作用域内的变量,即使函数在其原始作用域之外被调用。

闭包的核心特性

  1. 捕获词法环境:闭包会记住其定义时所在的词法环境(即外部函数的作用域),包括该作用域内的所有变量。
  2. 延长变量生命周期:即使外部函数执行完毕,其作用域内的变量也不会被垃圾回收,因为闭包仍然引用它们。
  3. 动态访问变量:闭包捕获的是变量的引用,而非值的副本,因此变量的变化会反映在闭包中。

1. 闭包的基本概念

闭包是指函数和其捕获的外部变量的组合。当一个函数在其词法作用域之外被调用时,它仍然可以访问其定义时所在作用域中的变量。

function outer() {//outer 函数内部创建了变量 count 和函数 innerlet count = 0;function inner() {count++;console.log(count);}return inner;
}const closure = outer();
closure(); // 输出: 1
closure(); // 输出: 2
  • 词法作用域inner 函数在定义时(即在 outer 内部),其作用域链包含 outer 的变量(如 count)。
  • 闭包的形成:当 inner 函数被返回并在其他地方调用时,它携带了整个词法环境(即 outer 的作用域),即使 outer 已执行完毕。

在上述代码中,inner 函数形成了一个闭包。inner 函数可以访问 outer 函数作用域内的 count 变量,即使 outer 函数已经执行完毕,count 变量的值仍然被 inner 函数记住。

2. 闭包的作用

(1)数据私有化

闭包可以用来实现数据的私有化,外部无法直接访问闭包内部的变量。

function createCounter() {let value = 0;return {increment() {value++;},getValue() {return value;}};
}const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 输出: 1

在这个例子中,value 变量被封装在闭包中,外部只能通过返回的对象方法来操作 value,实现了数据的私有化。

(2)记忆函数

闭包可以用来创建记忆函数,记住之前的计算结果。

function memoize(func: (n: number) => number): (n: number) => number {const cache = new Map<number, number>();// 用于存储已计算的结果return function(arg: number): number {// 返回一个闭包函数if (cache.has(arg)) { // 如果缓存中已有结果return cache.get(arg) as number;// 直接从缓存中返回}// 否则计算结果并缓存const result: number = func(arg);cache.set(arg, result);return result;};
}function factorial(n: number): number {return n === 0 ? 1 : n * factorial(n - 1);
}const memoizedFactorial = memoize(factorial);
console.log(memoizedFactorial(5));
console.log(memoizedFactorial(5)); // 从缓存中获取结果,提高性能

memoize 函数返回的新函数形成了闭包,记住了之前的计算结果,避免了重复计算。

3. 闭包与箭头函数

箭头函数也可以形成闭包,但箭头函数本身不创建自己的 this,它捕获其定义时所在作用域的 this

class Example {private value = 42;getValue = () => {setTimeout(() => {console.log(this.value); // 箭头函数捕获了 Example 实例的 this}, 1000);};
}const instance = new Example();
instance.getValue(); // 输出: 42

在这个例子中,getValue 箭头函数形成了闭包,并且捕获了 Example 实例的 this,即使在 setTimeout 回调中也能正确访问 value

4. 闭包与作用域链

闭包依赖于作用域链,当函数访问一个变量时,会先在自己的作用域中查找,如果找不到则沿着作用域链向上查找。

function outer() {let outerVar = "外部变量";function inner() {let innerVar = "内部变量";console.log(innerVar); // 找到内部变量console.log(outerVar); // 沿着作用域链找到外部变量}return inner;
}const closure = outer();
closure();

在这个例子中,inner 函数可以访问其自身作用域内的 innerVar 和外部作用域的 outerVar,这是通过作用域链实现的。

5. 闭包的注意事项

(1)内存管理

闭包会保留对外部变量的引用,如果不当使用,可能会导致内存泄漏。

function createLargeArray() {const largeArray = new Array(1000000).fill(0);return function() {// 即使 largeArray 不再被直接引用,闭包仍然持有对它的引用console.log(largeArray.length);};
}const closure = createLargeArray();
// 闭包保持对 largeArray 的引用,导致内存占用

在这个例子中,createLargeArray 函数返回的闭包保持了对 largeArray 的引用,即使 createLargeArray 执行完毕,largeArray 也不会被垃圾回收,可能导致内存占用过高。

(2)作用域混淆

过多的闭包嵌套可能会导致作用域混淆,使代码难以理解和维护。

inner 函数可以访问自己作用域内的变量 c,同时也可以访问其外部作用域(middle 函数的作用域)中的变量 b,以及更外层作用域(outer 函数的作用域)中的变量 a

function outer() {let a = 1;function middle() {let b = 2;function inner() {let c = 3;console.log(a); // 访问外部作用域的 aconsole.log(b); // 访问中间作用域的 bconsole.log(c); // 访问内部作用域的 c}return inner;}return middle;
}const closure = outer()();
closure();

在这个例子中,多层闭包嵌套使得变量的作用域变得复杂,增加了代码理解的难度。

http://www.xdnf.cn/news/5469.html

相关文章:

  • L1 第9次课 数组进阶
  • 大数据从专家到小白
  • MCP 通信机制:stdio vs SSE
  • 项目过程中使用vant组件使用踩坑记录
  • 【Bootstrap V4系列】学习入门教程之 组件-媒体对象(Media object)
  • Nginx的增强与可视化!OpenResty Manager - 现代化UI+高性能反向代理+安全防护
  • 无人甘蔗小车履带式底盘行走系统的研究
  • 语音合成之十三 中文文本归一化在现代语音合成系统中的应用与实践
  • 【Java学习笔记】instanceof操作符
  • 隐式/显式类型转换?编程语言的类型转换?其它类型转换成数值类型?其它类型转换成字符串?类型转换?
  • 【和春笋一起学C++】数组名作为函数参数实例
  • STM32f103 标准库 零基础学习之按键点灯(不涉及中断)
  • vim配置代码文档格式化
  • Http2多路复用的静态表和动态表
  • CSS专题之自定义属性
  • 记录学习《手动学习深度学习》这本书的笔记(十一)
  • Docker:安装配置教程(最新版本)
  • 元组类型的特性与应用场景:深入理解元组在 TypeScript 中的使用
  • Python训练营打卡DAY22
  • LVGL(lv_label实战)
  • 《设计模式之禅》笔记
  • 使用PHP对接印度股票市场API
  • AARRR用户增长模型(海盗指标)详解
  • C/C++跳动的爱心
  • 云计算-容器云-KubeVirt 运维
  • 【Tools】Visual Studio使用经验介绍(包括基本功能、远程调试、引入第三方库等等)
  • 深入理解 Pinia:状态管理的利器
  • [思维模式-29]:《本质思考力》-9- 两种相反的构建与解构系统的思维模式:①自顶向下的规划、分解、牵引;②自底向上的堆叠、聚合。
  • 【stata代码】地方政府驱动企业参与乡村振兴的机制——乡村振兴注意力视角的分析
  • 数据可视化大屏——智慧社区内网比对平台