Init
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
---
|
||||
tags: [Go, 考试, Week01, 武科大服务端]
|
||||
create time: 2026-05-14 12:00
|
||||
---
|
||||
|
||||
# 金山考试 - 武科大服务端-第一次课
|
||||
|
||||
## 概述
|
||||
|
||||
本次考试覆盖 Go 语言基础核心知识点,包括 Git 版本控制基础、字符编码、变量声明、流程控制(`if` / `for` / `switch`)、函数与 `defer`、`panic` / `recover` 错误处理、字符串不可变性等。适合用于检验 Go 入门学习成果。
|
||||
|
||||
**知识点地图:**
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Go 语言基础] --> B[Git 基础]
|
||||
A --> C[字符编码 Unicode / UTF-8]
|
||||
A --> D[变量与类型 rune / string]
|
||||
A --> E[流程控制 if for switch]
|
||||
A --> F[函数 init / defer]
|
||||
A --> G[错误处理 panic recover]
|
||||
A --> H[包管理 go mod]
|
||||
```
|
||||
|
||||
## 正文
|
||||
|
||||
### 一、单选题
|
||||
|
||||
#### 1. Git 查看状态命令
|
||||
|
||||
【单选题】在 Git 中,如何查看当前状态的最简命令是什么?(5分)
|
||||
|
||||
A. `git log`
|
||||
B. `git status`
|
||||
C. `git diff`
|
||||
D. `git commit`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
---
|
||||
|
||||
#### 2. Go 注释方式
|
||||
|
||||
【单选题】在Go语言中,以下哪种注释方式是正确的?(5分)
|
||||
|
||||
Go 语言支持 `//` 形式的单行注释和 `/* */` 形式的多行注释。
|
||||
|
||||
A. `// 这是单行注释`
|
||||
B. `/* 这是多行注释 */`
|
||||
C. `# 这是单行注释`
|
||||
D. A和B都正确
|
||||
|
||||
**正确答案:**D
|
||||
**作答答案:**D
|
||||
|
||||
> [!tip] 注意
|
||||
> Go 不像 Python 或 Shell 脚本使用 `#` 做注释。这是很多初学者容易混淆的地方。
|
||||
|
||||
---
|
||||
|
||||
#### 3. 函数可见性(Export)
|
||||
|
||||
【单选题】在Go语言中,如果一个函数需要被外部包访问,函数名应该如何书写?(5分)
|
||||
|
||||
A. 首字母小写并使用 public 关键字
|
||||
B. 首字母大写
|
||||
C. 完全小写
|
||||
D. 使用 func 关键字作为前缀
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!info] Go 没有 public / private 关键字
|
||||
> Go 的包可见性遵循简单规则:**首字母大写 = 导出(公共),首字母小写 = 不导出(私有)**。同样适用于变量、常量、类型等。
|
||||
|
||||
---
|
||||
|
||||
#### 4. UTF-8 英文字符字节数
|
||||
|
||||
【单选题】在UTF-8编码中,一个英文字符通常占用多少字节?(5分)
|
||||
|
||||
A. 1字节
|
||||
B. 2字节
|
||||
C. 3字节
|
||||
D. 4字节
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
---
|
||||
|
||||
#### 5. Unicode 与 UTF-8 的关系
|
||||
|
||||
【单选题】Unicode和UTF-8之间的关系是什么?(5分)
|
||||
|
||||
A. Unicode是一种字符集,UTF-8是Unicode的一种编码方式
|
||||
B. Unicode和UTF-8都是编程语言
|
||||
C. UTF-8是一种字符集,Unicode是UTF-8的一种编码方式
|
||||
D. 没有任何关系
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**C
|
||||
|
||||
> [!warning] 易错题 — Unicode vs UTF-8
|
||||
> - **Unicode**:字符集(Character Set),为每个字符分配唯一编号(码点 Code Point),如 `U+0041` → 'A'
|
||||
> - **UTF-8**:编码方案(Encoding Scheme),将码点转换成字节序列存储
|
||||
> - 三者常见关系:Unicode(字符集)→ UTF-8 / UTF-16 / UTF-32(编码方式)
|
||||
|
||||
**对比表:**
|
||||
|
||||
| 概念 | 类比 | 作用 |
|
||||
|------|------|------|
|
||||
| Unicode | 电报码本 | 给每个字符编号 |
|
||||
| UTF-8 | 传输协议 | 把编号变成可传输的字节 |
|
||||
|
||||
---
|
||||
|
||||
#### 6. rune 类型用途
|
||||
|
||||
【单选题】在Go语言中,rune类型通常用于处理什么?(5分)
|
||||
|
||||
A. 整型数据
|
||||
B. 浮点数数据
|
||||
C. Unicode字符
|
||||
D. 布尔型数据
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!info] rune 的本质
|
||||
> `rune` 就是 `int32` 的别名,专门用于表示 Unicode 码点(字符)。在 Go 中遍历字符串时,用 `range` 循环得到的就是 `rune` 类型的单个字符。
|
||||
|
||||
---
|
||||
|
||||
#### 7. defer 与命名返回值
|
||||
|
||||
【单选题】下面代码能编译通过吗?可以的话,输出什么?(5分)
|
||||
|
||||
```go
|
||||
func DeferTest2(i int) (r int) {
|
||||
defer func() {
|
||||
r += i
|
||||
}()
|
||||
return 2
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println(DeferTest2(1))
|
||||
}
|
||||
```
|
||||
|
||||
A. 1
|
||||
B. 2
|
||||
C. 3
|
||||
D. 以上都不是
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!important] defer + 命名返回值陷阱
|
||||
> `defer` 闭包捕获的是**命名返回值的引用**。执行流程:
|
||||
> 1. `return 2` 先将 `r` 赋值为 `2`
|
||||
> 2. `defer` 函数执行,`r += 1` → `r` 变为 `3`
|
||||
> 3. 函数返回 `r = 3`
|
||||
>
|
||||
> **关键记忆:有命名返回值时,defer 修改它会影响最终返回值。**
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Main as main
|
||||
participant Func as DeferTest2(1)
|
||||
participant Defer as defer closure
|
||||
Main->>Func: 调用 DeferTest2(1)
|
||||
Func->>Func: return 2 → r = 2
|
||||
Func->>Defer: 执行 defer: r += 1
|
||||
Defer-->>Func: r = 3
|
||||
Func-->>Main: 返回 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 8. fallthrough 语义
|
||||
|
||||
【单选题】下面代码输出什么?(5分)
|
||||
|
||||
```go
|
||||
func main() {
|
||||
isMatch := func(i int) bool {
|
||||
switch(i) {
|
||||
case 1:
|
||||
fallthrough
|
||||
case 2:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
fmt.Println(isMatch(1))
|
||||
fmt.Println(isMatch(2))
|
||||
}
|
||||
```
|
||||
|
||||
A. `false true`
|
||||
B. `true true`
|
||||
C. `false false`
|
||||
D. `true false`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!tip] fallthrough 的执行逻辑
|
||||
> - `case 1:` 后跟 `fallthrough` → 跳过 case 1 剩余代码,直接进入 case 2 并执行
|
||||
> - case 2 中有 `return true` → 所以传入 1 也返回 true
|
||||
> - 传入 2 → 直接命中 case 2 → 返回 true
|
||||
> - 因此两次输出均为 `true`
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
i[输入值 i] --> check1{i == 1?}
|
||||
check1 -->|是| fall[falsthrough<br/>进入 case 2]
|
||||
check1 -->|否| check2{i == 2?}
|
||||
check2 -->|是| retTrue[return true]
|
||||
fall --> retTrue
|
||||
check2 -->|否| retFalse[return false]
|
||||
retTrue --> OUT1[输出: true]
|
||||
retFalse --> OUT2[输出: false]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 9. 字符串不可变性
|
||||
|
||||
【单选题】下面代码输出什么?(5分)
|
||||
|
||||
```go
|
||||
func main() {
|
||||
str := "hello"
|
||||
str[0] = 'x'
|
||||
fmt.Println(str)
|
||||
}
|
||||
```
|
||||
|
||||
A. hello
|
||||
B. xello
|
||||
C. compilation error
|
||||
D. 其他
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!warning] Go 字符串是只读的!
|
||||
> Go 中的 `string` 是不可变的(immutable),不能像切片那样通过索引赋值修改。如果需要修改字符串,应转为 `[]byte` 或 `[]rune`:
|
||||
>
|
||||
> ```go
|
||||
> b := []byte("hello")
|
||||
> b[0] = 'x'
|
||||
> fmt.Println(string(b)) // 输出: xello
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
#### 10. Go 包管理工具
|
||||
|
||||
【单选题】Go语言包管理工具是哪个?(5分)
|
||||
|
||||
A. dep
|
||||
B. go mod
|
||||
C. glide
|
||||
D. gopkg
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!info] Go 包管理演进
|
||||
> - **dep**:早期官方实验性工具(已废弃)
|
||||
> - **glide**:社区流行但不再维护
|
||||
> - **go mod**:Go 1.11+ 引入,Go 1.16+ 成为默认标准
|
||||
> - **gopkg.in**:第三方包分发服务,非官方工具
|
||||
|
||||
---
|
||||
|
||||
#### 11. break 跳出循环
|
||||
|
||||
【单选题】在 Go 语言中,以下哪个关键字用于跳出当前的 for 循环?( )(5分)
|
||||
|
||||
A. `break`
|
||||
B. `continue`
|
||||
C. `return`
|
||||
D. `goto`
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
---
|
||||
|
||||
#### 12. 创建 Go Module
|
||||
|
||||
【单选题】在Go语言中,如何创建一个新的module?(5分)
|
||||
|
||||
A. 使用 `go create` 命令
|
||||
B. 使用 `go mod init` 命令
|
||||
C. 使用 `go new` 命令
|
||||
D. 直接创建一个新文件夹
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
---
|
||||
|
||||
#### 13. 全局变量声明
|
||||
|
||||
【单选题】在Go中全局声明和初始化一个名为"counter"的整型变量正确的方式是?(5分)
|
||||
|
||||
A. `var counter = int(0)`
|
||||
B. `var counter int = 0`
|
||||
C. `int counter = 0`
|
||||
D. `counter := 0`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!info] var 声明 vs 短变量声明
|
||||
> - 选项 D `counter := 0` 是短变量声明,只能在**函数内部**使用,不能在包级别(全局)使用
|
||||
> - 选项 B `var counter int = 0` 在全局和局部均可使用
|
||||
> - 实际开发中常用简化写法:`var counter = 0`(省略类型,自动推断)
|
||||
|
||||
---
|
||||
|
||||
#### 14. 短变量声明
|
||||
|
||||
【单选题】以下哪种是 Go 语言的短变量声明方式?(5分)
|
||||
|
||||
A. `var x = 5`
|
||||
B. `x := 5`
|
||||
C. `x int = 5`
|
||||
D. `let x = 5`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
---
|
||||
|
||||
#### 15. init 函数参数
|
||||
|
||||
【单选题】Go语言中的init函数可以接受参数吗?(5分)
|
||||
|
||||
A. 可以,可以接受任意类型的参数
|
||||
B. 可以,但只能接受同一个包内的类型为参数
|
||||
C. 不可以,init函数不能有参数,也不能定义返回值
|
||||
D. 可以,但只能接受基本类型的参数
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
---
|
||||
|
||||
#### 16. 下划线 _ 的作用
|
||||
|
||||
【单选题】在 Go 语言中,以下关于下划线的作用描述正确的是?( )(5分)
|
||||
|
||||
A. 作为变量名,用于存储无用的值
|
||||
B. 表示私有变量
|
||||
C. 用于注释
|
||||
D. 以上都不对
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
> [!info] 下划线 _ 的多重用途
|
||||
> 1. **忽略值**:`_, err := os.Open("file")` — 不需要 err
|
||||
> 2. **导入包副作用**:`import _ "image/png"` — 仅注册,不使用
|
||||
> 3. **空标识符**:用作占位符,避免编译器报错
|
||||
|
||||
---
|
||||
|
||||
#### 17. 转义符号
|
||||
|
||||
【单选题】在 Go 语言中,字符串转义使用的符号是?(5分)
|
||||
|
||||
A. @
|
||||
B. `\`
|
||||
C. `/`
|
||||
D. #
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
---
|
||||
|
||||
#### 18. defer + recover 处理 panic
|
||||
|
||||
【单选题】如何在Go中用`defer`和`recover`处理panic?(5分)
|
||||
|
||||
A. 在defer函数中使用panic
|
||||
B. 在defer函数中调用recover进行错误恢复
|
||||
C. 使用defer来抛出错误
|
||||
D. defer和recover无法一起使用
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!info] defer 与 recover 配合模式
|
||||
> ```go
|
||||
> defer func() {
|
||||
> if r := recover(); r != nil {
|
||||
> fmt.Println("Recovered from:", r)
|
||||
> }
|
||||
> }()
|
||||
> ```
|
||||
> - `recover()` 必须放在 `defer` 中才能捕获 panic
|
||||
> - 若未发生 panic,`recover()` 返回 `nil`
|
||||
|
||||
---
|
||||
|
||||
#### 19. 引发异常的关键字
|
||||
|
||||
【单选题】在Go程序中,使用哪个关键字可以引发一个异常?(5分)
|
||||
|
||||
A. throw
|
||||
B. error
|
||||
C. exception
|
||||
D. panic
|
||||
|
||||
**正确答案:**D
|
||||
**作答答案:**D
|
||||
|
||||
---
|
||||
|
||||
#### 20. defer 执行顺序
|
||||
|
||||
【单选题】下面的Go代码块中,使用defer关键字的输出顺序是什么?(5分)
|
||||
|
||||
```go
|
||||
package main
|
||||
import "fmt"
|
||||
func main() {
|
||||
defer fmt.Println("First")
|
||||
defer fmt.Println("Second")
|
||||
defer fmt.Println("Third")
|
||||
fmt.Println("Done")
|
||||
}
|
||||
```
|
||||
|
||||
A. `First Second Third Done`
|
||||
B. `Done Third Second First`
|
||||
C. `Third Second First Done`
|
||||
D. `Done First Second Third`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!important] defer 是 LIFO(后进先出)栈
|
||||
> defer 语句按照声明的**逆序**执行,类似栈的 push/pop:
|
||||
> - 先 defer `First` → 最后执行 `First`
|
||||
> - 再 defer `Second` → 倒数执行 `Second`
|
||||
> - 最后 defer `Third` → 最先执行 `Third`
|
||||
> - 正常 `fmt.Println("Done")` → 最先执行
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> MainCode
|
||||
MainCode --> Deferred_Fist : defer Println("First")
|
||||
Deferred_Fist --> Deferred_Second : defer Println("Second")
|
||||
Deferred_Second --> Deferred_Third : defer Println("Third")
|
||||
Deferred_Third --> Normal_Println : Println("Done")
|
||||
Normal_Println --> Third_Exec : defer 开始执行
|
||||
Third_Exec --> Second_Exec : Println("Third")
|
||||
Second_Exec --> First_Exec : Println("Second")
|
||||
First_Exec --> Exit : Println("First")
|
||||
Exit --> [*]
|
||||
|
||||
note right of Normal_Println
|
||||
<b>输出: Done Third Second First</b>
|
||||
defer 按声明逆序执行
|
||||
end note
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 二、多选题
|
||||
|
||||
#### 1. main() 函数特性
|
||||
|
||||
【多选题】关于 main() 函数,下面说法正确的是?(5分)
|
||||
|
||||
A. 不能带参数;
|
||||
B. 不能定义返回值;
|
||||
C. 所在的包必须为 main 包;
|
||||
D. 可以使用 flag 包来获取和解析命令行参数;
|
||||
|
||||
**正确答案:**ABCD
|
||||
**作答答案:**ABCD
|
||||
|
||||
---
|
||||
|
||||
#### 2. switch 语句特性(一)
|
||||
|
||||
【多选题】关于 switch 语句,下面说法正确的是?(5分)
|
||||
|
||||
A. 单个 case 中,可以出现多个结果选项;
|
||||
B. 需要使用 break 来明确退出一个 case;
|
||||
C. 只有在 case 中明确添加 fallthrough 关键字,才会继续执行紧跟的下一个 case;
|
||||
D. 条件表达式必须为常量或者整数;
|
||||
|
||||
**正确答案:**AC
|
||||
**作答答案:**AC
|
||||
|
||||
> [!tip] Go switch 与 C/C++ switch 的区别
|
||||
> - Go 的 case 后**隐式自带 break**,不需要手动写
|
||||
> - 要穿透需显式使用 `fallthrough`
|
||||
> - 条件表达式可以是任意类型(不限于整数)
|
||||
|
||||
---
|
||||
|
||||
#### 3. defer 的实际应用
|
||||
|
||||
【多选题】Go语言defer的实际应用场景(5分)
|
||||
|
||||
A. 在打开文件后立即使用defer来安排文件的关闭
|
||||
B. 在数据库连接成功后使用defer来安排断开连接
|
||||
C. 仅在程序的主函数中使用defer来进行错误处理
|
||||
D. 使用defer来执行重要的日志记录
|
||||
|
||||
**正确答案:**ABD
|
||||
**作答答案:**ABD
|
||||
|
||||
---
|
||||
|
||||
#### 4. bool 变量 if 判断规范
|
||||
|
||||
【多选题】flag 是 bool 型变量,下面 if 表达式符合编码规范的是?(5分)
|
||||
|
||||
A. `if flag == 1`
|
||||
B. `if flag`
|
||||
C. `if flag == false`
|
||||
D. `if !flag`
|
||||
|
||||
**正确答案:**BCD
|
||||
**作答答案:**C
|
||||
|
||||
> [!warning] 易错题
|
||||
> - Go 不支持将整数隐式转为 bool,A 选项 `flag == 1` 语法上就不通(除非 flag 定义为 int)
|
||||
> - Go Code Review Comments 推荐:`if condition` 而非 `if condition == true`
|
||||
> - 作答选 C 可能是因为理解偏差——实际上 B 和 D 是最符合 Go 惯用法的选择
|
||||
|
||||
---
|
||||
|
||||
#### 5. 函数声明语法
|
||||
|
||||
【多选题】关于函数声明,下面语法正确的是?(5分)
|
||||
|
||||
A. `func f(a, b int) (value int, err error)`
|
||||
B. `func f(a int, b int) (value int, err error)`
|
||||
C. `func f(a, b int) (value int, error)`
|
||||
D. `func f(a int, b int) (int, int, error)`
|
||||
|
||||
**正确答案:**ABD
|
||||
**作答答案:**AB
|
||||
|
||||
> [!info] 要点解析
|
||||
> - A ✅:合并相同类型的参数 `a, b int` 是合法的
|
||||
> - B ✅:逐个写类型也是合法的
|
||||
> - C ❌:混合了命名和无命名返回值,`error` 缺少类型名(应为 `e error`)
|
||||
> - D ✅:匿名返回值是完全合法的写法
|
||||
|
||||
---
|
||||
|
||||
#### 6. 变量的自增和自减
|
||||
|
||||
【多选题】关于变量的自增和自减操作,下面语句正确的是?(5分)
|
||||
|
||||
A. `i := 1; i++`
|
||||
B. `i := 1; j = i++`
|
||||
C. `i := 1; ++i`
|
||||
D. `i := 1; i--`
|
||||
|
||||
**正确答案:**AD
|
||||
**作答答案:**ABD
|
||||
|
||||
> [!warning] Go 的独特限制
|
||||
> - Go **只有** `++` 和 `--` 两个复合运算符,且它们是**语句**,不是表达式
|
||||
> - 不能出现在赋值右侧:❌ `j = i++`(选项 B 错误)
|
||||
> - 不支持前置形式:❌ `++i`、`--i`(选项 C 错误)
|
||||
> - 只能单独使用:✅ `i++`、`i--`
|
||||
|
||||
---
|
||||
|
||||
#### 7. bool 变量赋值错误用法
|
||||
|
||||
【多选题】关于 bool 变量 b 的赋值,下面错误的用法是?(5分)
|
||||
|
||||
A. `b = true`
|
||||
B. `b = 1`
|
||||
C. `b = bool(1)`
|
||||
D. `b = (1 == 2)`
|
||||
|
||||
**正确答案:**BC
|
||||
**作答答案:**BC
|
||||
|
||||
> [!info] Go 强类型约束
|
||||
> - Go 不允许整数到 bool 的隐式转换,也不允许 `bool(1)` 这种强制转换
|
||||
> - `1 == 2` 本身就是 bool 类型,赋值合法(选项 D 是正确的写法)
|
||||
|
||||
---
|
||||
|
||||
#### 8. switch 语句特性(二)
|
||||
|
||||
【多选题】关于switch语句,下面说法正确的有?(5分)
|
||||
|
||||
A. 条件表达式必须为常量或者整数;
|
||||
B. 单个case中,可以出现多个结果选项;
|
||||
C. 需要用break来明确退出一个case;
|
||||
D. 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;
|
||||
|
||||
**正确答案:**BD
|
||||
**作答答案:**BD
|
||||
|
||||
> [!note] 对比第 2 题
|
||||
> - 本题第 D 项和第 2 题第 C 项表述略有不同——注意区分"单个 case 中多个结果选项"(`case 1, 2, 3:`)和 fallthrough 穿透是两个不同机制
|
||||
> - Go switch 的 case 可以是任意类型,不限于整数
|
||||
|
||||
---
|
||||
|
||||
#### 9. 循环语句特性
|
||||
|
||||
【多选题】关于循环语句,下面说法正确的有(5分)
|
||||
|
||||
A. 循环语句既支持 for 关键字,也支持 while 和 do-while;
|
||||
B. 关键字 for 的基本使用方法与 C/C++ 中没有任何差异;
|
||||
C. for 循环支持 continue 和 break 来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环;
|
||||
D. for 循环不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量;
|
||||
|
||||
**正确答案:**CD
|
||||
**作答答案:**ACD
|
||||
|
||||
> [!info] Go 只有 for 一种循环
|
||||
> ```go
|
||||
> // 经典三要素
|
||||
> for i := 0; i < 10; i++ {}
|
||||
> // while 风格
|
||||
> for condition {}
|
||||
> // 无限循环
|
||||
> for {}
|
||||
> ```
|
||||
> - Go **没有** while 和 do-while(选项 A 错)
|
||||
> - Go 的 for 比 C/C++ 简化了许多场景(选项 B 错)
|
||||
> - 多变量需平行赋值:`for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {}`(选项 D 对)
|
||||
|
||||
---
|
||||
|
||||
#### 10. 全局字符串变量声明
|
||||
|
||||
【多选题】定义一个包内全局字符串变量,下面语法正确的是()(5分)
|
||||
|
||||
A. `var str string`
|
||||
B. `str:=""`
|
||||
C. `str=""`
|
||||
D. `var str=""`
|
||||
|
||||
**正确答案:**AD
|
||||
**作答答案:**AD
|
||||
|
||||
> [!info] 全局 vs 局部
|
||||
> - A ✅:`var str string` — 包级别变量声明,零值为 `""`
|
||||
> - B ❌:`:=` 只在函数体内可用
|
||||
> - C ❌:包级别不能用简单赋值
|
||||
> - D ✅:`var str = ""` — 包级别变量声明,带初始值
|
||||
|
||||
---
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/EXAM/Week05]]
|
||||
- [[hhs/EXAM/Week06]]
|
||||
- [[Go/基础语法/变量与类型]]
|
||||
- [[Go/基础语法/流程控制]]
|
||||
- [[Go/基础语法/函数]]
|
||||
- [[Go/进阶/defer与panic]]
|
||||
@@ -0,0 +1,539 @@
|
||||
---
|
||||
tags: [Go, 考试, Week02, 武科大服务端]
|
||||
create time: 2026-05-14 12:00
|
||||
---
|
||||
|
||||
# 金山考试 - 武科大服务端-第二次课
|
||||
|
||||
## 概述
|
||||
|
||||
本次考试覆盖 Go 语言进阶核心知识点,包括切片(slice)、数组与切片的底层关系、Map 操作、结构体与方法、接口实现、标准库(JSON / 文件 / 时间)、指针与内存分配、并发编程(channel / select / sync)等。适合用于检验 Go 入门阶段学习成果。
|
||||
|
||||
**知识点地图:**
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Go 进阶基础] --> B[切片 slice]
|
||||
A --> C[映射 map]
|
||||
A --> D[结构体 struct]
|
||||
A --> E[方法 method]
|
||||
A --> F[接口 interface]
|
||||
A --> G[标准库 JSON/文件/时间]
|
||||
A --> H[指针 new &]
|
||||
A --> I[并发 channel select sync]
|
||||
B --> B1[底层引用共享]
|
||||
B --> B2[make vs new]
|
||||
D --> D1[字段访问 .]
|
||||
E --> E1[值接收者 vs 指针接收者]
|
||||
I --> I1[无缓冲 Channel 同步语义]
|
||||
I --> I2[select 多路复用]
|
||||
I --> I3[WaitGroup / Mutex / RWMutex]
|
||||
```
|
||||
|
||||
## 正文
|
||||
|
||||
### 一、单选题
|
||||
|
||||
#### 1. 错误的切片声明方式
|
||||
|
||||
【单选题】下列关于 Go 语言的切片声明,哪个是错误的?(5分)
|
||||
|
||||
A. `var slice1 []int`
|
||||
B. `slice2 := []int{}`
|
||||
C. `slice3 := make([]int, 0)`
|
||||
D. `slice4 := new([]int)`
|
||||
|
||||
**正确答案:**D
|
||||
**作答答案:**D
|
||||
|
||||
> [!warning] new vs make
|
||||
> - `make(T, args)`:仅用于 **slice、map、channel**,返回类型 T 本身并初始化
|
||||
> - `new(T)`:返回指向类型 T 的零值指针 `*T`,不初始化内部结构
|
||||
> - `new([]int)` 返回的是 `*[]int`,虽然能编译但不能直接当作切片使用
|
||||
|
||||
---
|
||||
|
||||
#### 2. 创建长度 0 容量 10 的切片
|
||||
|
||||
【单选题】要创建一个初始长度为 0,容量为 10 的整数切片,应该使用(5分)
|
||||
|
||||
A. `new([]int)`
|
||||
B. `make([]int, 0, 10)`
|
||||
C. `new([10]int)`
|
||||
D. `make([10]int)`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!info] make 的参数含义
|
||||
> `make([]int, len, cap)` — len 和 cap 均可选:
|
||||
> - `make([]int, 0, 10)` → 长度 0、容量 10
|
||||
> - `make([]int, 10)` → 长度 10、容量 10(cap 省略时等于 len)
|
||||
> - 数组用 `[N]T` 语法声明,不需要 make
|
||||
|
||||
---
|
||||
|
||||
#### 3. 数组 vs 切片的长度特性
|
||||
|
||||
【单选题】以下关于数组和切片的说法,正确的是(5分)
|
||||
|
||||
A. 数组的长度可以动态改变,切片不行
|
||||
B. 切片的长度可以动态改变,数组不行
|
||||
C. 数组和切片的长度都可以动态改变
|
||||
D. 数组和切片的长度都不能动态改变
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!tip] 核心区别一句话
|
||||
> 数组长度是**类型的一部分**(`[5]int` 和 `[10]int` 是不同的类型),不可变;切片是对数组的**轻量描述符**(ptr + len + cap),长度可以通过 append/slice 操作动态变化。
|
||||
|
||||
---
|
||||
|
||||
#### 4. 切片修改对底层数组的影响
|
||||
|
||||
【单选题】给定以下 Go 代码,运行后输出结果是什么?(5分)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [5]int{1, 2, 3, 4, 5}
|
||||
slice := arr[1:4] // 引用 arr[1]=2, arr[2]=3, arr[3]=4
|
||||
slice[0] = 10 // 等价于 arr[1] = 10
|
||||
fmt.Println(arr)
|
||||
}
|
||||
```
|
||||
|
||||
A. `[1 2 3 4 5]`
|
||||
B. `[1 10 3 4 5]`
|
||||
C. `[10 2 3 4 5]`
|
||||
D. 编译错误
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!important] 切片共享底层数组
|
||||
> 切片是对底层数组的**引用**。`arr[1:4]` 创建的切片其索引 0 对应 `arr[1]`,因此 `slice[0] = 10` 会直接修改 `arr[1]`。这是理解 Go 切片性能优势的关键——零拷贝传递数据。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Arr as arr = [1,2,3,4,5]
|
||||
participant Slc as slice = arr[1:4]
|
||||
participant Set as slice[0] = 10
|
||||
Arr->>Slc: 创建引用(ptr→arr[1], len=3, cap=4)
|
||||
Slc->>Set: 写 slice[0](即 arr[1])
|
||||
Set-->>Arr: arr[1] 被改为 10
|
||||
Arr-->>Arr: arr = [1,10,3,4,5]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 5. Map 的 delete 操作
|
||||
|
||||
【单选题】观察下面的 Go 代码,判断运行结果是什么?(5分)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
m := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
delete(m, "b")
|
||||
fmt.Println(m)
|
||||
}
|
||||
```
|
||||
|
||||
A. `map[a:1 b:2 c:3]`
|
||||
B. `map[a:1 b:0 c:3]`
|
||||
C. `map[a:1 c:3]`
|
||||
D. `map[a:1]`
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!info] delete 的语义
|
||||
> `delete(map, key)` 直接删除键值对,key 不存在时也是安全的(noop,不 panic)。打印时只保留剩余元素。
|
||||
|
||||
---
|
||||
|
||||
#### 6. map 的 json.Unmarshal
|
||||
|
||||
【单选题】关于 map,下面说法正确的是?(5分)
|
||||
|
||||
A. `json.Unmarshal()` 的入参必须为 map 的地址
|
||||
B. 在函数调用中传递 map,则子函数中对 map 元素的增加不会导致父函数中 map 的修改
|
||||
C. 不能使用内置函数 `delete()` 删除 map 的元素
|
||||
D. 以上都不对
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
> [!info] 为什么必须是地址?
|
||||
> `json.Unmarshal(data, &m)` 需要指针才能修改原 map。而 `json.Marshal(data, m)` 不需要地址(值即可读取)。
|
||||
>
|
||||
> **补充:map 作为参数传递**——Go 中 map 是引用类型(内部是指针结构体),所以子函数通过同一底层数据结构增删元素,父函数可见。选项 B 说反了。
|
||||
|
||||
---
|
||||
|
||||
#### 7. 结构体字段访问
|
||||
|
||||
【单选题】在 Go 语言中,如果你有一个结构体实例 `myStruct`,你如何访问其字段 `field1` 的值?(5分)
|
||||
|
||||
A. `myStruct[field1]`
|
||||
B. `myStruct->field1`
|
||||
C. `myStruct.field1`
|
||||
D. `*myStruct.field1`
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!note] Go 没有箭头运算符
|
||||
> Go 不使用 `->`(C/C++ 风格),也不使用 `[key]`(像 map 那样)。统一用点号 `.` 访问字段。当持有指针 `p` 时,也只需 `p.Field`,编译器自动解引用。
|
||||
|
||||
---
|
||||
|
||||
#### 8. 结构体方法的错误认知
|
||||
|
||||
【单选题】以下关于结构体方法的说法,错误的是(5分)
|
||||
|
||||
A. 值接收者方法不能修改结构体的字段值
|
||||
B. 指针接收者方法可以修改结构体的字段值
|
||||
C. 值接收者方法在传递参数时效率更高
|
||||
D. 指针接收者方法在结构体较大时更节省内存
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!warning] 值接收者的陷阱
|
||||
> - A ✅:值接收者是副本,修改不影响原对象
|
||||
> - B ✅:指针接收者直接操作原对象
|
||||
> - C ❌:**这是本题答案**。值接收者在传参时需要复制整个结构体,当结构体较大时反而更慢
|
||||
> - D ✅:指针只传 8 字节(ptr),避免完整复制
|
||||
|
||||
---
|
||||
|
||||
#### 9. 接口实现的完整性
|
||||
|
||||
【单选题】Go 中如何正确实现一个接口?(5分)
|
||||
|
||||
A. 只需要声明接口中的所有方法
|
||||
B. 只需要部分实现接口中的方法
|
||||
C. 需要声明并实现接口中的所有方法
|
||||
D. 接口不能被实现,只能被继承
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!info] Go 接口是隐式实现
|
||||
> Go 没有 `implements` 关键字。只要一个类型的**所有方法签名**与接口一致,就自动实现了该接口。缺少任一方法都会编译报错。
|
||||
|
||||
---
|
||||
|
||||
#### 10. JSON 处理的标准库
|
||||
|
||||
【单选题】在 Go 中处理 JSON 数据的推荐方法是?(5分)
|
||||
|
||||
A. 使用 `encoding/json` 标准库
|
||||
B. 使用外部包如 `jsoniter`
|
||||
C. 手动解析字符串
|
||||
D. 使用 `bufio` 读取器
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
> [!info] encoding/json
|
||||
> Go 标准库 `encoding/json` 提供 `Marshal` / `Unmarshal` 两个核心函数,通过结构体 tag(`` `json:"name"` ``)控制序列化行为,满足绝大多数场景需求。
|
||||
|
||||
---
|
||||
|
||||
#### 11. 格式化输出的函数选择
|
||||
|
||||
【单选题】在 Go 中,如何通过 fmt 包输出格式化的字符串?(5分)
|
||||
|
||||
A. `fmt.Print()`
|
||||
B. `fmt.Println()`
|
||||
C. `fmt.Printf()`
|
||||
D. `fmt.Format()`
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!tip] fmt 包常用函数速记
|
||||
> - `Print`:无换行,无格式
|
||||
> - `Println`:自动追加换行
|
||||
> - `Printf`:**支持占位符格式化**,如 `%d`、`%s`、`%v`
|
||||
|
||||
---
|
||||
|
||||
#### 12. 读取文件的便捷方式
|
||||
|
||||
【单选题】选择 Go 语言中正确的方式来读取文件。(5分)
|
||||
|
||||
A. `os.ReadFile()`
|
||||
B. `os.OpenFile()`
|
||||
C. `ioutil.ReadAll()`
|
||||
D. `file.Read()`
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
> [!note] ioutil 已废弃
|
||||
> `ioutil.ReadAll` 和 `ioutil.ReadFile` 在 Go 1.16+ 中被迁移到 `io` 和 `os` 包中。推荐写法:`os.ReadFile("path")` 一次性读取全部文件内容到 `[]byte`。
|
||||
|
||||
---
|
||||
|
||||
#### 13. 时间日期的标准库
|
||||
|
||||
【单选题】在 Go 中,用于处理时间和日期的标准库是哪一个?(5分)
|
||||
|
||||
A. `time`
|
||||
B. `date`
|
||||
C. `timer`
|
||||
D. `clock`
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
> [!info] time 包核心用法
|
||||
> ```go
|
||||
> now := time.Now() // 当前时间
|
||||
> time.Sleep(1 * time.Second) // 休眠
|
||||
> t.Format("2006-01-02 15:04:05") // 格式化(记忆:参考时间 2006-01-02)
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
#### 14. new 的正确使用场景
|
||||
|
||||
【单选题】以下哪种情况应该使用 `new` 来分配内存?(5分)
|
||||
|
||||
A. 创建一个切片
|
||||
B. 创建一个映射
|
||||
C. 创建一个结构体指针
|
||||
D. 扩展一个切片的容量
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!info] new vs make 对比
|
||||
> | 用途 | new(T) | make(T, ...)|
|
||||
> |------|--------|-------------|
|
||||
> | 返回类型 | `*T`(指针) | T 本身 |
|
||||
> | 适用于 | 所有类型 | slice, map, channel |
|
||||
> | 初始化 | 零值 | 完整初始化 |
|
||||
> | 典型场景 | `new(Person)` | `make([]int, 10)` |
|
||||
|
||||
---
|
||||
|
||||
#### 15. 无缓冲 channel 的同步语义
|
||||
|
||||
【单选题】无缓冲 channel 的特点是什么?(5分)
|
||||
|
||||
A. 发送操作永远不会阻塞
|
||||
B. 接收操作永远不会阻塞
|
||||
C. 发送和接收操作必须同时发生
|
||||
D. 可以存储无限数量的值
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!important] 无缓冲 Channel = 同步通道
|
||||
> - 容量为 0,send 和 receive **必须同时就绪**才能完成
|
||||
> - 生产者 send 后阻塞直到有消费者 receive
|
||||
> - 消费者 receive 后阻塞直到有生产者 send
|
||||
> - 这是 goroutine 间最原子的同步机制
|
||||
|
||||
---
|
||||
|
||||
#### 16. Select 语句的作用
|
||||
|
||||
【单选题】Go 语言中的 `select` 语句用于什么目的?(5分)
|
||||
|
||||
A. 声明变量
|
||||
B. 捕获异常
|
||||
C. 监听多个 Channel 操作
|
||||
D. 同步 Goroutine
|
||||
|
||||
**正确答案:**C
|
||||
**作答答案:**C
|
||||
|
||||
> [!info] select 的行为规则
|
||||
> - 每个 case 必须是 channel 的 send 或 receive 操作
|
||||
> - 所有 case 求值后,随机选择一个可用的执行
|
||||
> - 若无可用 case 且有 `default`,立即执行 default
|
||||
> - 若无可用 case 且无 default,阻塞直到某个 case 就绪
|
||||
|
||||
---
|
||||
|
||||
#### 17. Goroutine 同步的原语选择
|
||||
|
||||
【单选题】在 Go 语言中,哪个 sync 原语可以用于确保多个 Goroutine 的同步执行?(5分)
|
||||
|
||||
A. `WaitGroup`
|
||||
B. `Mutex`
|
||||
C. `Atomic`
|
||||
D. `Context`
|
||||
|
||||
**正确答案:**A
|
||||
**作答答案:**A
|
||||
|
||||
> [!info] 各 sync 原语分工
|
||||
> - `sync.WaitGroup`:**等待一组 goroutine 完成**(Add / Done / Wait)
|
||||
> - `sync.Mutex`:互斥锁,保护临界区
|
||||
> - `sync/atomic`:原子操作,无锁并发
|
||||
> - `context.Context`:取消信号与超时控制
|
||||
|
||||
---
|
||||
|
||||
#### 18. 取地址运算符
|
||||
|
||||
【单选题】在 Go 中获取变量的内存地址应使用什么运算符?(5分)
|
||||
|
||||
A. `*`
|
||||
B. `&`
|
||||
C. `#`
|
||||
D. `@`
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!tip] 指针运算口诀
|
||||
> - `&x`:取 x 的地址
|
||||
> - `*p`:解除 p 指向,访问目标值
|
||||
> - 二者互为逆运算:`*&x == x`,`&*p == p`
|
||||
|
||||
---
|
||||
|
||||
#### 19. RWMutex vs Mutex
|
||||
|
||||
【单选题】在 Go 中,`sync.RWMutex` 与 `sync.Mutex` 的主要区别是?(5分)
|
||||
|
||||
A. Mutex 只支持读操作
|
||||
B. RWMutex 允许多个读锁或一个写锁,Mutex 仅支持互斥访问
|
||||
C. 两者没有区别
|
||||
D. RWMutex 不能与 channel 同时使用
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!info] 读写锁的性能意义
|
||||
> 当读多写少时,`RWMutex` 显著优于 `Mutex`:多个 goroutine 可同时持有多把读锁 (`RLock`),只有写操作 (`Lock`) 时才互斥。这是典型的「读并发、写串行」优化手段。
|
||||
|
||||
---
|
||||
|
||||
#### 20. 使用指针接收者的原因
|
||||
|
||||
【单选题】以下结构体方法定义中,使用指针接收者的原因通常是(5分)
|
||||
|
||||
A. 方法内部需要修改结构体的字段值
|
||||
B. 为了提高性能(避免大结构体拷贝)
|
||||
C. 为了遵循代码规范
|
||||
D. 以上都是
|
||||
|
||||
**正确答案:**D
|
||||
**作答答案:**D
|
||||
|
||||
> [!info] 何时用指针接收者?
|
||||
> 1. 需要**修改**原对象状态
|
||||
> 2. 结构体**较大**,减少拷贝开销
|
||||
> 3. 需要保证**一致性**(同一个对象的方法要么全用指针,要么全用值接收者)
|
||||
|
||||
---
|
||||
|
||||
#### 21. 值接收者方法不会修改原对象
|
||||
|
||||
【单选题】对于以下结构体和方法定义,调用 ChangeName 方法后,原始的 Person 结构体实例的 Name 字段会改变吗?(5分)
|
||||
|
||||
```language
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func (p Person) ChangeName(newName string) {
|
||||
p.Name = newName
|
||||
}
|
||||
```
|
||||
|
||||
A. 会
|
||||
B. 不会
|
||||
C. 不确定
|
||||
D. 以上都不对
|
||||
|
||||
**正确答案:**B
|
||||
**作答答案:**B
|
||||
|
||||
> [!warning] 经典面试题
|
||||
> `ChangeName` 使用**值接收者**,这意味着 `p` 是原对象的**副本**。方法内修改 `p.Name` 只会影响副本,原对象不受影响。若希望修改生效,应将接收者改为指针 `(p *Person)`。
|
||||
|
||||
---
|
||||
|
||||
### 二、多选题
|
||||
|
||||
#### 1. 指针访问成员变量的方式
|
||||
|
||||
【多选题】通过指针变量 p 访问其成员变量 name,有哪几种方式?(5分)
|
||||
|
||||
A. `p.name`
|
||||
B. `(&p).name`
|
||||
C. `(*p).name`
|
||||
D. `p->name`
|
||||
|
||||
**正确答案:**AC
|
||||
**作答答案:**AC
|
||||
|
||||
> [!info] 指针访问字段两种方式
|
||||
> - `p.name` ✅:Go 语法糖,编译器自动解引用,推荐写法
|
||||
> - `(*p).name` ✅:先解引用再 `.name`,等价但稍显繁琐
|
||||
> - `(&p).name` ❌:`&p` 是指针的地址(双重指针 `**P`),不符合题意
|
||||
> - `p->name` ❌:Go 没有 `->` 运算符
|
||||
|
||||
---
|
||||
|
||||
#### 2. panic 和 recover 的使用
|
||||
|
||||
【多选题】关于 Go 语言中 panic 和 recover 的使用,以下正确的是?(5分)
|
||||
|
||||
A. panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效
|
||||
B. recover 用于终止 panic 的异常传递过程
|
||||
C. panic 无法被捕获,会直接导致程序崩溃
|
||||
D. 使用 panic 和 recover 可以替代传统的错误处理方式
|
||||
|
||||
**正确答案:**AB
|
||||
**作答答案:**ABC
|
||||
|
||||
> [!warning] 易错辨析
|
||||
> - A ✅:recover 放在 defer 中能捕获当前 goroutine 的 panic,阻止程序崩溃
|
||||
> - B ✅:recover 被调用后,panic 链中断,程序恢复正常运行
|
||||
> - C ❌:panic **可以**被 recover 捕获,并非一定会导致程序崩溃
|
||||
> - D ❌:Go 设计哲学中,panic 用于**真正的异常情况**(如编程错误、初始化失败),业务错误应用 error 返回
|
||||
|
||||
---
|
||||
|
||||
#### 3. 数组和切片的综合理解
|
||||
|
||||
【多选题】在 Go 语言中,关于数组(array)和切片(slice),以下说法正确的是?(5分)
|
||||
|
||||
A. 数组定义时必须指定长度,且长度是数组类型的一部分
|
||||
B. 切片定义时不需要指定长度,其长度可以在运行时动态变化
|
||||
C. 数组作为函数参数传递时,传递的是数组的副本,函数内对数组的修改不会影响原始数组
|
||||
D. 切片作为函数参数传递时,传递的是切片结构体的副本,函数内对切片的修改会反映到原始切片上
|
||||
|
||||
**正确答案:**ABCD
|
||||
**作答答案:**ABCD
|
||||
|
||||
> [!important] 四个选项全对的原因
|
||||
> - A ✅:`[3]int` 和 `[5]int` 是不同长度决定类型
|
||||
> - B ✅:slice 本质是「指针+长度+容量」描述符
|
||||
> - C ✅:数组传值是**深拷贝**整个数组内容
|
||||
> - D ✅:slice 内部含底层数组指针,虽然 slice 自身是副本(ptr/len/cap 三件套被拷贝),但对 `slice[i] = x` 等元素级修改通过 ptr 反映到底层数组,因此源数据可见
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/EXAM/Week01]]
|
||||
- [[hhs/EXAM/Week05]]
|
||||
- [[hhs/EXAM/Week06]]
|
||||
@@ -0,0 +1,811 @@
|
||||
---
|
||||
tags: [exam, gin, go, http, jwt, cors, middleware, session, cookie, web]
|
||||
create time: 2026-05-14 18:30
|
||||
---
|
||||
|
||||
# 金山考试 - 武科大服务端 - 第三次课
|
||||
|
||||
## 概述
|
||||
|
||||
武科大服务端第三次课的考试错题整理与知识点拓展。共 30 道单选题,满分 150 分,涉及 HTTP 协议基础、Gin 框架中间件、Cookie/Session/JWT 认证、CORS 跨域、Nginx 反向代理等核心后端开发知识。
|
||||
|
||||
**得分:145/150(仅第 12 题出错)**
|
||||
|
||||
---
|
||||
|
||||
## 我的错题
|
||||
|
||||
### 第 12 题 — Gin 全局注册中间件
|
||||
|
||||
**你的作答:** D · **正确答案:** A
|
||||
|
||||
```go
|
||||
// 错误答案 ❌
|
||||
router.Middleware(middleware)
|
||||
|
||||
// 正确答案 ✔
|
||||
router.Use(middleware)
|
||||
```
|
||||
|
||||
> [!tip] 考点解析
|
||||
>
|
||||
> Gin 使用 `Use()` 方法注册**全局中间件**,参数接受一个或多个 `HandlerFunc`:
|
||||
>
|
||||
> ```go
|
||||
> router := gin.Default()
|
||||
> // 全局注册:所有路由都会经过此中间件
|
||||
> router.Use(GinLogger())
|
||||
> router.Use(GinRecovery())
|
||||
>
|
||||
> // 组级注册:只对 /v1 组生效
|
||||
> v1 := router.Group("/v1")
|
||||
> v1.Use(AuthMiddleware())
|
||||
> {
|
||||
> v1.GET("/user", getUserHandler)
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> **记忆要点:** `Use` 是链式的,中间件按注册顺序执行。
|
||||
>
|
||||
> | 方法 | 作用范围 | 说明 |
|
||||
> |------|---------|------|
|
||||
> | `router.Use()` | 全局中间件 | 所有路由生效 |
|
||||
> | `group.Use()` | 组中间件 | 仅该 Group 下路由生效 |
|
||||
|
||||
---
|
||||
|
||||
## 正文
|
||||
|
||||
### 一、HTTP 协议基础
|
||||
|
||||
> [!abstract] 本节覆盖:第 1、5、29 题
|
||||
>
|
||||
> HTTP 是 Web 的基石。理解它的无状态特性、方法语义和端口规范,是后续学习所有后端技术的前提。
|
||||
|
||||
#### 第 1 题
|
||||
|
||||
【单选题】HTTP请求中的 GET 和 POST 有什么主要区别?(5分)
|
||||
|
||||
A. GET请求通常用于请求数据,POST请求只用于提交数据。
|
||||
|
||||
B. GET请求的数据会附加在URL之后,POST请求的数据放在请求体内。
|
||||
|
||||
C. GET请求可以获取新的资源,POST请求无法获取资源。
|
||||
|
||||
D. GET请求不安全,POST请求比GET请求安全。
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!tip] 深度理解
|
||||
>
|
||||
> - **URL vs Body**:GET 的参数拼在 URL 后(如 `/api/user?id=1`),受长度限制且会出现在浏览器历史记录中;POST 的数据放在 Request Body 中,没有长度限制。
|
||||
> - **幂等性**:GET 应该是幂等的(多次请求结果一致),POST 不是。
|
||||
> - **安全性误区**:POST != 安全!两者都是明文传输(除非配合 HTTPS)。敏感数据应该用 HTTPS,而非依赖 POST。
|
||||
|
||||
#### 第 5 题
|
||||
|
||||
【单选题】下列哪一项是 HTTP 的默认端口号?(5分)
|
||||
|
||||
A. 21
|
||||
|
||||
B. 80
|
||||
|
||||
C. 443
|
||||
|
||||
D. 8080
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!note] 常见端口速查
|
||||
>
|
||||
> | 端口 | 协议 | 用途 |
|
||||
> |------|------|------|
|
||||
> | 80 | HTTP | 标准 Web 服务 |
|
||||
> | 443 | HTTPS | 加密 Web 服务 |
|
||||
> | 21 | FTP | 文件传输 |
|
||||
> | 3306 | MySQL | 数据库 |
|
||||
> | 8080 | HTTP | 常用开发/替代端口 |
|
||||
|
||||
#### 第 29 题
|
||||
|
||||
【单选题】HTTP协议被称为"无状态 (Stateless)"协议,这到底意味着什么?(5分)
|
||||
|
||||
A. 服务器无法一次性处理多个状态的请求
|
||||
|
||||
B. 每次请求都是独立的,服务器默认不会记住之前的请求信息
|
||||
|
||||
C. 数据在传输过程中没有加密状态
|
||||
|
||||
D. 客户端不需要等待服务器的响应即可发送下一个请求
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!example] 为什么需要 Cookie/Session/JWT?
|
||||
>
|
||||
> 因为 HTTP 是无状态的——每次请求都是一次"失忆"的新对话。为了维持用户登录态,我们需要额外的机制来"记住"用户是谁:
|
||||
>
|
||||
> ```mermaid
|
||||
> sequenceDiagram
|
||||
> participant Browser as 浏览器
|
||||
> participant Server as 服务端
|
||||
> Browser->>Server: 1. POST /login (账号密码)
|
||||
> Server->>Browser: 2. Set-Cookie: sessionId=abc123
|
||||
> Browser->>Server: 3. GET /profile (携带 Cookie)
|
||||
> Server->>Server: 4. 根据 sessionId 查 Session 数据
|
||||
> Server->>Browser: 5. 返回用户个人信息
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
### 二、缓存控制
|
||||
|
||||
> [!abstract] 本节覆盖:第 2、3、6 题
|
||||
>
|
||||
> 缓存是 Web 性能优化的核心手段。理解 Cache-Control 和 304 状态码,能让你的应用快得多。
|
||||
|
||||
#### 第 2 题
|
||||
|
||||
【单选题】以下哪个 HTTP 头字段可以用于管理缓存的过期时间?(5分)
|
||||
|
||||
A. Content-Type
|
||||
|
||||
B. Cache-Control
|
||||
|
||||
C. Authorization
|
||||
|
||||
D. Location
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
#### 第 3 题
|
||||
|
||||
【单选题】哪种 HTTP 状态码代表"未修改"?(5分)
|
||||
|
||||
A. 200 OK
|
||||
|
||||
B. 304 Not Modified
|
||||
|
||||
C. 404 Not Found
|
||||
|
||||
D. 500 Internal Server Error
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
#### 第 6 题
|
||||
|
||||
【单选题】HTTP协议中哪个状态码表示请求的资源未被修改?(5分)
|
||||
|
||||
A. 200
|
||||
|
||||
B. 403
|
||||
|
||||
C. 304
|
||||
|
||||
D. 500
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
> [!tip] Cache-Control + 304 组合拳
|
||||
>
|
||||
> 两种缓存策略各有分工:
|
||||
>
|
||||
> | 策略 | 原理 | 关键头 |
|
||||
> |------|------|--------|
|
||||
> | **强缓存** | 不发起请求,直接读本地缓存 | `Cache-Control: max-age=3600` |
|
||||
> | **协商缓存** | 发请求问服务端"我缓存过期的没" | `ETag` + `If-None-Match` |
|
||||
>
|
||||
> **完整流程:**
|
||||
>
|
||||
> ```mermaid
|
||||
> sequenceDiagram
|
||||
> participant 浏览器
|
||||
> participant 服务端
|
||||
> 浏览器->>服务端: GET /data (带 If-None-Match: "abc123")
|
||||
> 服务端->>服务端: 检查资源是否变化
|
||||
> alt 资源未变化
|
||||
> 服务端-->>浏览器: 304 Not Modified
|
||||
> 浏览器->>浏览器: 使用本地缓存
|
||||
> else 资源已变化
|
||||
> 服务端-->>浏览器: 200 + 新内容 + 新 ETag
|
||||
> end
|
||||
> ```
|
||||
>
|
||||
> **两个关键头**:
|
||||
> - **ETag**:服务器生成的资源指纹(哈希值),如 `"abc123"`
|
||||
> - **If-None-Match**:浏览器下次请求时把上次的 ETag 传回来让服务端比对
|
||||
|
||||
---
|
||||
|
||||
### 三、Gin 框架核心
|
||||
|
||||
> [!abstract] 本节覆盖:第 8、13~18、20、27 题
|
||||
>
|
||||
> Gin 是 Go 生态最流行的 Web 框架。掌握 Context、路由、绑定和中间件,你就掌握了 Gin 的命脉。
|
||||
|
||||
#### 第 8 题
|
||||
|
||||
【单选题】在Go language HTTP server中,如何从HTTP请求中解析查询参数?(5分)
|
||||
|
||||
A. 使用req.URL.Query()方法
|
||||
|
||||
B. 调用req.ParseForm()然后使用req.Form
|
||||
|
||||
C. 使用req.GetHeader('Query')方法
|
||||
|
||||
D. 手动解析req.Body流中的内容
|
||||
|
||||
**正确答案:** A
|
||||
|
||||
**作答答案:** A
|
||||
|
||||
> [!code Go] 查询参数解析对比
|
||||
>
|
||||
> ```go
|
||||
> // 方式1:net/http 原生
|
||||
> values := req.URL.Query() // map[string][]string
|
||||
> id := values.Get("id") // string
|
||||
>
|
||||
> // 方式2:Gin 封装(更简洁)
|
||||
> id := c.Query("id") // string(单值)
|
||||
> ids := c.QueryArray("id") // []string(多值)
|
||||
> fid := c.DefaultQuery("id", "1") // 带默认值
|
||||
> ```
|
||||
|
||||
#### 第 13 题
|
||||
|
||||
【单选题】编写一个Gin中间件(比如计算请求耗时)时,哪个方法用于暂停当前中间件的执行,先去执行后续的业务逻辑(Handler),等业务执行完再回来继续执行中间件剩余的代码?(5分)
|
||||
|
||||
A. c.Next()
|
||||
|
||||
B. c.Abort()
|
||||
|
||||
C. c.Stop()
|
||||
|
||||
D. c.Return()
|
||||
|
||||
**正确答案:** A
|
||||
|
||||
**作答答案:** A
|
||||
|
||||
> [!important] Next() vs Abort() — 中间件的双向控制
|
||||
>
|
||||
> ```go
|
||||
> func Logger() gin.HandlerFunc {
|
||||
> start := time.Now() // 记录开始时间
|
||||
> c.Next() // ▶ 放行给后续中间件/handler
|
||||
> // ◀ 这里会继续执行(像洋葱剥开又合上)
|
||||
> log.Printf("%s %s %v", c.Writer.Status(), c.Request.URL.Path, time.Since(start))
|
||||
> }
|
||||
>
|
||||
> func Auth() gin.HandlerFunc {
|
||||
> token := c.GetHeader("Authorization")
|
||||
> if token == "" {
|
||||
> c.Abort() // 中断链条,不再执行后续中间件
|
||||
> c.JSON(401, gin.H{"error": "缺少认证"})
|
||||
> return
|
||||
> }
|
||||
> c.Next() // 验证通过,放行
|
||||
> }
|
||||
> ```
|
||||
|
||||
#### 第 14 题
|
||||
|
||||
【单选题】当前端发送了一段JSON数据,后端想将其解析并映射到Go的一个结构体(Struct)上,推荐使用哪个方法?(5分)
|
||||
|
||||
A. c.Query()
|
||||
|
||||
B. c.PostForm()
|
||||
|
||||
C. c.ShouldBindJSON()
|
||||
|
||||
D. c.GetString()
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
> [!code Go] Gin 常用绑定方法
|
||||
>
|
||||
> ```go
|
||||
> type User struct {
|
||||
> Name string `json:"name" binding:"required"`
|
||||
> Age int `json:"age" binding:"gte=0,lte=150"`
|
||||
> }
|
||||
>
|
||||
> func handler(c *gin.Context) {
|
||||
> var u User
|
||||
> _ = c.ShouldBindJSON(&u) // 绑定 JSON body
|
||||
> // 其他绑定方式:
|
||||
> c.ShouldBindQuery(&u) // 绑定 ?name=x&age=18
|
||||
> c.ShouldBindUri(&u) // 绑定 /users/123
|
||||
> c.ShouldBind(&u) // 自动识别 content-type
|
||||
> }
|
||||
> ```
|
||||
|
||||
#### 第 15 题
|
||||
|
||||
【单选题】在Gin中,你想设计一个路由来获取特定ID的用户信息(例如从 /user/123 中提取出 123),正确的动态路由定义方式是什么?(5分)
|
||||
|
||||
A. router.GET("/user/?id", handler)
|
||||
|
||||
B. router.GET("/user/:id", handler)
|
||||
|
||||
C. router.GET("/user/*id", handler)
|
||||
|
||||
D. router.GET("/user/{id}", handler)
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!note] Gin 路由语法速记
|
||||
>
|
||||
> | 模式 | 含义 | 示例 |
|
||||
> |------|------|------|
|
||||
> | `/user/:id` | 单变量 | `/user/123` → id="123" |
|
||||
> | `/user/*path` | 通配符 | `/user/a/b/c` → path="/a/b/c" |
|
||||
> | `/user/search` | 固定路径 | 精确匹配 |
|
||||
|
||||
#### 第 16 题
|
||||
|
||||
【单选题】在Gin框架中,中间件的用途是什么?(5分)
|
||||
|
||||
A. 在处理请求和发送响应之间执行额外的逻辑。
|
||||
|
||||
B. 生成项目的文档。
|
||||
|
||||
C. 处理数据库连接。
|
||||
|
||||
D. 定义项目配置。
|
||||
|
||||
**正确答案:** A
|
||||
|
||||
**作答答案:** A
|
||||
|
||||
#### 第 17 题
|
||||
|
||||
【单选题】在Gin框架中,若需要在中间件之间共享数据,应使用哪种方式?(5分)
|
||||
|
||||
A. 利用c.Set存储数据,并在另一个中间件中通过c.Get获取。
|
||||
|
||||
B. 直接将数据作为参数传递至下一个中间件。
|
||||
|
||||
C. 使用全局变量来存储和共享数据。
|
||||
|
||||
D. 创建一个新的结构体来在中间件之间传递数据。
|
||||
|
||||
**正确答案:** A
|
||||
|
||||
**作答答案:** A
|
||||
|
||||
> [!code Go] c.Set / c.Get 在中间间共享数据
|
||||
>
|
||||
> ```go
|
||||
> func UserInfoExtractor() gin.HandlerFunc {
|
||||
> user := fetchUserFromToken(c.GetHeader("Authorization"))
|
||||
> c.Set("userID", user.ID) // 存入上下文
|
||||
> c.Set("username", user.Name)
|
||||
> c.Next()
|
||||
> }
|
||||
>
|
||||
> func GetProfile(c *gin.Context) {
|
||||
> // 直接从上下文中取,无需层层传参
|
||||
> id, _ := c.Get("userID")
|
||||
> name, _ := c.Get("username")
|
||||
> c.JSON(200, gin.H{"id": id, "name": name})
|
||||
> }
|
||||
> ```
|
||||
|
||||
#### 第 18 题
|
||||
|
||||
【单选题】在Gin框架中,如何使用JWT验证中间件?(5分)
|
||||
|
||||
A. 通过router组添加中间件进行验证
|
||||
|
||||
B. 使用静态文件服务
|
||||
|
||||
C. 直接在handler函数中编写JWT验证代码
|
||||
|
||||
D. 使用外部服务进行验证
|
||||
|
||||
**正确答案:** A
|
||||
|
||||
**作答答案:** A
|
||||
|
||||
> [!code Go] JWT 中间件实战模板
|
||||
>
|
||||
> ```go
|
||||
> func JWTAuth() gin.HandlerFunc {
|
||||
> return func(c *gin.Context) {
|
||||
> tokenStr := c.GetHeader("Authorization")
|
||||
> claims, err := parseJWT(tokenStr)
|
||||
> if err != nil {
|
||||
> c.AbortWithStatusJSON(401, gin.H{"error": "无效令牌"})
|
||||
> return
|
||||
> }
|
||||
> c.Set("userID", claims.UserID) // 存入上下文供后续使用
|
||||
> c.Next()
|
||||
> }
|
||||
> }
|
||||
>
|
||||
> // 使用:只对需要认证的路由组生效
|
||||
> admin := r.Group("/admin")
|
||||
> admin.Use(JWTAuth())
|
||||
> {
|
||||
> admin.GET("/users", listUsers)
|
||||
> }
|
||||
> ```
|
||||
|
||||
#### 第 20 题
|
||||
|
||||
【单选题】在Gin框架中,gin.Context 是出现频率最高的参数。它的核心作用是什么?(5分)
|
||||
|
||||
A. 仅仅用于连接数据库的上下文
|
||||
|
||||
B. 专门用来渲染HTML模板的对象
|
||||
|
||||
C. 封装了HTTP的请求(Request)和响应(ResponseWriter),并在中间件之间传递数据
|
||||
|
||||
D. 用于启动Gin的底层HTTP服务器
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
> [!note] gin.Context 一图看懂
|
||||
>
|
||||
> ```mermaid
|
||||
> graph TD
|
||||
> A["gin.Context"] --> B["Request 读取<br/>Query/Header/Body"]
|
||||
> A --> C["Response 写入<br/>JSON/String/File"]
|
||||
> A --> D["中间件通信<br/>c.Set / c.Get / c.Next"]
|
||||
> A --> E["生命周期管理<br/>Abort / Deadline"]
|
||||
> ```
|
||||
>
|
||||
> Context 贯穿整个请求生命周期——从接收到响应,它是最核心的桥梁。
|
||||
|
||||
#### 第 27 题
|
||||
|
||||
【单选题】后端处理完逻辑后,想给前端返回一个标准的JSON格式响应(包含状态码200),以下哪句代码是正确的?(5分)
|
||||
|
||||
A. c.String(200, "{\"status\": \"success\"}")
|
||||
|
||||
B. c.JSON(http.StatusOK, gin.H{"message": "success", "data": myData})
|
||||
|
||||
C. c.XML(200, myData)
|
||||
|
||||
D. c.HTML(http.StatusOK, "index.html", nil)
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!code Go] Gin 响应方法一览
|
||||
>
|
||||
> | 方法 | 适用场景 | Content-Type |
|
||||
> |------|---------|-------------|
|
||||
> | `c.JSON()` | JSON 响应 | application/json |
|
||||
> | `c.String()` | 纯文本 | text/plain |
|
||||
> | `c.XML()` | XML 响应 | application/xml |
|
||||
> | `c.Data()` | 自定义类型 | 可指定任意类型 |
|
||||
> | `c.File()` | 下载文件 | 自动推断 |
|
||||
|
||||
---
|
||||
|
||||
### 四、Cookie、Session 与 JWT 认证
|
||||
|
||||
> [!abstract] 本节覆盖:第 7、9、10、11、21 题
|
||||
>
|
||||
> 这三种方案解决了同一个问题——在**无状态**的 HTTP 上建立**有状态**的会话。各有优劣,适用于不同场景。
|
||||
|
||||
#### 第 7 题
|
||||
|
||||
【单选题】在使用 Gin 设置 Cookie 时,为了防止前端恶意脚本通过 JavaScript 读取到包含用户登录凭证的敏感 Cookie,你应该将哪个参数设置为什么值?(5分)
|
||||
|
||||
httpOnly 是防御 XSS 攻击的利器。当它设为 true 时,浏览器会把这个 Cookie 藏好,只有在向后端发送 HTTP 请求时才会默默带上,前端的任何 JS 代码都无权读取。顺便提一下,secure 也很重要,它表示该 Cookie 只能在 HTTPS 加密连接下传输,用来防止网络中间人窃听。
|
||||
|
||||
A. maxAge 设置为 0
|
||||
|
||||
B. secure 设置为 true
|
||||
|
||||
C. httpOnly 设置为 true
|
||||
|
||||
D. domain 设置为 "localhost"
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
> [!tip] Cookie 安全属性对照表
|
||||
>
|
||||
> | 属性 | 作用 | 推荐生产环境值 |
|
||||
> |------|------|---------------|
|
||||
> | `HttpOnly` | JS 无法读取,防 XSS | `true` |
|
||||
> | `Secure` | 仅 HTTPS 传输,防抓包 | `true` |
|
||||
> | `SameSite` | 防 CSRF,限制第三方携带 | `Strict` 或 `Lax` |
|
||||
> | `Max-Age` | 有效期(秒),0=删除 | 按需设置 |
|
||||
|
||||
#### 第 9 题
|
||||
|
||||
【单选题】关于 Gin 框架中 Session 的运用,以下说法正确的是?(5分)
|
||||
|
||||
Session 的本质是服务器在后端存储了用户状态,只把一个无意义的、随机的 SessionID 通过 Cookie 发给前端保存。Gin 核心库为了保持轻量,并没有内置 Session,推荐使用第三方中间件。
|
||||
|
||||
A. Gin 官方核心库自带了完善的 Session 管理器,无需引入第三方包。
|
||||
|
||||
B. Session 数据默认以明文形式保存在客户端的浏览器中。
|
||||
|
||||
C. Session 的本质是服务器在后端存储用户状态,只把 SessionID 发给前端。
|
||||
|
||||
D. 只要用户关闭浏览器页面,服务器端的 Session 数据就会被自动删除。
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
#### 第 10 题
|
||||
|
||||
【单选题】JWT 解决了传统 Session 架构的什么核心痛点?(5分)
|
||||
|
||||
JWT 是"无状态"的,服务端不需要存储会话记录就能验证其有效性,从而解决了传统 Session 在服务器集群环境下的共享与同步问题。
|
||||
|
||||
A. JWT 对用户密码进行了极强的加密,即便被黑客拦截也绝对安全。
|
||||
|
||||
B. 传统 Session 强依赖于服务端内存或集中存储,JWT 是"无状态"的。
|
||||
|
||||
C. JWT 生成的字符串非常短,比传统的 SessionID 更省网络带宽。
|
||||
|
||||
D. JWT 只能在 Go 语言和前端之间解析,跨语言安全性极高。
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
#### 第 11 题
|
||||
|
||||
【单选题】当中间件成功解析并验证了前端请求头中的 JWT 令牌后,最佳的代码实践是?(5分)
|
||||
|
||||
中间件是查票员,查验完 JWT 后,通过 c.Set() 把乘客信息存储在当前 HTTP 请求的生命周期上下文中,后续函数可以安全、隔离地拿到用户信息。
|
||||
|
||||
A. 将解析出的用户 ID 保存到全局变量中。
|
||||
|
||||
B. 使用 c.Redirect 将请求重定向,并在 URL 参数里带上用户 ID。
|
||||
|
||||
C. 使用 c.Set("userID", id) 将用户信息存储在上下文中。
|
||||
|
||||
D. 将用户信息打包成 JSON 写回到响应体中,结束请求。
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
#### 第 21 题
|
||||
|
||||
【单选题】当用户首次访问网站时,服务器通常会在HTTP响应中使用哪个字段来向客户端存储session ID?(5分)
|
||||
|
||||
A. Cache-Control
|
||||
|
||||
B. Set-Cookie
|
||||
|
||||
C. Content-Length
|
||||
|
||||
D. User-Agent
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!summary] 认证方案横向对比
|
||||
>
|
||||
> | 维度 | Session (Cookie) | JWT |
|
||||
> |------|-----------------|-----|
|
||||
> | 存储位置 | 服务端(内存/Redis) | 客户端(自行保管) |
|
||||
> | 服务端压力 | 需维护会话,集群需共享 | 无状态,天然分布式友好 |
|
||||
> | 主动注销 | 可从服务端删除 ✅ | 需黑名单/缩短有效期 ⚠️ |
|
||||
> | token 大小 | Cookie 仅传 SessionID(很小) | JWT 本身较长(含 payload) |
|
||||
> | 适用场景 | 后台管理系统、需要频繁踢人 | 移动端 API、微服务、SSO |
|
||||
|
||||
---
|
||||
|
||||
### 五、CORS 跨域
|
||||
|
||||
> [!abstract] 本节覆盖:第 22~26 题
|
||||
>
|
||||
> 跨域是现代全栈开发的"必修课"。理解同源策略、预检请求和各类解决方案,才能游刃有余地应对生产环境。
|
||||
|
||||
#### 第 22 题
|
||||
|
||||
【单选题】当你的项目正式上线:前端部署在 www.myweb.com,Go 后端 API 部署在 api.myweb.com,此时前端又碰到了跨域问题。生产环境下,业界最标准、最推荐的解决方式是什么?(5分)
|
||||
|
||||
A. 在 Nginx 配置中开启反向代理,让前端和后端走同一个域名,彻底消灭跨域。或者在 Go 后端通过代码严格配置 CORS 中间件,允许指定域名的访问。
|
||||
|
||||
B. 要求所有用户在浏览器中安装一个允许跨域的插件。
|
||||
|
||||
C. 把 Vue/React 前端代码编译进 Go 语言的二进制文件里,打包成一个单体服务,从而避免跨域。
|
||||
|
||||
D. 在前端代码中使用 JSONP 的方式来绕过浏览器的同源策略。
|
||||
|
||||
**正确答案:** A
|
||||
|
||||
**作答答案:** A
|
||||
|
||||
#### 第 23 题
|
||||
|
||||
【单选题】当我们的全栈项目要部署到生产环境时,通常会在最外层挡一个 Nginx,并将外部请求"反向代理"给内部的 Go 应用程序。为什么要多此一举,而不是直接让 Go 监听 80/443 端口对外提供服务?(5分)
|
||||
|
||||
A. 因为 Go 语言标准库的 net/http 性能太差,无法承受高并发,必须靠 Nginx 提速。
|
||||
|
||||
B. 因为 Go 程序无法处理 HTTPS 证书的加密解密。
|
||||
|
||||
C. Nginx 专精于静态文件的高效分发,并且能够作为统一入口隐藏后端真实 IP,还能轻松实现请求路由分发、负载均衡和限流,极大提升了整体架构的健壮性和安全性。
|
||||
|
||||
D. 这是操作系统的强制要求,所有的 Web 流量必须先经过 Nginx 才能到达用户态程序。
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
#### 第 24 题
|
||||
|
||||
【单选题】当你在前端用 axios 发送一个携带了自定义请求头的 POST 请求时,浏览器实际上发了两次请求。这个 OPTIONS 请求是做什么的?(5分)
|
||||
|
||||
A. 它是用来试探后端服务器是否死机的心跳检测。
|
||||
|
||||
B. 这是浏览器的 CORS"预检请求 (Preflight)"。因为带有自定义头或非普通类型的请求属于"复杂请求",浏览器必须先发个 OPTIONS 问问后端:"我带了特殊的头,你可以接受吗?"
|
||||
|
||||
C. 这是 axios 库本身的 bug 导致的重复请求。
|
||||
|
||||
D. 它是用来提前把数据进行加密的握手请求。
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
#### 第 25 题
|
||||
|
||||
【单选题】在前端开发阶段,我们通常会在 vite.config.js 或 vue.config.js 中配置 proxy 代理来解决跨域问题。这个 Proxy 底层的运作逻辑是什么?(5分)
|
||||
|
||||
A. 它修改了浏览器的安全设置,让浏览器临时关闭了同源策略。
|
||||
|
||||
B. 它修改了前端发出的 HTTP 请求的 Origin,伪装成和后端同源。
|
||||
|
||||
C. 前端代码实际上把请求发给了 Vite 自带的本地 Node.js 开发服务器,开发服务器再把请求转发给真正的 Go 后端,最后把结果原样返回给前端。
|
||||
|
||||
D. 它自动为后端的 Go 代码注入了处理跨域的中间件。
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
#### 第 26 题
|
||||
|
||||
【单选题】前后端分离开发时,前端调用后端经常会遇到可怕的 CORS 报错。导致跨域报错的根本原因是什么?它为了解决什么痛点而存在?(5分)
|
||||
|
||||
A. 因为 HTTP 协议不允许不同端口之间通信,这是底层网络协议的限制。
|
||||
|
||||
B. 因为后端的 Go 代码默认开启了防火墙,拦截了来自其他端口的请求。
|
||||
|
||||
C. 这是浏览器的"同源策略 (Same-Origin Policy)"导致的,为了防止恶意网站窃取用户的敏感数据(如 Cookie),浏览器强制拦截了跨域的响应。
|
||||
|
||||
D. 因为前端框架(如 Vue/React)的安全机制,阻止了发起非同源的请求。
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
> [!tip] 跨域三大解决方案全景图
|
||||
>
|
||||
> ```mermaid
|
||||
> graph LR
|
||||
> A["跨域问题"] --> B["开发环境"]
|
||||
> A --> C["生产环境"]
|
||||
> B --> B1["Vite Proxy<br/>Node 中转"]
|
||||
> C --> C1["Nginx 反向代理<br/>同域名免跨域"]
|
||||
> C --> C2["CORS 中间件<br/>服务端放白名单"]
|
||||
> ```
|
||||
>
|
||||
> **Go 后端 CORS 中间件示例:**
|
||||
>
|
||||
> ```go
|
||||
> import "github.com/gin-contrib/cors"
|
||||
>
|
||||
> r := gin.New()
|
||||
> r.Use(cors.New(cors.Config{
|
||||
> AllowOrigins: []string{"https://www.myweb.com"},
|
||||
> AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
> AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||||
> AllowCredentials: true,
|
||||
> MaxAge: 12 * time.Hour,
|
||||
> }))
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
### 六、响应与状态码
|
||||
|
||||
> [!abstract] 本节覆盖:第 28、29 题(续)
|
||||
>
|
||||
> 正确使用 HTTP 状态码是专业后端的基本素养。
|
||||
|
||||
#### 第 28 题
|
||||
|
||||
【单选题】前端向你的Go后端发送了一个请求,但忘了带上认证Token。作为标准化的后端,你应该返回哪个状态码来明确告知他"你没有权限/未认证"?(5分)
|
||||
|
||||
A. 400 Bad Request
|
||||
|
||||
B. 401 Unauthorized
|
||||
|
||||
C. 404 Not Found
|
||||
|
||||
D. 500 Internal Server Error
|
||||
|
||||
**正确答案:** B
|
||||
|
||||
**作答答案:** B
|
||||
|
||||
> [!note] 常用 HTTP 状态码速查
|
||||
>
|
||||
> | 状态码 | 含义 | 何时使用 |
|
||||
> |--------|------|---------|
|
||||
> | **200** | OK | 请求成功 |
|
||||
> | **201** | Created | 资源创建成功 |
|
||||
> | **301** | Moved Permanently | 永久重定向 |
|
||||
> | **304** | Not Modified | 资源未变化(缓存) |
|
||||
> | **400** | Bad Request | 请求参数错误 |
|
||||
> | **401** | Unauthorized | 未认证/Token失效 |
|
||||
> | **403** | Forbidden | 已认证但无权限 |
|
||||
> | **404** | Not Found | 资源不存在 |
|
||||
> | **429** | Too Many Requests | 触发限流 |
|
||||
> | **500** | Internal Server Error | 服务端内部错误 |
|
||||
|
||||
---
|
||||
|
||||
### 七、Git 版本控制
|
||||
|
||||
#### 第 30 题
|
||||
|
||||
【单选题】在Git中,如何临时保存当前工作进度以便切换到其他分支进行工作?(5分)
|
||||
|
||||
A. 使用 git stash apply 命令
|
||||
|
||||
B. 使用 git commit 命令
|
||||
|
||||
C. 使用 git stash 命令
|
||||
|
||||
D. 使用 git merge 命令
|
||||
|
||||
**正确答案:** C
|
||||
|
||||
**作答答案:** C
|
||||
|
||||
> [!code Bash] git stash 常用命令
|
||||
>
|
||||
> ```bash
|
||||
> git stash save "WIP: 修复bug" # 保存当前改动
|
||||
> git stash list # 查看保存列表
|
||||
> git stash pop stash@{0} # 恢复并删除栈顶记录
|
||||
> git stash apply stash@{0} # 恢复但不删除(保留记录)
|
||||
> git stash drop stash@{0} # 删除指定记录
|
||||
> git stash drop # 删除最近一条(快捷写法)
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/EXAM/]]
|
||||
- [[Go/Gin框架/中间件]]
|
||||
- [[Web/CORS跨域]]
|
||||
- [[Web/JWT认证]]
|
||||
@@ -0,0 +1,804 @@
|
||||
---
|
||||
tags: []
|
||||
create time: 2026-05-14 10:30
|
||||
---
|
||||
|
||||
# 金山考试 - 武科大 - 第四次课(React 核心)
|
||||
|
||||
## 概述
|
||||
|
||||
本次考试围绕 **React 核心概念与性能优化**,涵盖以下知识点:
|
||||
|
||||
- useState / useEffect / useMemo / useCallback 等 Hooks 的使用与陷阱
|
||||
- React.memo 与组件渲染行为控制
|
||||
- 单向数据流与组件通信模式(父子/兄弟/跨级)
|
||||
- Context API 的作用域与注意事项
|
||||
- React Router 路由机制( BrowserRouter、Route、动态参数)
|
||||
- Redux 状态管理(Action、Reducer、中间件)
|
||||
- Axios 拦截器封装最佳实践
|
||||
- 大列表渲染与 Vite 打包性能优化
|
||||
- 前端监控埋点方案对比(GIF vs AJAX)
|
||||
|
||||
## 我的错题
|
||||
|
||||
> [!FAIL] ❌ 错题记录
|
||||
> 以下题目作答错误,标注解题思路与关键误区。
|
||||
|
||||
### Q14 — 兄弟组件通信
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 官方答案 | C(Context) |
|
||||
| 你的答案 | D(Redux) |
|
||||
|
||||
> [!NOTE] 💡 为什么选 Context?
|
||||
> Redux 属于**全局状态管理库**,适用于跨多个不相关组件共享复杂状态的**大型项目**。而本题限定为「**兄弟组件之间的简单通信**」,使用 Context 更轻量、更符合场景定位——通过一个公共父组件创建 Context,两个兄弟组件分别消费即可,无需引入额外依赖。
|
||||
|
||||
### Q29 — React.memo + 对象 prop
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 官方答案 | D(父组件每次创建新对象引用) |
|
||||
| 你的答案 | (正确 ✅) |
|
||||
|
||||
> [!NOTE] ℹ️ 标记已纠正
|
||||
> 此题已掌握,保留备忘。React.memo 基于**浅比较**(shallow equal),只要 prop 引用地址变了就会重新渲染。**每次渲染都 new 一个对象**会导致引用始终不同,`useMemo` 可以缓存引用解决这个问题。
|
||||
|
||||
---
|
||||
|
||||
## 武科大 - 第四次课
|
||||
|
||||
### 一、单选题
|
||||
|
||||
#### 1. useState 闭包陷阱
|
||||
|
||||
【单选题】点击按钮后,以下 React 代码,console 的输出和按钮中的展示分别是什么?(2分)
|
||||
|
||||
```js
|
||||
function Counter() {
|
||||
const [count, setCount] useState(0);
|
||||
|
||||
const handleClick = () => {
|
||||
setCount(count + 1);
|
||||
setCount(count + 1);
|
||||
setCount(count + 1);
|
||||
console.log(count);
|
||||
};
|
||||
|
||||
return <button onClick={handleClick}>{count}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
A. 3, 3
|
||||
B. 0, 1
|
||||
C. 0, 3
|
||||
D. 3, 1
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** C
|
||||
|
||||
> [!TIP] 🔑 核心考点
|
||||
> `setCount(count + 1)` 三次调用中,`count` 是**同一闭包快照值 0**,所以实际等价于 `setCount(1)` 执行三次。但 React 会**批量合并**同一次事件中的状态更新,最终只渲染一次 → count = 1。
|
||||
>
|
||||
> ⚠️ 若用函数式更新 `setCount(prev => prev + 1)` 三次,则结果为 3。
|
||||
|
||||
---
|
||||
|
||||
#### 2. React.memo + 对象 prop
|
||||
|
||||
【单选题】考虑一个使用 `React.memo` 包装的 `Child` 组件。如果父组件传递一个对象 `prop`,以下哪种方法可以确保 `Child` 组件在父组件重新渲染时不会不必要地重新渲染?
|
||||
|
||||
A. 将 `Child` 组件从 `React.memo` 中移除
|
||||
B. 在 `Child` 组件内部使用 `useEffect` 来比较 `prop`
|
||||
C. 将对象 `prop` 传递给 `useMemo`,并将其作为 `Child` 的 `prop`
|
||||
D. 将对象 `prop` 的所有属性分别作为独立的 `prop` 传递给 `Child`
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** C ✅
|
||||
|
||||
---
|
||||
|
||||
#### 3. 父组件状态更新 → 子组件渲染
|
||||
|
||||
【单选题】在 React 中,当父组件状态更新时子组件的渲染行为是怎样的?(2分)
|
||||
|
||||
A. 子组件不会重新渲染,除非子组件自身的状态或属性变化
|
||||
B. 子组件会随父组件的重新渲染而无条件重新渲染
|
||||
C. 子组件仅在其接收的 props 发生变化时重新渲染
|
||||
D. 父组件的渲染不影响子组件
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** B ✅
|
||||
|
||||
> [!NOTE] ℹ️ 深入理解
|
||||
> 父组件 re-render 会**默认触发所有子组件** re-render,无论 props 是否改变。如需跳过,可使用 `React.memo`、`useMemo`、`useCallback` 等手段进行优化。
|
||||
|
||||
---
|
||||
|
||||
#### 4. useMemo vs useCallback
|
||||
|
||||
【单选题】React 中 useMemo 和 useCallback 的主要区别是什么?(2分)
|
||||
|
||||
A. useMemo 返回一个 memoized 值,useCallback 返回一个 memoized 函数
|
||||
B. useMemo 和 useCallback 效果完全一样
|
||||
C. useCallback 可以依赖多个变量,而 useMemo 只能依赖一个变量
|
||||
D. 没有区别,两者在 React 中可以互换使用
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 5. useCallback 的主要用途
|
||||
|
||||
【单选题】在 React 中 useCallback 的主要用途是什么?(2分)
|
||||
|
||||
A. 用于在组件之间传递状态
|
||||
B. 用于避免组件中的函数在每次渲染时都重新创建
|
||||
C. 用于直接修改组件的 state
|
||||
D. 用于数据的结构化管理
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** B ✅
|
||||
|
||||
---
|
||||
|
||||
#### 6. React 性能优化手段
|
||||
|
||||
【单选题】下面哪些是 React 性能优化的手段?(2分)
|
||||
|
||||
```js
|
||||
// 方案1 — useMemo 缓存计算结果
|
||||
const memoizedValue = useMemo(() => computeValue(a), [a]);
|
||||
|
||||
// 方案2 — useCallback 缓存函数引用
|
||||
const handleClick = () => { /*...*/ };
|
||||
const memoizedHandle = useCallback(handleClick, []);
|
||||
```
|
||||
|
||||
A. 仅方案1
|
||||
B. 仅方案2
|
||||
C. 两者都需要
|
||||
D. 都不需要
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** C ✅
|
||||
|
||||
> [!TIP] 🔑 何时使用?
|
||||
> - `useMemo`:适合**昂贵计算**的结果缓存
|
||||
> - `useCallback`:适合**传给子组件作为 prop 的函数**,配合 `React.memo` 防止子组件无谓重渲染
|
||||
|
||||
---
|
||||
|
||||
#### 7. 单项数据流
|
||||
|
||||
【单选题】在 React 中,以下关于单项数据流的描述,哪个是正确的?(3分)
|
||||
|
||||
A. 数据可以在组件间随意双向流动
|
||||
B. 数据只能从父组件流向子组件,子组件不能直接修改父组件的数据
|
||||
C. 数据从子组件流向父组件,父组件接收数据后再分发给其他子组件
|
||||
D. 数据在同级组件间可以自由流动
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** B ✅
|
||||
|
||||
---
|
||||
|
||||
#### 8. React Router 核心原理
|
||||
|
||||
【单选题】React Router 的核心原理是基于以下哪种机制来实现路由匹配和渲染的?(2分)
|
||||
|
||||
A. 事件监听
|
||||
B. 虚拟 DOM 比对
|
||||
C. URL 变化监听和组件映射
|
||||
D. 状态管理
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** C ✅
|
||||
|
||||
---
|
||||
|
||||
#### 9. 路由根组件
|
||||
|
||||
【单选题】在 React 中使用路由时,用于包裹整个应用路由组件的根组件是?(3分)
|
||||
|
||||
A. Route
|
||||
B. BrowserRouter
|
||||
C. Switch
|
||||
D. Link
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** B ✅
|
||||
|
||||
> [!NOTE] ℹ️ 注意版本差异
|
||||
> React Router v5 用 `BrowserRouter` + `Switch`;v6 改用 `BrowserRouter` + `<Routes>` + `<Route>`。Switch 在 v6 已被废弃。
|
||||
|
||||
---
|
||||
|
||||
#### 10. React Context 错误说法
|
||||
|
||||
【单选题】关于 React Context 的说法,错误的是?(3分)
|
||||
|
||||
A. 适合跨层级传递 props(避免 props drilling)
|
||||
B. 每次 Context 值变化时,所有消费组件都会重渲染
|
||||
C. 可以通过 useContext Hook 或 Consumer 组件使用
|
||||
D. 不能与类组件一起使用
|
||||
|
||||
**正确答案:** D
|
||||
**作答答案:** D ✅
|
||||
|
||||
> [!TIP] 🔑 Context 常见误区
|
||||
> Context 本身**不优化性能**——值变化时所有 consumer 都会重渲染。应结合 `useMemo` 分割 context 或将消费逻辑抽取到自定义 Hook 中以缩小更新范围。
|
||||
|
||||
---
|
||||
|
||||
#### 11. Fragment 主要作用
|
||||
|
||||
【单选题】在 React 中,Fragment 的主要作用是?(3分)
|
||||
|
||||
A. 为组件添加额外的 DOM 节点
|
||||
B. 替代 div 减少冗余 DOM 层级
|
||||
C. 优化组件的渲染性能
|
||||
D. 包裹多个子元素且不添加额外节点
|
||||
|
||||
**正确答案:** D
|
||||
**作答答案:** D ✅
|
||||
|
||||
> [!NOTE] ℹ️ 两种写法
|
||||
> - 完整语法:`<React.Fragment>` 或 `<Fragment>`
|
||||
> - 简写语法:`<>...</>` (不支持 key 属性)
|
||||
|
||||
---
|
||||
|
||||
#### 12. 按需加载
|
||||
|
||||
【单选题】React Router 中如何实现按需加载?(3分)
|
||||
|
||||
A. 使用 React.lazy 和 React.Suspense 实现
|
||||
B. 使用 webpack 的 Code Splitting 实现
|
||||
C. 使用 React Router 自带的按需加载功能实现
|
||||
D. 使用 ES6 的 import() 函数实现
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 13. 父向子传数据
|
||||
|
||||
【单选题】在 React 中,如何实现父组件向子组件传递数据?(3分)
|
||||
|
||||
A. 使用 state 属性
|
||||
B. 使用 props 属性
|
||||
C. 使用 context 属性
|
||||
D. 使用 ref 属性
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** B ✅
|
||||
|
||||
---
|
||||
|
||||
#### 14. 兄弟组件通信
|
||||
|
||||
【单选题】在 React 中,实现兄弟组件之间的通信,相对来说下面哪个比较合适?(3分)
|
||||
|
||||
A. 使用 props
|
||||
B. 使用 state
|
||||
C. 使用 context
|
||||
D. 使用 Redux
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** D
|
||||
|
||||
---
|
||||
|
||||
#### 15. 子传父
|
||||
|
||||
【单选题】子传父在 React 中通常通过什么实现?(3分)
|
||||
|
||||
A. 回调函数
|
||||
B. 事件
|
||||
C. 状态提升
|
||||
D. 上升至共同的父组件
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 16. React.createElement
|
||||
|
||||
【单选题】React.createElement 用于做什么?(3分)
|
||||
|
||||
A. 创建类组件实例
|
||||
B. 创建函数组件实例
|
||||
C. 创建 DOM 元素
|
||||
D. 创建 React 元素
|
||||
|
||||
**正确答案:** D
|
||||
**作答答案:** D ✅
|
||||
|
||||
---
|
||||
|
||||
#### 17. 获取 DOM 引用
|
||||
|
||||
【单选题】如果需要在 React 组件中保持对 DOM 元素的引用,应该使用哪个钩子?(3分)
|
||||
|
||||
A. useState
|
||||
B. useEffect
|
||||
C. useRef
|
||||
D. useReducer
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** C ✅
|
||||
|
||||
---
|
||||
|
||||
#### 18. useState 返回值
|
||||
|
||||
【单选题】在 React 中,useState 钩子的返回值包含什么?(3分)
|
||||
|
||||
A. 一个包含状态和更新状态的函数
|
||||
B. 一个包含副作用和清理副作用的函数
|
||||
C. 一个包含 DOM 引用和更新 DOM 的方法
|
||||
D. 一个包含生命周期方法的对象
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
> [!NOTE] ℹ️ 解构方式
|
||||
> ```js
|
||||
> const [count, setCount] = useState(0);
|
||||
> // ↑ 更新函数(非 setter)
|
||||
> // ↑ 当前状态值
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
#### 19. useEffect 清除副作用
|
||||
|
||||
【单选题】在 React 中,如何清除 useEffect 钩子中设置的副作用?(3分)
|
||||
|
||||
A. 返回一个函数
|
||||
B. 使用 useRef
|
||||
C. 使用 useState
|
||||
D. 使用 useMemo
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 20. useEffect 主要用途
|
||||
|
||||
【单选题】在 React 中,useEffect 钩子的主要用途是什么?(3分)
|
||||
|
||||
A. 声明性地处理组件的副作用
|
||||
B. 声明性地处理组件的样式
|
||||
C. 声明性地处理组件的事件
|
||||
D. 声明性地处理组件的渲染
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 21. 动态参数写法
|
||||
|
||||
【单选题】在路由路径中定义动态参数(如用户 ID)的正确写法是?(3分)
|
||||
|
||||
A. `/user?id=123`
|
||||
B. `/user/:id`
|
||||
C. `/user/{id}`
|
||||
D. `/user[id]`
|
||||
|
||||
**正确答案:** B
|
||||
**作答答案:** B ✅
|
||||
|
||||
---
|
||||
|
||||
#### 22. 路由匹配组件
|
||||
|
||||
【单选题】以下哪个组件用于定义路由匹配规则?(3分)
|
||||
|
||||
A. Link
|
||||
B. NavLink
|
||||
C. Route
|
||||
D. Switch
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** C ✅
|
||||
|
||||
---
|
||||
|
||||
#### 23. 全局状态管理适用场景
|
||||
|
||||
【单选题】以下哪种场景最适合使用全局状态管理库(如 Redux)?(3分)
|
||||
|
||||
A. 多个不相关组件共享复杂状态
|
||||
B. 单一组件内部状态管理
|
||||
C. 兄弟组件简单数据传递
|
||||
D. 跨页面数据持久化
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
> [!TIP] 🔑 选型决策树
|
||||
>
|
||||
> ```mermaid
|
||||
> graph LR
|
||||
> A[需要共享状态?] -->|否| B[用 useState/useRef]
|
||||
> A -->|是| C{是否在同一个组件树中?}
|
||||
> C -->|是| D[父→子 props / 状态提升]
|
||||
> C -->|否| E{涉及组件数量?}
|
||||
> E -->|少且相邻| F[Context]
|
||||
> E -->|多且不相关| G[Redux/Zustand]
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
#### 24. Redux reducer
|
||||
|
||||
【单选题】在 React-Redux 中,什么是 reducer?(3分)
|
||||
|
||||
以下哪个描述最准确地解释了 React-Redux 中的 reducer?
|
||||
|
||||
A. reducer 是一个纯函数,它接收一个先前的状态和一个 action,返回一个新的状态
|
||||
B. reducer 是一个对象,它定义了应用程序的状态和行为
|
||||
C. reducer 是一个数组,它包含了应用程序的所有状态
|
||||
D. reducer 是一个类,它封装了应用程序的所有逻辑
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 25. Redux action
|
||||
|
||||
【单选题】在 React-Redux 中,什么是 action?(3分)
|
||||
|
||||
以下哪个描述最准确地解释了 React-Redux 中的 action?
|
||||
|
||||
A. action 是一个对象,它描述了发生了什么事件,它包含了 type 和 payload 属性
|
||||
B. action 是一个函数,它定义了应用程序的行为
|
||||
C. action 是一个数组,它包含了应用程序的所有状态
|
||||
D. action 是一个类,它封装了应用程序的所有逻辑
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
---
|
||||
|
||||
#### 26. Action 与 Reducer 关系
|
||||
|
||||
【单选题】Redux 中的 action 和 reducer 有什么关系?(3分)
|
||||
|
||||
在 Redux 中,action 和 reducer 是组成状态管理的两个重要概念。请问,action 和 reducer 之间的关系是什么?
|
||||
|
||||
A. action 用于描述应用状态的结构,reducer 用于修改状态
|
||||
B. action 用于存储应用状态的数据,reducer 用于获取状态
|
||||
C. action 用于触发状态修改,reducer 用于响应状态修改
|
||||
D. action 用于监听状态变化,reducer 用于修改状态
|
||||
|
||||
**正确答案:** C
|
||||
**作答答案:** C ✅
|
||||
|
||||
> [!NOTE] ℹ️ Redux 数据流全景
|
||||
>
|
||||
> ```mermaid
|
||||
> flowchart LR
|
||||
> UI["组件发起 dispatch"] -->|Action| STORE["Store"]
|
||||
> STORE -->|派发| REDUCER["Reducer 纯函数"]
|
||||
> REDUCER -->|new State| STORE
|
||||
> STORE -->|订阅更新| UI
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
#### 27. Redux 异步操作
|
||||
|
||||
【单选题】在 Redux 中,有时需要进行异步操作,比如发送网络请求。请问,以下哪个中间件可以用来实现异步操作?(3分)
|
||||
|
||||
A. redux-thunk
|
||||
B. redux-saga
|
||||
C. redux-logger
|
||||
D. redux-observable
|
||||
|
||||
**正确答案:** A
|
||||
**作答答案:** A ✅
|
||||
|
||||
> [!TIP] 🔑 常用中间件对比
|
||||
>
|
||||
> | 中间件 | 特点 |
|
||||
> |--------|------|
|
||||
> | redux-thunk | 轻量,返回函数的 action creator,适合简单异步 |
|
||||
> | redux-saga | 基于 Generator,适合复杂异步流程控制 |
|
||||
> | redux-logger | 日志打印,不参与业务逻辑 |
|
||||
> | redux-observable | 基于 RxJS,响应式编程风格 |
|
||||
|
||||
---
|
||||
|
||||
#### 28. Axios 拦截器问题
|
||||
|
||||
【单选题】axios 封装请求拦截器时,以下代码有什么问题?(3分)
|
||||
|
||||
```js
|
||||
import axios from 'axios';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://api.example.com',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
instance.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
if (error.response?.status === 401) {
|
||||
// token过期,跳转登录
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
```
|
||||
|
||||
A. 没有问题,这是标准的 axios 封装
|
||||
B. token 过期时应该先尝试刷新 token
|
||||
C. 直接操作 window.location 会导致 SPA 路由失效
|
||||
D. B 和 C 都对
|
||||
|
||||
**正确答案:** D
|
||||
**作答答案:** D ✅
|
||||
|
||||
> [!TIP] 🔑 最佳实践
|
||||
> - **刷新 Token**:使用 refresh_token 流程重试原请求,而非直接跳登录
|
||||
> - **SPA 导航**:使用 `router.push('/login')` 而非 `window.location.href`,避免整页刷新丢失状态
|
||||
|
||||
---
|
||||
|
||||
#### 29. React.memo + 新对象引用
|
||||
|
||||
【单选题】在使用 `React.memo` 时,如果父组件传递一个对象作为 `prop`,以下哪种情况可能导致子组件仍然不必要的重新渲染?(3分)
|
||||
|
||||
A. 父组件传递的是一个原始类型的值,例如数字
|
||||
B. 父组件传递的是一个使用 `useMemo` 缓存的对象
|
||||
C. 子组件内部的状态发生了变化
|
||||
D. 父组件每次渲染时都创建了一个新的对象
|
||||
|
||||
**正确答案:** D
|
||||
**作答答案:** D ✅
|
||||
|
||||
---
|
||||
|
||||
### 二、多选题
|
||||
|
||||
#### 1. 大数据渲染优化
|
||||
|
||||
【多选题】项目中遇到大量数据渲染导致页面卡顿,以下哪些优化方案有效?(4分)
|
||||
|
||||
```js
|
||||
// 方案A:虚拟滚动
|
||||
<virtual-list
|
||||
:data="list"
|
||||
:item-height="80"
|
||||
:buffer-scale="2"
|
||||
/>
|
||||
|
||||
// 方案B:分页加载
|
||||
const pageSize = 20;
|
||||
const currentPage = ref(1);
|
||||
const displayList = computed(() => {
|
||||
return list.slice(0, currentPage.value * pageSize);
|
||||
});
|
||||
|
||||
// 方案C:时间分片
|
||||
function renderWithTimeSlice(list) {
|
||||
let current = 0;
|
||||
const pageSize = 50;
|
||||
function render() {
|
||||
const fragment = document.createDocumentFragment();
|
||||
const end = Math.min(current + pageSize, list.length);
|
||||
for (let i = current; i < end; i++) {
|
||||
const item = createListItem(list[i]);
|
||||
fragment.appendChild(item);
|
||||
}
|
||||
container.appendChild(fragment);
|
||||
current = end;
|
||||
if (current < list.length) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
// 方案D:Web Worker 处理数据
|
||||
const worker = new Worker('process-data.js');
|
||||
worker.postMessage(rawData);
|
||||
worker.onmessage = (e) => {
|
||||
renderList(e.data);
|
||||
};
|
||||
```
|
||||
|
||||
A. 只渲染可视区域
|
||||
B. 按需加载
|
||||
C. 避免长时间阻塞
|
||||
D. 数据处理放在 Worker
|
||||
|
||||
**正确答案:** ABCD
|
||||
**作答答案:** ABCD ✅
|
||||
|
||||
> [!NOTE] ℹ️ 各方案定位
|
||||
> - **虚拟滚动**:DOM 节点数固定,滚动时复用节点
|
||||
> - **分页加载**:减少一次性渲染量,配合无限滚动体验更佳
|
||||
> - **时间分片**:将任务拆入微任务队列,避免主线程长时间占用
|
||||
> - **Web Worker**:计算密集型任务放后台线程,主线程只管渲染
|
||||
|
||||
---
|
||||
|
||||
#### 2. Vite 打包优化
|
||||
|
||||
【多选题】Vite 项目打包优化时,以下哪些配置是有效的?(4分)
|
||||
|
||||
```js
|
||||
// vite.config.js
|
||||
export default {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 配置A:手动分包
|
||||
manualChunks: {
|
||||
'vendor': ['vue', 'vue-router', 'pinia'],
|
||||
'ui': ['element-plus']
|
||||
},
|
||||
// 配置B:第三方库按包名分组
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
return id.toString().split('node_modules/')[1].split('/')[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 配置C:启用压缩
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true
|
||||
}
|
||||
},
|
||||
// 配置D:调整 chunk 大小警告阈值
|
||||
chunkSizeWarningLimit: 2000
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
A. 配置 A 可以有效减少首屏加载时间
|
||||
B. 配置 B 比 A 更灵活,能自动分离第三方库
|
||||
C. 配置 C 能减小包体积,但会增加构建时间
|
||||
D. 配置 D 只是隐藏警告,不能真正优化性能
|
||||
|
||||
**正确答案:** ABCD
|
||||
**作答答案:** ABC
|
||||
|
||||
> [!FAIL] 💔 漏选分析
|
||||
> **配置 D 虽不直接优化性能,但它影响开发体验**——合理设置可避免频繁干扰,让真正的警告突出显示。这是一项工程化最佳实践。建议全选。
|
||||
|
||||
---
|
||||
|
||||
#### 3. 大列表渲染最佳实践
|
||||
|
||||
【多选题】以下性能优化的最佳实践是什么?(4分)
|
||||
|
||||
```js
|
||||
// 场景:大列表渲染
|
||||
const List = ({ items }) => {
|
||||
return (
|
||||
<div>
|
||||
{items.map((item, index) => (
|
||||
<div key={index} onClick={() => handleClick(item)}>
|
||||
{item.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
A. 使用 item.id 作为 key 而非 index
|
||||
B. 使用虚拟滚动
|
||||
C. 使用 useMemo 包裹 items
|
||||
D. onClick 使用 useCallback
|
||||
|
||||
**正确答案:** ABD
|
||||
**作答答案:** ABD ✅
|
||||
|
||||
> [!TIP] 🔑 为什么不选 C?
|
||||
> `useMemo` 包裹 items 并不能直接优化列表渲染性能——items 来自 props,其变化由父组件决定。关键在于 **key 稳定性**(A)、**渲染策略**(B)和**事件处理器引用稳定**(D)。
|
||||
|
||||
---
|
||||
|
||||
#### 4. useEffect + 键盘事件监听
|
||||
|
||||
【多选题】关于下面代码说法正确的是?(4分)
|
||||
|
||||
```js
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e) => {
|
||||
const userAgent = navigator.userAgent;
|
||||
const isMac = /Macintosh|MacIntel|MacPPC|Mac68K/.test(userAgent);
|
||||
if ((isMac && e.metaKey && e.key.toLowerCase() === 'k') ||
|
||||
(!isMac && e.ctrlKey && e.key.toLowerCase() === 'k')) {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, []);
|
||||
```
|
||||
|
||||
A. 在 Mac 设备上按下 Command+K 键,或在非 Mac 设备上按下 Ctrl+K 键时,让 inputRef 对应的输入框获取焦点
|
||||
B. 该 useEffect 仅在组件挂载时执行一次,因为其依赖数组为空
|
||||
C. 清理函数会在组件卸载时执行,用于移除添加的 keydown 事件监听,避免内存泄漏
|
||||
D. 清理函数的主要作用是阻止键盘事件的默认行为
|
||||
|
||||
**正确答案:** ABC
|
||||
**作答答案:** ABCD
|
||||
|
||||
> [!FAIL] 💔 易错点
|
||||
> **D 是错误的**:`e.preventDefault()` 阻止的是浏览器默认行为(如 Cmd+K 的搜索框聚焦),而清理函数 `removeEventListener` 的作用是**移除事件监听器本身**,二者职责完全不同。
|
||||
|
||||
---
|
||||
|
||||
#### 5. GIF 埋点 vs AJAX
|
||||
|
||||
【多选题】相比较 AJAX,有些公司前端监控都喜欢用 GIF 做埋点上报,以下哪些是 GIF 的优势?(4分)
|
||||
|
||||
```js
|
||||
// GIF 方案
|
||||
const report = (data) => {
|
||||
const img = new Image();
|
||||
img.src = `https://log.example.com/track.gif?data=${encodeURIComponent(JSON.stringify(data))}`;
|
||||
};
|
||||
|
||||
// Ajax 方案
|
||||
const report = (data) => {
|
||||
fetch('https://log.example.com/track', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
A. GIF 请求不需要等待响应,发送即走
|
||||
B. GIF 可以跨域,不受 CORS 限制
|
||||
C. 页面卸载时 GIF 请求不会被取消
|
||||
D. GIF 体积最小(1x1 像素)
|
||||
|
||||
**正确答案:** ABD
|
||||
**作答答案:** ABD ✅
|
||||
|
||||
> [!NOTE] ℹ️ 为什么不用 C?
|
||||
> 页面卸载(unload)时,`navigator.sendBeacon()` 可以保证请求发送,但普通 `<img>` 请求**同样会被取消**,因为浏览器不再维护连接。GIF 的优势在于**零 CORS 限制**(资源加载不走 XHR 同源策略)和**极简体积**。
|
||||
|
||||
---
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/EXAM/Week03]](推测:前三次课相关考题)
|
||||
- [[hhs/EXAM/README]](考试文档索引)
|
||||
+1228
File diff suppressed because it is too large
Load Diff
+1313
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user