Appearance
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
}
// 其他文件无法直接访问 SECRET2. 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 的简单回调两者皆可;构造函数必须用普通函数。
🐱 小猫提示:闭包部分记住一句话——"在组件回调中,永远优先用箭头函数"。这是最实用的面试回答。