Appearance
ArkTS 空安全
ArkTS 的空安全是编译期强制的,严格区分 null 和 undefined,是防止 NullPointerException 的第一道防线。
1. 空安全的核心理念
ArkTS 的空安全不是运行时检查,而是编译期强制。这意味着:
- 如果一个变量可能为空,必须显式声明为联合类型
- 在访问可能为空的值之前,必须进行非空校验
- 编译器会在编译期报错,防止空指针异常
核心原则
1. 默认不可为空 → 声明的类型默认为非空
2. 允许为空 → 必须用联合类型明确标注
3. 使用前必须校验 → 编译器强制非空检查2. null vs undefined 的区别
ArkTS 严格区分 null 和 undefined,它们是两个不同的概念:
| 值 | 含义 | 使用场景 |
|---|---|---|
null | 有意为"无" | 明确声明一个值不存在 |
undefined | 未赋值 | 变量声明后未初始化 |
typescript
// null:明确声明为空
let name: string | null = null
name = 'Hello' // ✅ 正常赋值
name = null // ✅ 合法
// undefined:未初始化
let age: number
console.log(age) // undefined
// 不能直接比较 null 和 undefined
console.log(null == undefined) // true(宽松相等)
console.log(null === undefined) // false(严格相等)3. 联合类型(Union Types)
3.1 声明可能为空的变量
typescript
// ❌ 错误:string 不能为空,赋值 null 会编译报错
let name: string = null
// ✅ 正确:使用联合类型
let name: string | null = null
let name: string | undefined = undefined
let name: string | null | undefined = null3.2 访问前的非空校验
typescript
let name: string | null = 'Hello'
// ✅ 方式1:if 判断
if (name !== null) {
console.log(name.length) // ✅ 编译器知道此时 name 不为 null
}
// ✅ 方式2:可选链操作符
console.log(name?.length) // number | undefined
// ✅ 方式3:空值合并运算符
console.log(name ?? '默认值')
// ❌ 错误:直接访问会编译报错
console.log(name.length) // 编译错误!name 可能为 null4. 非空断言操作符(!)
4.1 何时使用
当你确定某个值不为空,但编译器无法判断时:
typescript
function processInput(input: string | null) {
// 如果你确定 input 在这里不为 null
// 但编译器无法推断
let processed = input!.toUpperCase() // 非空断言
}4.2 非空断言的风险
typescript
function getUserName(): string {
let name: string | null = null
// ❌ 危险!如果 name 确实为 null,会运行时崩溃
return name! // 断言编译器"相信我"
}
// ✅ 更好的方式:使用空值合并
function getUserNameSafe(): string {
let name: string | null = null
return name ?? '匿名用户' // 提供默认值更安全
}⚠️ 最佳实践:优先使用空值合并(
??)或可选链(?.),只在绝对确定时使用非空断言(!)。
5. 函数参数的空安全
5.1 函数参数
typescript
// 参数默认为非空,必须显式声明可能为空
function greet(name: string): string {
// name 在此函数中一定是非空的
return `Hello, ${name}!`
}
// 可选参数:类型为 string | undefined
function greetOptional(name?: string): string {
// name 可能是 undefined
return `Hello, ${name ?? 'World'}!`
}5.2 函数返回值
typescript
// 返回值默认为非空
function getName(): string {
return '小明' // ✅ 始终返回非空值
}
// 返回值可能为空:使用联合类型
function findUser(id: number): User | null {
// 根据条件可能返回 null
return users.find(u => u.id === id) ?? null
}6. 可选链与空值合并
6.1 可选链操作符(?.)
typescript
interface Address {
street: string
city: string
zipCode?: string // 可能为空
}
interface User {
name: string
address?: Address // 地址可能不存在
}
let user: User = { name: '小明', address: undefined }
// 多层可选访问
console.log(user.address?.street) // string | undefined
console.log(user.address?.zipCode ?? '未知') // string(提供默认值)
// 调用可能为空的方法
let callback: (() => void) | undefined = undefined
callback?.() // 安全调用,不会崩溃6.2 空值合并操作符(??)
typescript
let value: number | null = null
// ?? 只在左侧为 null 或 undefined 时返回右侧值
console.log(value ?? 0) // 0(value 是 null)
console.log(0 ?? 10) // 0(0 不是 null/undefined)
// vs 逻辑或(||)
console.log(0 || 10) // 10(0 是 falsy)
console.log('' || 10) // 10(空字符串是 falsy)
// 区别:
// ?? 只对 null/undefined 生效
// || 对所有 falsy 值(null/undefined/0/''/false)生效
// 空安全场景中应优先使用 ??7. 类型守卫(Type Guard)
7.1 typeof 守卫
typescript
function processValue(value: string | number | null): void {
if (typeof value === 'string') {
console.log(value.length) // string
} else if (typeof value === 'number') {
console.log(value.toFixed(2)) // number
} else {
console.log('null') // null
}
}7.2 in 守卫
typescript
interface Admin {
name: string
role: string
}
interface Guest {
name: string
ticket: string
}
function getAccess(user: Admin | Guest): string {
if ('role' in user) {
return `管理员:${user.role}` // 编译器知道这里是 Admin
} else {
return `访客:${user.ticket}` // 编译器知道这里是 Guest
}
}7.3 自定义类型守卫
typescript
function isUser(obj: any): obj is User {
return 'name' in obj && 'age' in obj
}
function process(obj: User | Admin): void {
if (isUser(obj)) {
console.log(obj.age) // 编译器知道这里是 User
} else {
console.log(obj.role) // 编译器知道这里是 Admin
}
}8. 常见陷阱
8.1 数组索引访问
typescript
let arr: string[] = ['a', 'b']
// ❌ 编译错误:arr[10] 可能不存在
console.log(arr[10].toUpperCase())
// ✅ 方式1:长度检查
if (arr.length > 10) {
console.log(arr[10].toUpperCase())
}
// ✅ 方式2:可选链
console.log(arr[10]?.toUpperCase())8.2 ForEach 中的空值
typescript
let items: Array<string | null> = ['a', null, 'c']
// ❌ 错误:item 可能为 null
items.forEach(item => console.log(item.length))
// ✅ 正确:在回调中校验
items.forEach(item => {
if (item !== null) {
console.log(item.length)
}
})9. 面试高频考点
Q1: ArkTS 的空安全如何防止 NullPointerException?
回答:通过编译期强制类型检查。所有可能为空的值必须用联合类型声明(如 string | null),使用前必须进行非空校验(if/可选链/空值合并),编译器会在编译期报错阻止空指针访问。
Q2: ?? 和 || 的区别?
回答:??(空值合并)只对 null/undefined 生效,而 ||(逻辑或)对所有 falsy 值(null/undefined/0/''/false)生效。在空安全场景中优先用 ??,例如数字默认值时 value ?? 0 会正确返回 0。
Q3: 非空断言(!)的作用和危险?
回答:告诉编译器"我知道这个值不为空",跳过编译期检查。危险在于如果实际为空,会导致运行时崩溃。应优先用 ?? 或 ?.。
🐱 小猫提示:空安全是 ArkTS 最基础也最重要的概念之一。记住三个关键词:联合类型声明、非空校验、空值合并。面试中一定会考!