6  第5章:数组与对象

7 第5章:数组与对象

7.1 🎯 学习目标

  • 掌握数组的创建与常用操作方法
  • 理解对象字面量与接口的使用
  • 熟练使用解构赋值
  • 掌握展开运算符与剩余运算符

7.2 5.1 数组

7.2.1 📚 数组的创建

数组是用于存储多个值的容器。

// 方式一:数组字面量(推荐)
let numbers: number[] = [1, 2, 3, 4, 5]
let fruits: string[] = ['苹果', '香蕉', '橙子']

// 方式二:泛型数组类型
let scores: Array<number> = [90, 85, 92, 88]
let names: Array<string> = ['张三', '李四', '王五']

// 方式三:创建空数组后添加元素
let emptyArr: number[] = []
emptyArr.push(1)
emptyArr.push(2)

7.2.2 📊 数组常用属性和方法

let arr: number[] = [10, 20, 30, 40, 50]

// 属性
arr.length      // 5(数组长度)

// 添加/删除元素
arr.push(60)          // 末尾添加 → [10,20,30,40,50,60]
arr.pop()              // 末尾删除 → 60
arr.unshift(5)        // 开头添加 → [5,10,20,30,40,50]
arr.shift()            // 开头删除 → 5

// 查找元素
arr.indexOf(30)       // 2(返回索引,找不到返回-1)
arr.includes(30)      // true(是否包含)
arr.find(x => x > 25)  // 30(返回第一个符合条件的元素)
arr.findIndex(x => x > 25)  // 2(返回索引)

// 数组转换
arr.map(x => x * 2)           // [20,40,60,80,100](转换每个元素)
arr.filter(x => x > 25)       // [30,40,50](过滤)
arr.reduce((acc, curr) => acc + curr, 0)  // 150(累积)
arr.sort((a, b) => a - b)    // 排序(原数组被修改)
arr.reverse()                  // 反转(原数组被修改)

// 数组切片
arr.slice(1, 4)     // [20,30,40](截取,不包含结束索引)
arr.splice(2, 1)    // 从索引2开始删除1个元素(原数组被修改)

// 数组合并
let arr2: number[] = [60, 70]
arr.concat(arr2)     // [10,20,30,40,50,60,70]

🎮 互动演示:数组方法可视化

选择数组方法,查看执行效果:

原始数组:[10, 20, 30, 40, 50]

7.3 5.2 对象

7.3.1 📦 对象字面量

对象用于存放键值对集合。

// 创建对象
let person = {
  name: '张三',
  age: 25,
  city: '北京',
  isStudent: true
}

// 访问属性
person.name       // '张三'(点号表示法)
person['age']     // 25(方括号表示法)

// 修改属性
person.age = 26
person['city'] = '上海'

// 添加新属性
person.email = 'zhangsan@example.com'

// 删除属性
delete person.isStudent

7.3.2 📋 接口(Interface)

接口用于定义对象的结构,提供类型检查和代码提示。

// 定义接口
interface Person {
  name: string
  age: number
  email?: string      // 可选属性
  readonly id: number // 只读属性
}

// 使用接口
let user: Person = {
  id: 1,
  name: '张三',
  age: 25
}
// user.id = 2  // ❌ 错误:不能修改只读属性

// 函数类型接口
interface GreetFunction {
  (name: string): string
}

let greet: GreetFunction = (name) => 'Hello, ' + name + '!'

接口 vs 类型别名

interfacetype 都可以定义对象类型,但有一些区别: - interface 可以被扩展(extends)和实现(implements) - type 可以使用联合类型、交叉类型等更复杂的类型操作 - 一般建议:定义对象形状用 interface,其他复杂类型用 type

7.3.3 🏗️ 对象解构赋值

解构赋值允许从对象中提取值并赋值给变量。

let person = {
  name: '张三',
  age: 25,
  city: '北京'
}

// 基本解构
let { name, age } = person
console.log(name)  // '张三'
console.log(age)   // 25

// 重命名变量
let { name: personName, city: personCity } = person
console.log(personName)  // '张三'

// 默认值
let { name, email = '未设置' } = person
console.log(email)  // '未设置'(person中没有email属性)

// 嵌套解构
let user = {
  id: 1,
  info: {
    name: '李四',
    age: 30
  }
}
let { info: { name, age } } = user
console.log(name, age)  // '李四' 30

7.4 5.3 展开运算符与剩余运算符

7.4.1 📦 展开运算符(…)

展开运算符用于展开数组或对象的元素。

// 展开数组
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
let combined = [...arr1, ...arr2]
console.log(combined)  // [1, 2, 3, 4, 5, 6]

// 复制数组
let original = [1, 2, 3]
let copy = [...original]  // 创建新数组,不是引用
copy.push(4)
console.log(original)  // [1, 2, 3](原数组未被修改)

// 展开对象
let obj1 = { x: 1, y: 2 }
let obj2 = { ...obj1, z: 3 }
console.log(obj2)  // { x: 1, y: 2, z: 3 }

// 合并对象(后面的属性会覆盖前面的)
let defaults = { theme: 'light', lang: 'zh' }
let userPrefs = { theme: 'dark' }
let prefs = { ...defaults, ...userPrefs }
console.log(prefs)  // { theme: 'dark', lang: 'zh' }

7.4.2 📦 剩余运算符(…)

剩余运算符用于收集多个值。

// 剩余参数(函数参数中)
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0)
}
console.log(sum(1, 2, 3, 4))  // 10

// 剩余元素(数组解构中)
let [first, second, ...rest] = [1, 2, 3, 4, 5]
console.log(first)   // 1
console.log(second)  // 2
console.log(rest)    // [3, 4, 5]

// 剩余属性(对象解构中)
let { name, ...otherProps } = { name: '张三', age: 25, city: '北京' }
console.log(name)        // '张三'
console.log(otherProps)  // { age: 25, city: '北京' }

🎮 互动练习:展开与剩余运算符

展开运算符 ...

合并数组/对象:

let arr1 = [1,2];
let arr2 = [3,4];
let merged = [...arr1, ...arr2];
// [1,2,3,4]

剩余运算符 ...

收集剩余值:

let [a, b, ...rest] = [1,2,3,4,5];
// a=1, b=2
// rest=[3,4,5]
💡 记忆技巧:
- 在 右侧(赋值语句右边):展开运算符 → 展开元素
- 在 左侧(赋值语句左边):剩余运算符 → 收集元素

7.5 5.4 实用案例

7.5.1 📊 数据处理案例

// 学生成绩处理
interface Student {
  name: string
  scores: number[]
}

let students: Student[] = [
  { name: '张三', scores: [85, 90, 78] },
  { name: '李四', scores: [92, 88, 95] },
  { name: '王五', scores: [76, 82, 79] }
]

// 计算每个学生平均分
let studentAverages = students.map(student => {
  let avg = student.scores.reduce((a, b) => a + b, 0) / student.scores.length
  return {
    name: student.name,
    average: Math.round(avg * 10) / 10
  }
})
console.log(studentAverages)
// [
//   { name: '张三', average: 84.3 },
//   { name: '李四', average: 91.7 },
//   { name: '王五', average: 79 }
// ]

// 找出平均分最高的学生
let topStudent = studentAverages.reduce((top, current) => 
  current.average > top.average ? current : top
)
console.log(`最高分:${topStudent.name},平均分:${topStudent.average}`)

7.5.2 🗂️ 对象数组去重

// 根据 id 去重
interface Item {
  id: number
  value: string
}

let items: Item[] = [
  { id: 1, value: 'A' },
  { id: 2, value: 'B' },
  { id: 1, value: 'A' },  // 重复
  { id: 3, value: 'C' }
]

// 方法一:使用 Set
let uniqueItems1 = Array.from(
  new Map(items.map(item => [item.id, item])).values()
)

// 方法二:使用 filter + findIndex
let uniqueItems2 = items.filter(
  (item, index, self) => index === self.findIndex(t => t.id === item.id)
)

console.log(uniqueItems1)  // [{id:1,value:'A'}, {id:2,value:'B'}, {id:3,value:'C'}]

7.6 📝 本章小结

本章我们学习了ArkTS中的数组与对象:

  1. 数组:创建方式、常用方法(push/pop/map/filter/reduce等)
  2. 对象:对象字面量、属性访问与修改
  3. 接口:定义对象结构,提供类型检查
  4. 解构赋值:从对象/数组中提取值
  5. 展开与剩余运算符... 的两种用法

7.7 ✏️ 练习

  1. 创建一个包含5个学生成绩的数组,使用 map 将每个成绩加上5分(满分100)
  2. 使用 filter 筛选出数组中大于平均值的元素
  3. 定义一个 Book 接口,包含 titleauthoryear 属性,创建一个书籍对象数组
  4. 使用展开运算符合并两个对象,并处理属性冲突

7.8 📚 参考资料