Skip to content

ArkTS 空安全

ArkTS 的空安全是编译期强制的,严格区分 null 和 undefined,是防止 NullPointerException 的第一道防线。


1. 空安全的核心理念

ArkTS 的空安全不是运行时检查,而是编译期强制。这意味着:

  • 如果一个变量可能为空,必须显式声明为联合类型
  • 在访问可能为空的值之前,必须进行非空校验
  • 编译器会在编译期报错,防止空指针异常

核心原则

1. 默认不可为空 → 声明的类型默认为非空
2. 允许为空 → 必须用联合类型明确标注
3. 使用前必须校验 → 编译器强制非空检查

2. null vs undefined 的区别

ArkTS 严格区分 nullundefined,它们是两个不同的概念:

含义使用场景
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 = null

3.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 可能为 null

4. 非空断言操作符(!)

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 最基础也最重要的概念之一。记住三个关键词:联合类型声明、非空校验、空值合并。面试中一定会考!