Skip to content

ArkTS 闭包与作用域

理解 ArkTS 中 this 绑定、闭包机制和回调陷阱,是写出正确代码的基础。


1. 作用域类型

1.1 块级作用域

typescript
function test() {
    if (true) {
        let x = 1
        const y = 2
        console.log(x, y)  // 1, 2
    }
    // console.log(x)  // ❌ 编译错误!x 超出块级作用域
}

1.2 模块作用域

typescript
// utils/const.ets
const SECRET = '123456'  // 模块级别常量
export function getSecret(): string {
    return SECRET
}

// 其他文件无法直接访问 SECRET

2. this 绑定问题

2.1 普通函数的 this 问题

typescript
@Component
struct TimerComponent {
    @State seconds: number = 0

    startTimer() {
        // ❌ this 丢失!因为 setTimeout 回调中 this 指向 Window
        setInterval(function () {
            this.seconds++  // ❌ this 是 undefined,编译/运行时错误
        }, 1000)
    }
}

2.2 箭头函数绑定 this ✅

typescript
@Component
struct TimerComponent {
    @State seconds: number = 0

    // ✅ 箭头函数自动绑定 this(捕获外层 this)
    startTimer() {
        setInterval(() => {
            this.seconds++  // ✅ this 正确指向组件实例
        }, 1000)
    }
}

2.3 事件回调中的 this

typescript
@Component
struct ButtonComponent {
    @State count: number = 0

    // ✅ 方式1:箭头函数属性(推荐)
    onClick1 = () => {
        this.count++
    }

    // ✅ 方式2:在模板中绑定
    onClick2() {
        this.count++
    }

    build() {
        Column() {
            // 方式1:直接引用
            Button('点击1')
                .onClick(this.onClick1)

            // 方式2:箭头函数绑定
            Button('点击2')
                .onClick(() => this.onClick2())

            Text(`Count: ${this.count}`)
        }
    }
}

3. 闭包(Closure)

3.1 闭包的概念

闭包是一个函数和它引用的外部变量的组合。在 ArkTS 中,箭头函数是最常见的闭包形式。

typescript
// 闭包:函数捕获了外部的 counter 变量
function createCounter(): () => number {
    let counter: number = 0

    return () => {
        counter++
        return counter
    }
}

const inc = createCounter()
console.log(inc())  // 1
console.log(inc())  // 2
console.log(inc())  // 3
// counter 变量被闭包保留,外部无法访问

3.2 闭包中的常见陷阱

陷阱1:循环中的闭包

typescript
// ❌ 错误:所有回调都引用同一个 index(最终值为 3)
for (let index = 0; index < 3; index++) {
    setTimeout(() => {
        console.log(index)  // 3, 3, 3(所有都是 3!)
    }, 100)
}

// ✅ 正确:使用 let 的块级作用域
for (let i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i)  // 0, 1, 2
    }, 100 * i)
}

// ✅ 正确:使用 IIFE 捕获值
for (var j = 0; j < 3; j++) {
    ((value) => {
        setTimeout(() => {
            console.log(value)  // 0, 1, 2
        }, 100 * j)
    })(j)
}

陷阱2:组件状态在闭包中失效

typescript
@Component
struct ListComponent {
    @State items: string[] = ['a', 'b', 'c']

    // ❌ 错误:闭包捕获了 items 的旧引用
    updateItems() {
        const oldItems = this.items
        setTimeout(() => {
            this.items = [...oldItems, 'd']  // 可能不会触发 UI 刷新
        }, 1000)
    }

    // ✅ 正确:直接修改状态
    updateItemsCorrect() {
        setTimeout(() => {
            this.items.push('d')  // ✅ @State 检测到变化,触发刷新
        }, 1000)
    }
}

4. 箭头函数 vs 普通函数

特性箭头函数 () => {}普通函数 function() {}
this 绑定✅ 自动绑定外层 this❌ this 指向调用者
arguments❌ 不支持✅ 支持
new 调用❌ 不能 new✅ 可以
性能略高(无 this 绑定开销)略低
适用场景回调、事件处理构造函数、需要 this 的场景

在 ArkTS 组件中的选择

typescript
@Component
struct Example {
    @State value: string = ''

    // ✅ 事件回调:用箭头函数
    onTextChange = (text: string) => {
        this.value = text
    }

    // ✅ 定时器:用箭头函数
    doTimeout() {
        setTimeout(() => {
            this.value = 'timeout'
        }, 1000)
    }

    // ✅ ForEach 回调:用箭头函数
    renderList() {
        ForEach(this.items, (item: string) => {  // 不需要 this,用普通函数也行
            Text(item)
        })
    }

    // ❌ 绝对避免:在回调中使用普通函数
    onButtonClick() {
        setTimeout(function () {
            this.value = 'wrong'  // ❌ this 丢失
        }, 1000)
    }
}

5. 闭包与内存泄漏

5.1 闭包不释放的问题

typescript
// 不推荐:长时间运行的应用,闭包持有大对象引用
function createBigDataProcessor() {
    const bigData = new Array(1000000).fill('x')  // 大数组

    return () => {
        // bigData 被闭包持有,永远不会被 GC
        return bigData.length
    }
}

5.2 如何释放闭包

typescript
@Component
struct MyComponent {
    private timerId: number = -1

    startTask() {
        this.timerId = setInterval(() => {
            // 执行任务
        }, 1000)
    }

    aboutToDisappear() {
        // ✅ 组件销毁时清除定时器,释放闭包引用
        if (this.timerId >= 0) {
            clearInterval(this.timerId)
        }
    }
}

6. 面试高频考点

Q1: ArkTS 中的 this 绑定问题怎么解决?

回答:使用箭头函数(() => {})自动绑定外层 this。在组件的事件回调、定时器、异步回调中必须使用箭头函数。

Q2: 闭包的核心概念和常见坑?

回答:闭包是函数+外部变量的组合。常见坑:循环中的 this 丢失、闭包持有大对象不释放、闭包中的状态引用过期。解决方式:用箭头函数、及时清除定时器、直接引用组件状态。

Q3: 什么时候用普通函数,什么时候用箭头函数?

回答:回调和事件处理用箭头函数(保证 this 绑定);不需要 this 的简单回调两者皆可;构造函数必须用普通函数。


🐱 小猫提示:闭包部分记住一句话——"在组件回调中,永远优先用箭头函数"。这是最实用的面试回答。