Tiny'Wo | 小窝

网络中的一小块自留地

JS 基础知识

JS 是前端开发的核心能力,面试重点考察,无论工作经验长短�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

了解哪些最新的 ES 新特性?

参考答�?
::: details

特�?1: ES2024 �?JSON 模块

支持直接通过 import 语法加载 JSON 文件,避免额外的文件读取逻辑�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import config from './config.json' assert { type: 'json' }

console.log(config.setting) // 输出 JSON 文件中的指定属�?```

**特�?2: ES2023 �?Array.prototype.findLast & Array.prototype.findLastIndex**

两个数组新方法,用于从最后一个元素搜索数组元素。它们的功能�?`find() �?findIndex()` 类似,但搜索从数组末尾开始�?
这些方法可在 `Array �?TypedArray` 原型上使用。此功能通过消除手动数组反转的过程,为逆序搜索提供了一种有效的方法�?
```js
const isOdd = (number) => number % 2 === 1
const numbers = [1, 2, 3, 4, 5]

console.log(numbers.findLast(isOdd)) // 5
console.log(numbers.findLastIndex(isOdd)) // 4

*特�?3: ES2022 的类字段与私有方�?

支持类中的私有字�?�?field) 和私有方法,增强了封装性�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Counter {
#count = 0

increment() {
this.#count++
}

#logCount() {
console.log(this.#count)
}
}

const counter = new Counter()
counter.increment()
// counter.#logCount(); // 报错,私有方法不可访�?```

**特�?4: ES2021 的逻辑赋值运算符**

新增 `&&=, ||=, ??=`,简化条件赋值逻辑�?
```js
let user = { name: 'Alice', age: null }

user.name ||= 'Default Name' // 如果 name �?falsy,则赋�?user.age ??= 18 // 如果 age �?null �?undefined,则赋�?
console.log(user) // { name: 'Alice', age: 18 }

特�?5: ES2020 的可选链和空值合并操作符

简化深层嵌套对象属性的访问,并安全处理空值�?

1
2
3
4
5
6
7
8
9
10
11
const user = {
profile: {
details: { name: 'Alice' },
},
}

const name = user.profile?.details?.name ?? 'Anonymous'
console.log(name) // 输出 'Alice'

const age = user.profile?.age ?? 18
console.log(age) // 输出 18

特�?6: ES2019 的数�?flat �?flatMap 方法

flat 展开多层嵌套数组,flatMap 结合映射与扁平化操作�?

1
2
3
4
5
6
const nestedArray = [1, [2, [3, 4]], 5]
console.log(nestedArray.flat(2)) // [1, 2, 3, 4, 5]

const strings = ['hello', 'world']
console.log(strings.flatMap((str) => str.split('')))
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

:::

参考文�?
::: details

:::

typeof 能判断哪些类�?

参考答�?
::: details

类型 *返回�? 备注
Undefined "undefined" 当变量未被定义或未赋值时,返回此值�?
Null "object" 历史遗留问题,null 被错误地识别为对象�?
Boolean "boolean" 适用�?true �?false 值�?
Number "number" 适用于整数和浮点数(包括特殊�?NaN �?Infinity)�?
String "string" 适用于字符串(例�?"hello")�?
BigInt "bigint" 适用于任意大的整数(例如 10n)�?
Symbol "symbol" 适用�?Symbol 类型�?
*Function(classes�? "function" 适用于可调用的对象(如函数和类定义)�?
其他对象 "object" 包括数组、普通对象、日期对象、正则表达式等非函数对象�?

*注意�?

  1. typeof null === "object"
    �?JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签�?0。由�?null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返�?”object”

  2. 实际使用
    对于更复杂的类型检测,可以使用工具函数,如 Object.prototype.toString.call() 或第三方库(�?lodash)�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // 数�?typeof 37 === 'number'
    typeof 3.14 === 'number'
    typeof 42 === 'number'
    typeof Math.LN2 === 'number'
    typeof Infinity === 'number'
    typeof NaN === 'number' // 尽管它是 "Not-A-Number" (非数�? 的缩�?typeof Number(1) === 'number' // Number 会尝试把参数解析成数�?typeof Number('shoe') === 'number' // 包括不能将类型强制转换为数字的�?
    typeof 42n === 'bigint'

    // 字符�?typeof '' === 'string'
    typeof 'bla' === 'string'
    typeof `template literal` === 'string'
    typeof '1' === 'string' // 注意内容为数字的字符串仍是字符串
    typeof typeof 1 === 'string' // typeof 总是返回一个字符串
    typeof String(1) === 'string' // String 将任意值转换为字符串,�?toString 更安�?
    // 布尔�?typeof true === 'boolean'
    typeof false === 'boolean'
    typeof Boolean(1) === 'boolean' // Boolean() 会基于参数是真值还是虚值进行转�?typeof !!1 === 'boolean' // 两次调用 !(逻辑非)运算符相当于 Boolean()

    // Symbols
    typeof Symbol() === 'symbol'
    typeof Symbol('foo') === 'symbol'
    typeof Symbol.iterator === 'symbol'

    // Undefined
    typeof undefined === 'undefined'
    typeof declaredButUndefinedVariable === 'undefined'
    typeof undeclaredVariable === 'undefined'

    // 对象
    typeof { a: 1 } === 'object'

    // 使用 Array.isArray 或�?Object.prototype.toString.call
    // 区分数组和普通对�?typeof [1, 2, 4] === 'object'

    typeof new Date() === 'object'
    typeof /regex/ === 'object'

    // 下面的例子令人迷惑,非常危险,没有用处。避免使用它们�?typeof new Boolean(true) === 'object'
    typeof new Number(1) === 'object'
    typeof new String('abc') === 'object'

    // 函数
    typeof function () {} === 'function'
    typeof class C {} === 'function'
    typeof Math.sin === 'function'

:::

== �?=== 有什么区别?

参考答�?
::: details

  • ==(宽松相等):会在比较两个操作数时执�?类型转换,尝试将两者转换为相同类型后再比较�?- ===(严格相等):不会执行类型转换,仅在类型和值完全相同的情况下返�?true�?- **推荐使用 ===**:因为它更严格、更符合预期,能避免潜在的错误。尤其是在需要精确判断值和类型时�?- 实际工作中,使用 if (a == null) 可判�?a 是否�?null 或�?undefined�?
    常见比较结果
x y == ===
undefined undefined �? �?
null null �? �?
true true �? �?
false false �? �?
'foo' 'foo' �? �?
0 0 �? �?
+0 -0 �? �?
+0 0 �? �?
-0 0 �? �?
0n -0n �? �?
0 false �? �?
"" false �? �?
"" 0 �? �?
'0' 0 �? �?
'17' 17 �? �?
[1, 2] '1,2' �? �?
new String('foo') 'foo' �? �?
null undefined �? �?
null false �? �?
undefined false �? �?
{ foo: 'bar' } { foo: 'bar' } �? �?
new String('foo') new String('foo') �? �?
0 null �? �?
0 NaN �? �?
'foo' NaN �? �?
NaN NaN �? �?

说明�?

  • �?表示比较结果�?true
  • �?表示比较结果�?false

:::

你熟悉哪些数�?API �?

参考答�?
::: details

  1. 创建数组
    • Array(), Array.of(), Array.from()
1
2
Array.of(1, 2, 3) // [1, 2, 3]
Array.from('123') // ['1', '2', '3']
  1. 添加/删除元素

    • push(): 在末尾添�? - pop(): 删除末尾
    • unshift(): 在开头添�? - shift(): 删除开�?
      1
      2
      3
      4
      5
      let arr = [1, 2]
      arr.push(3) // [1, 2, 3]
      arr.pop() // [1, 2]
      arr.unshift(4) // [4, 1, 2]
      arr.shift() // [1, 2]
  2. 组合/拆分数组

    • concat(): 合并数组,不影响原数组,浅拷�? - join(): 将数组连接为字符�? - slice(): 截取部分数组(不修改原数组)
1
2
;[1, 2].concat([3, 4]) // [1, 2, 3, 4]
;['a', 'b', 'c'].join('-') // 'a-b-c'
  1. 替换/重组
    • splice(): 添加、删除或替换元素
1
2
let arr = [1, 2, 3]
arr.splice(1, 1, 'a') // [1, 'a', 3]
  1. 查找单个元素
    • indexOf(): 查找首次出现的索�? - lastIndexOf(): 查找最后出现的索引
    • find(): 找到第一个满足条件的元素
    • findIndex(): 找到第一个满足条件的索引
1
2
3
;[1, 2, 3].indexOf(2) // 1
;[1, 2, 3, 2].lastIndexOf(2) // 3
;[1, 2, 3].find((x) => x > 2) // 3
  1. 判断

    • includes(): 判断是否包含某元�? - some(): 判断是否至少有一个元素满足条�? - every(): 判断是否所有元素满足条�?
      1
      2
      3
      ;[1, 2, 3].includes(2) // true
      ;[1, 2, 3].some((x) => x > 2) // true
      ;[1, 2, 3].every((x) => x > 0) // true
  2. 迭代

    • forEach(): 遍历元素,无�?break,可以用 try/catch �?throw new Error 来停�?
      1
      ;[1, 2, 3].forEach((item, index) => console.log(item, index))
  3. 映射/变换

    • map(): 对每个元素进行操作并生成新数�?
      1
      ;[1, 2, 3].map((x) => x * 2) // [2, 4, 6]
  4. 过滤

    • filter(): 筛选出满足条件的元�?
      1
      ;[1, 2, 3].filter((x) => x > 1) // [2, 3]
  5. 规约

  • reduce(): 将数组缩减为单一�?- reduceRight(): 从右向左缩减
1
2
;[1, 2, 3].reduce((acc, val) => acc + val, 0) // 6
;['a', 'b', 'c'].reduceRight((acc, val) => acc + val, '') // 'cba'
  1. 排序
  • sort(): 对数组进行排�?- reverse(): 反转数组顺序
1
2
;[3, 1, 2].sort((a, b) => a - b) // [1, 2, 3]
;[1, 2, 3].reverse() // [3, 2, 1]
  1. 填充
  • fill(): 用指定值填充数�?
    1
    new Array(3).fill(0) // [0, 0, 0]
  1. *扁平�?
  • flat(): 将多维数组展平成一�?- flatMap(): 映射并展�?
    1
    2
    ;[1, [2, [3]]].flat(2) // [1, 2, 3]
    ;[1, 2].flatMap((x) => [x, x * 2]) // [1, 2, 2, 4]
  1. 复制/填充
  • copyWithin(): 将数组的部分内容复制到其他位�?
    1
    ;[1, 2, 3, 4].copyWithin(1, 2) // [1, 3, 4, 4]
  1. 生成键值对
  • keys(), values(), entries()
1
2
3
const arr = ['a', 'b', 'c']
;[...arr.keys()] // [0, 1, 2]
;[...arr.entries()] // [[0, 'a'], [1, 'b'], [2, 'c']]
  1. *判断是否是数�?
  • Array.isArray()
1
Array.isArray([1, 2, 3]) // true

:::

值类型和引用类型的区�?

1
2
3
4
// 值类�?let a = 100
let b = a
a = 200
console.log(b) // 100
1
2
3
4
5
// 引用类型
let a = { age: 20 }
let b = a
b.age = 21
console.log(a.age) // 21

参考答�?
::: details

特�? 值类�? 引用类型
存储内容 数据值本�? 数据的引用(地址�?
存储位置 栈内�? 栈存引用,堆存实际数�?
*赋值方�? 拷贝�? 拷贝引用(地址�?
*变量之间独立�? 互相独立,互不影�? 指向同一数据,互相影�?
常见数据类型 基本数据类型(如 number,string,boolean,undefined,null,symbol�? 复杂数据类型(如 Object,Array,Function�?
  1. 为什么有值类型和引用类型�?
  • **值类�?*适合存储简单、占用内存较小的数据,操作快速�?- 引用类型适合存储复杂、占用内存较大的数据,支持动态扩展�?
  1. 如何避免引用类型的共享问题?
  • 如果需要创建引用类型的副本,使用深拷贝,而非浅拷贝�?
    深拷贝例子:
1
2
3
const obj1 = { name: 'Alice' }
const obj2 = JSON.parse(JSON.stringify(obj1)) // 创建深拷�?obj2.name = 'Bob'
console.log(obj1.name) // "Alice"

浅拷贝例子:

1
2
3
const obj1 = { name: 'Alice' }
const obj2 = { ...obj1 } // 浅拷�?obj2.name = 'Bob'
console.log(obj1.name) // "Alice"

:::

箭头函数和普通函数的区别

参考答�?
::: details

特�? 箭头函数 普通函�?
语法 简洁,使用 => 定义 使用 function 定义
this 绑定 词法绑定,继承外�?this 动态绑定,调用时决�?
arguments 对象 没有,需要使�?...args 有自己的 arguments 对象
是否能作为构造函�? 不能 可以
是否�?prototype 属�? 没有 �?
是否支持 bind/call/apply 不支�? 支持
适用场景 用于回调函数、闭包、需要继承外�?this 的场�? 需要动态绑�?this,或用作构造函数时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 箭头函数 this
const obj = {
name: 'Alice',
say: () => {
console.log(this.name) // undefined (继承全局作用域的 this)
},
}
obj.say()

// 普通函�?this
const obj = {
name: 'Alice',
say: function () {
console.log(this.name) // "Alice" (this 指向 obj)
},
}
obj.say()

// 箭头函数 不能作为构造函�?const Person = (name) => {
this.name = name
}
const p = new Person('Alice') // TypeError: Person is not a constructor

// 普通函�?构造函�?function Person(name) {
this.name = name
}
const p = new Person('Alice')
console.log(p.name) // "Alice"

// 箭头函数 ...args
const add = (...args) => {
console.log(args) // [1, 2, 3]
}
add(1, 2, 3)

// 普通函�?arguments
function add() {
console.log(arguments) // Arguments(3) [1, 2, 3]
}
add(1, 2, 3)

// 箭头函数 不支�?`bind/call/apply`
const obj = {
value: 42,
}
const arrowFn = () => {
console.log(this.value)
}
arrowFn.call(obj) // undefined

// 普通函�?支持 `bind/call/apply`
const obj = {
value: 42,
}
function normalFn() {
console.log(this.value)
}
normalFn.call(obj) // 42

:::

什么时候不能使用箭头函�?

参考答�?
::: details

  1. 需要动态绑�?this 的场景�?2. 作为构造函数�?3. 需�?arguments 对象的场景�?4. 需要显式修�?this 的场景(使用 bind/call/apply 等)�?5. 类的实例方法(特别是 getter �?setter)。—�?无法动态绑�?this

:::

for…in �?for…of 的区�?

参考答�?
::: details

特�? for...in for...of
*用�? 遍历对象�?*可枚举属�? 遍历 **可迭代对�?*(如数组、字符串等)
*返回�? 返回 **�?*(属性名�? 返回 **�?*(元素值)
适用范围 对象、数组(不推荐用于数组) 数组、字符串、Set、Map等可迭代对象
*是否遍历原型�? 会遍历原型链上的可枚举属�? 不会遍历原型�?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// for...in 遍历对象
const obj = { name: 'Alice', age: 25 }

for (let key in obj) {
console.log(key) // 输出属性名:name, age
console.log(obj[key]) // 输出属性值:Alice, 25
}

// for...in 遍历数组,不推荐
const arr = [10, 20, 30]

for (let index in arr) {
console.log(index) // 输出索引�?, 1, 2
console.log(arr[index]) // 输出值:10, 20, 30
}

// for...of 遍历数组
const arr = [10, 20, 30]

for (let value of arr) {
console.log(value) // 输出值:10, 20, 30
}

:::

JS 原型和原型链

参考答�?
::: details

proto

*1. 原型(Prototype�?

  • 每个 函数(构造函数)都有一�?prototype 属性,指向�?原型对象�?- 每个 对象 都有一�?__proto__ 指向其构造函数的 prototype,形成继承关系�?
    *2. 原型链(Prototype Chain�?

  • 访问对象属性时,先查找自身属性,找不到则�?__proto__ 逐级向上查找,直�?null 终止�?- Object.prototype.__proto__ === null,原型链的顶端是 Object.prototype�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Person(name) {
    this.name = name
    }
    Person.prototype.sayHello = function () {
    console.log('Hello!')
    }

    const p = new Person('Rain')
    console.log(p.__proto__ === Person.prototype) // true
    console.log(Person.prototype.__proto__ === Object.prototype) // true
    console.log(Object.prototype.__proto__ === null) // true

:::

JS 继承有几种方式?

参考答�?
::: details

*1. 原型链继�?

*核心思路�? 让子类的 prototype 指向父类实例�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Parent() {
this.name = 'Parent'
}
Parent.prototype.sayHello = function () {
console.log('Hello from Parent')
}

function Child() {}
Child.prototype = new Parent() // 继承 Parent
Child.prototype.constructor = Child

const child = new Child()
console.log(child.name) // "Parent"
child.sayHello() // "Hello from Parent"

�?*优点�? 父类方法可复�?�?*缺点�? 1. 共享引用类型属性(�?arr = [] 会被多个实例共享),2. 无法向父类构造函数传�?
*2. 借用构造函数继�?

*核心思路�? 在子类构造函数中使用 call 继承父类属性�?

1
2
3
4
5
6
7
8
9
function Parent(name) {
this.name = name
}
function Child(name, age) {
Parent.call(this, name) // 继承 Parent
this.age = age
}
const child = new Child('Rain', 18)
console.log(child.name, child.age) // "Rain", 18

�?*优点�? 1. 解决原型链继承共享问题,2. 可传�?�?*缺点�? 无法继承父类原型上的方法

*3. 组合继承(原型链 + 构造函数继承,最常用�?

核心思路�? 结合前两种方式,*继承属性用构造函数,继承方法用原型链**�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Parent(name) {
this.name = name
}
Parent.prototype.sayHello = function () {
console.log('Hello from Parent')
}

function Child(name, age) {
Parent.call(this, name) // �?1 次调�?Parent
this.age = age
}

Child.prototype = new Parent() // �?2 次调�?Parent
Child.prototype.constructor = Child

const child = new Child('Rain', 18)
console.log(child.name, child.age) // "Rain", 18
child.sayHello() // "Hello from Parent"

�?*优点�? 解决了前两种方法的缺�?�?*缺点�? 调用两次 Parent 构造函数(一�?call,一�?Object.create()�?
*4. Object.create() 继承(原型式继承�?

*核心思路�? 直接�?Object.create() 创建一个新对象,继承已有对象�?

1
2
3
4
5
6
7
8
9
10
const parent = {
name: 'Parent',
sayHello() {
console.log('Hello!')
},
}
const child = Object.create(parent)
child.age = 18
console.log(child.name, child.age) // "Parent", 18
child.sayHello() // "Hello!"

�?*优点�? 适合创建对象而非类的继承
�?*缺点�? 不能传参,只适用于简单继�?
5. 寄生组合继承(优化版,推荐)

*核心思路�? **组合继承的优化版�?*,避免了 Parent 被调用两次的问题�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent(name) {
this.name = name
}
Parent.prototype.sayHello = function () {
console.log('Hello from Parent')
}

function Child(name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = Object.create(Parent.prototype) // 关键优化
Child.prototype.constructor = Child

const child = new Child('Rain', 18)
console.log(child.name, child.age) // "Rain", 18
child.sayHello() // "Hello from Parent"

�?*优点�? 1. 继承属性和方法�?. 只调用一�?Parent
�?*缺点�? 代码略微复杂

*6. ES6 class 继承(最现代化的方式�?

*核心思路�? class 语法糖,实际仍然基于原型继承�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Parent {
constructor(name) {
this.name = name
}
sayHello() {
console.log('Hello from Parent')
}
}

class Child extends Parent {
constructor(name, age) {
super(name) // 继承属�? this.age = age
}
}

const child = new Child('Rain', 18)
console.log(child.name, child.age) // "Rain", 18
child.sayHello() // "Hello from Parent"

�?*优点�? 语法更清晰,易读易用
�?*缺点�? 本质仍是 prototype 继承

:::

JS 作用域和作用域链

参考答�?
::: details

  • **作用�?*:变量的可访问范围,分为 全局作用域、函数作用域、块级作用域�?- 作用域链:变量查找机制,从当前作用域 逐级向上查找,直到全局作用域或 ReferenceError�?- **ES6 关键�?*�? - let / const **具有块级作用�?*,避�?var 变量提升带来的问题�? - 闭包 利用作用域链,保留外部作用域的变量�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var a = 'global'

    function outer() {
    var b = 'outer'

    function inner() {
    var c = 'inner'
    console.log(a, b, c) // �?global outer inner
    }

    inner()
    }

    outer()
    console.log(b) // �?ReferenceError: b is not defined

:::

JS 自由变量,如何理�?

参考答�?
::: details
自由变量 指的�?在当前作用域中未声明,但在上层作用域中找到的变量�?
�?JavaScript 中,当代码执行时,如果遇到一个变量:

  • *当前作用�? 找不到该变量,就会沿着 作用域链 向上查找,直到找到该变量或报 ReferenceError�?- *这个在外层作用域中找到的变量,就是自由变量�?
1
2
3
4
5
6
7
var a = 10 // 全局变量(自由变量)

function foo() {
console.log(a) // 访问自由变量 a
}

foo() // 10

:::

JS 闭包,如何理�?

参考答�?
::: details
闭包的核心特性:

  1. 访问外部函数作用域的变量

  2. 即使外部函数执行结束,变量依然被保留

  3. 不会被垃圾回收,直到闭包不再被引�?
    闭包的应用场景:

  4. 私有变量(模拟封装)

1
2
3
4
5
6
7
8
9
10
11
12
13
function createCounter() {
let count = 0 // 私有变量,外部无法直接访�? return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
}
}

const counter = createCounter()
console.log(counter.increment()) // 1
console.log(counter.increment()) // 2
console.log(counter.getCount()) // 2
console.log(counter.count) // undefined(外部无法直接访问)
  1. 回调 & 事件监听
1
2
3
4
5
6
7
function addEventLogger(eventName) {
return function () {
console.log(`Event ${eventName} triggered!`)
}
}

document.addEventListener('click', addEventLogger('click'))
  1. 定时�?& 异步操作
1
2
3
4
5
6
7
function delayedGreeting(name) {
setTimeout(() => {
console.log(`Hello, ${name}!`)
}, 2000)
}

delayedGreeting('Rain') // 2 秒后打印 "Hello, Rain!"

闭包的缺点:

  1. 可能导致内存泄漏
  • 闭包会持有外部变量的引用,导致变量无法被垃圾回收
  • 解决方案:手动将变量置为 null 或谨慎管理作用域
  1. 滥用闭包可能影响性能
  • 每次调用都会创建新的作用域,影响垃圾回收机制
  • 适度使用,避免不必要的闭�?
    :::

同步和异步有什么区别?异步的意义是什么?

参考答�?
::: details
同步:任务按顺序执行,当前任务未完成时,后续代码必须等待,代码是阻塞的�?异步:任务可�?*不按顺序执行,不会阻塞代码,后续代码可以继续执行,代码是非阻�?*的�?

特�? 同步 异步
执行方式 顺序执行,阻塞后续任�? 非阻塞,任务可以并行执行
代码特点 阻塞,必须等待上一个任务完�? **非阻�?*,任务可以同时进�?
适用场景 计算密集型、简单逻辑处理 网络请求、I/O 操作、高并发
1
2
3
4
// 同步
console.log('任务 1')
alert('等待用户操作...')
console.log('任务 2') // 只有用户关闭 alert,任�?2 才能执行
1
2
3
4
5
6
7
8
9
10
11
// 异步
console.log('任务 1')

setTimeout(() => {
console.log('任务 2(延�?2 秒)')
}, 2000)

console.log('任务 3') // 任务 3 不会等待 任务 2
// 任务 1
// 任务 3
// �? 秒后�?// 任务 2(延�?2 秒)

*为什么要用异步?(异步的意义�?

  1. 避免阻塞,提升用户体�?
  • 异步任务(如网络请求、文件读写)可以在后台执行,避免阻塞 UI,保证页面流畅�?
  1. 提升系统性能,支持高并发
  • 服务器可以同时处理多个请求,提高吞吐量(�?Node.js 处理高并发)�?
  1. 更适合现代 Web 开�?
  • Promise / async-await 让异步代码更可读,配�?fetch 进行网络请求,提升开发效率�?
    :::

JS Promise 有几种状态?如何变化

参考答�?
::: details

1. Promise 有几种状态?

状�? 说明 是否可变�?
*Pending(进行中�? 初始状态,异步操作未完�? �?可以变更
*Fulfilled(已完成�? 操作成功,返�?resolve 结果 �?变更结束
*Rejected(已拒绝�? 操作失败,返�?reject 错误 �?变更结束

2. Promise 状态如何变化?

Promise 的状�?*只会�?Pending �?Fulfilled �?Pending �?Rejected,且一旦变化就不会再改�?*(不可逆)�?

1
2
3
4
5
6
7
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
// reject("失败"); // 只会触发一次,状态不可�? }, 1000)
})

promise.then((result) => console.log('Fulfilled:', result)).catch((error) => console.log('Rejected:', error))

:::

JS Promise 使用

参考答�?
::: details
*1. 什么是 Promise�?

**Promise �?JavaScript 处理异步操作的一种方�?*,用于解决回调地狱(Callback Hell)问题�?> 它表示一个未来才会完成(或失败)的异步操作,并提�?.then().catch().finally() 方法进行处理�?
*2. Promise 的基本用�?

创建一�?Promise

1
2
3
4
5
6
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
let success = true
success ? resolve('操作成功') : reject('操作失败')
}, 1000)
})

使用 thencatch 处理结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
myPromise
.then((result) => console.log('成功:', result)) // 处理成功
.catch((error) => console.log('失败:', error)) // 处理失败
.finally(() => console.log('操作结束')) // 无论成功或失败都会执�?```

**3. Promise 串行执行**

**多个异步操作依次执行(避免回调地狱)**

```js
function step1() {
return new Promise((resolve) => setTimeout(() => resolve('Step 1 完成'), 1000))
}
function step2() {
return new Promise((resolve) => setTimeout(() => resolve('Step 2 完成'), 1000))
}

step1()
.then((result) => {
console.log(result)
return step2() // 返回 Promise
})
.then((result) => console.log(result))
.catch((error) => console.error('错误:', error))

4. Promise 并行执行

*多个异步任务同时执行,全部完成后再处�?

1
2
3
4
5
6
const p1 = new Promise((resolve) => setTimeout(() => resolve('任务 1'), 1000))
const p2 = new Promise((resolve) => setTimeout(() => resolve('任务 2'), 1500))

Promise.all([p1, p2])
.then((results) => console.log('所有任务完�?', results))
.catch((error) => console.error('任务失败:', error))

如果只要最快完成的结果

1
2
3
Promise.race([p1, p2])
.then((result) => console.log('最先完成的:', result))
.catch((error) => console.error('失败:', error))

5. 面试回答总结

*Promise 解决异步回调问题,提�?.then().catch().finally() 处理状态变化。支�?Promise.all() 并行执行,Promise.race() 竞争执行。用 async/await 可以让异步代码更清晰�?

:::

async/await 使用

参考答�?
::: details

async/await �?ES2017(ES8)引入的 基于 Promise 的语法糖,用于更清晰地编写异步代码,使其看起来像同步代码,提高可读性�?

  • async 关键字:用于声明一个异步函数,返回值始终是 Promise�?- await 关键字:只能�?async 函数中使用,等待 Promise 解析(resolve)并返回结果,而不会阻塞线程�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    async function fetchData() {
    try {
    let response = await fetch('https://api.example.com/data')
    let data = await response.json()
    console.log(data)
    } catch (error) {
    console.error('Error:', error)
    }
    }
    fetchData()

:::

JS 异步执行顺序

执行以下代码,会输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function async1() {
console.log('async1')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeOut')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')

答案

::: details

1
2
3
4
5
6
7
8
script start
async1
async2
promise
script end
async1 end
promise2
setTimeOut

:::

宏任务和微任务的区别

参考答�?
::: details
�?JavaScript �?事件循环(Event Loop�?机制中,任务分为 *宏任务(Macro Task�? �?**微任务(Micro Task�?*�?

  • **微任务优�?*:微任务队列会在每次 宏任务执行完�?后立即执行,保证微任务先执行完再进入下一个宏任务�?- **宏任�?*:常见的宏任务包�?setTimeout、setInterval、setImmediate(Node.js)、I/O、UI 渲染�?- **微任�?*:常见的微任务包�?Promise.then、MutationObserver、queueMicrotask、process.nextTick(Node.js)�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    console.log('start')

    setTimeout(() => {
    console.log('setTimeout')
    }, 0)

    Promise.resolve()
    .then(() => {
    console.log('promise1')
    })
    .then(() => {
    console.log('promise2')
    })

    console.log('end')

    // 输出�?// start
    // end
    // promise1
    // promise2
    // setTimeout

:::

描述 Event Loop 运行机制

参考答�?
::: details

eventloop

Event Loop(事件循环)�?JavaScript 处理 异步操作 的核心机制。它允许 JavaScript �?*非阻�? 的方式执行代码,即使遇到 I/O 操作(如网络请求、定时器),也不会影响主线程继续执行其他任务�?
执行流程(核心步骤)

  1. 执行同步任务
  • 所有同步任务在 调用栈(Call Stack�?中依次执行,直到调用栈清空�?
  1. *处理微任�?
  • 检�?微任务队列(MicroTask Queue�?是否有任务(�?Promise.then()、queueMicrotask())�?- 依次执行所有微任务,直到微任务队列清空�?
  1. *执行宏任�?
  • �?宏任务队列(MacroTask Queue�?取出 一�?任务(如 setTimeout 回调、I/O 任务),放入调用栈执行�?
  1. *重复步骤 2(处理新的微任务�?
  • 宏任务执行完毕后,再次检查微任务队列,如果有新产生的微任务,立即执行所有微任务�?
  1. *重复步骤 3(执行下一个宏任务�?
  • 继续取出下一�?宏任务,重复整个过程,形成循环(Event Loop�?
    :::

Set �?Array 有什么区�?

参考答�?
::: details

特�? Array Set
*是否允许重复�? �?允许重复元素 �?只能存储唯一值,自动去重
索引访问 �?可通过索引 (arr[0]) 访问 �?不支持索引访�?
查找性能 🔴 O(n),需要遍历整个数�? 🟢 O(1),基于哈希表查找更快
删除性能 🔴 O(n),需要遍历查找删�? 🟢 O(1),删除性能更优
遍历方式 �?forEach / map / filter �?forEach / for...of
*适合的场�? 存储有序数据,支持索引访�? 需要唯一值集合,去重、快速查�?
转换方式 Array.from(set) (Set �?Array) new Set(array) (Array �?Set)
1
2
3
4
5
6
7
8
9
10
11
12
// Array 允许重复�?const arr = [1, 2, 2, 3, 4, 4]
console.log(arr) // [1, 2, 2, 3, 4, 4]

// Set 自动去重
const set = new Set(arr)
console.log([...set]) // [1, 2, 3, 4]

// Set 无索引访�?console.log(set[0]) // undefined

// Set �?Array
const arrFromSet = Array.from(set)
console.log(arrFromSet) // [1, 2, 3, 4]

:::

Map �?Object 有什么区�?

参考答�?
::: details

特�? Object Map
键的类型 只能�?string �?symbol 可以是任何类型(对象、函数等�?
*键值对的存储顺�? 无序(属性顺序可能变化) 有序(插入顺序保持不变)
查找性能 相对较慢(基于哈希表�? 更快(专门优化的键值存储结构)
迭代方式 for...inObject.keys() �? forEach()for...of(支持迭代器�?
获取键的方式 Object.keys(obj) 只能获取 string �? map.keys() 可获取所有类型的�?
获取大小 需手动计算 Object.keys(obj).length map.size 直接获取大小
是否能轻松转换为 JSON �?可以 JSON.stringify() �?不能直接 JSON.stringify()
适用场景 适用于存储结构化数据,如对象属�? 适用�?高效键值存储和查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Object 只能用字符串作为�?const obj = {}
obj['key1'] = 'value1'
obj[1] = 'value2' // 这里�?1 会被转换�?"1"
console.log(obj) // { '1': 'value2', key1: 'value1' }

// Map 可用任何类型作为�?const map = new Map()
map.set('key1', 'value1')
map.set(1, 'value2') // 数字 1 不会被转换为字符�?console.log(map) // Map(2) { 'key1' => 'value1', 1 => 'value2' }

// Object 迭代(无序)
console.log(Object.keys(obj)) // ['1', 'key1']

// Map 迭代(有序)
console.log([...map.keys()]) // ['key1', 1]

// Map 直接获取大小
console.log(map.size) // 2

// Object 需要手动计算大�?console.log(Object.keys(obj).length) // 2

:::

setTimeout、requestAnimationFrame �?requestIdleCallback 有什么区�?

参考答�?
::: details

特�? setTimeout requestAnimationFrame requestIdleCallback
执行时机 设定时间后执行(不保证准时) 下一帧渲染前�?6.6ms 以内�? 浏览器空闲时(可能延迟执行)
*主要用�? 延迟执行代码 *动画和流畅渲�? 低优先级任务(如日志、分析)
帧率控制 **�?*,可能丢�? **跟随屏幕刷新�?*(一�?60FPS�? 不受限制,完全取决于浏览�?
影响页面性能 *可能影响页面流畅�? 保证流畅动画 *不会阻塞主线�?
*是否适用于动�? �?可能卡顿 �?适合 �?不适合
是否�?CPU 影响 �?受影�? �?受影�? �?受影�?
适用场景 *定时任务、轮�? 动画、过渡、流�?UI 渲染 后台任务、低优先级执行(如数据同步、日志收集)

setTimeout - 定时执行

1
2
3
setTimeout(() => {
console.log('100ms 后执�?)
}, 100)

**requestAnimationFrame - 适用于动�?*

1
2
3
4
5
function animate() {
console.log('下一帧渲染前执行')
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)

**requestIdleCallback - 空闲时执�?*

1
2
3
4
5
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0) {
console.log('空闲时执行低优先级任�?)
}
})

:::

写一个验�?email 的正则表达式

参考答�?
::: details

1
2
const reg = /\w+((-\w+)|(\.\w+))*@[a-zA-Z0-9]+((\.|-)[a-zA-Z0-9]+)*\.[a-zA-Z0-9]+$/
reg.test(email)

:::

JS 模块化规范有哪些�?

参考答�?
::: details

  1. CommonJS

    • 概述:这�?Node.js 中使用的模块化规范。它通过 module.exports �?require() 来导出和引入模块�? - 特点:同步加载,主要用于服务器端(Node.js)�? - 使用场景:服务器端开发,尤其是在 Node.js 中�?
      1
      2
      3
      4
      5
      6
      7
      8
      // 导出模块
      module.exports = function () {
      console.log('Hello, CommonJS!')
      }

      // 导入模块
      const hello = require('./hello')
      hello()
  2. *AMD(Asynchronous Module Definition�?

    • 概述:AMD 是一种异步加载模块的规范,常用于浏览器端�? - 特点:支持异步加载,模块和依赖是按需加载的,通常使用 define() �?require()�? - 使用场景:浏览器端的模块化,尤其是当需要异步加载模块时�?
      1
      2
      3
      4
      5
      define(['dependency'], function (dep) {
      return function () {
      console.log('Hello, AMD!')
      }
      })
  3. *UMD(Universal Module Definition�?

    • 概述:UMD 是一个兼容多种模块化规范(CommonJS、AMD 和全局变量)的模块化方案�? - 特点:确保模块在不同的环境中都能使用�? - 使用场景:需要在多种环境下(�?Node.js、浏览器)使用的库或框架�?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      ;(function (root, factory) {
      if (typeof exports === 'object' && typeof module !== 'undefined') {
      module.exports = factory()
      } else if (typeof define === 'function' && define.amd) {
      define(factory)
      } else {
      root.myModule = factory()
      }
      })(this, function () {
      return function () {
      console.log('Hello, UMD!')
      }
      })
  4. *ES6 Modules(ESM�?

    • 概述:ES6 模块化是 JavaScript 原生的模块化标准,使�?import �?export 语法�? - 特点:支持静态分析,加载时可以进行优化,现代 JavaScript 标准�? - 使用场景:现代前端开发(浏览器和 Node.js)�?
      1
      2
      3
      4
      5
      6
      7
      8
      // 导出模块
      export function greet() {
      console.log('Hello, ESM!')
      }

      // 导入模块
      import { greet } from './greet.js'
      greet()
  5. SystemJS

    • 概述:SystemJS 是一个支持多种模块规范(CommonJS、AMD �?ESM)的模块加载器�? - 特点:支持多种模块格式,动态加载模块�? - 使用场景:需要跨模块加载器兼容的复杂应用�?
      1
      2
      3
      4
      5
      6
      7
      8
      System.config({
      map: {
      greet: './greet.js',
      },
      })
      System.import('greet').then((greet) => {
      greet()
      })

:::

JS 如何捕获异常?有几种方式�?

参考答�?
::: details

  1. try…catch 语句
1
2
3
4
5
6
try {
// 可能会抛出异常的代码
throw new Error('Something went wrong!')
} catch (error) {
// 捕获并处理异�? console.error('Caught an error:', error.message)
}
  1. *Promise 中的错误捕获(catch�?
1
2
3
4
5
6
7
someAsyncFunction()
.then((result) => {
console.log(result)
})
.catch((error) => {
console.error('Async error caught:', error)
})
  1. *window.onerror(全局错误处理�?
1
2
3
4
window.onerror = function (message, source, lineno, colno, error) {
console.error(`Error occurred: ${message}`)
return true // 阻止默认错误处理
}

:::

0.1 + 0.2 === 0.3 表达式返回什么?

参考答�?
::: details
0.1 + 0.2 === 0.3 �?JavaScript 中会返回 **false**�?
*原因�?
JavaScript 中的浮点数运算存在精度问题。由于计算机在内部表示浮点数时不能精确表示某些小数,导致 0.1 + 0.2 的结果并不是精确�?0.3,而是一个接近于 0.3 的小数�?
具体来说,0.1 + 0.2 的计算结果是 0.30000000000000004,而不�?0.3。因此,当你�?===(严格相等)进行比较时,0.30000000000000004 �?0.3 不相等,结果�?false�?
*解决方法�?

  1. 四舍五入�?

    1
    console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON) // true
  2. **自定义精度比�?*�? 将浮动值限制到一定的小数位,进行比较�? ```javascript
    console.log(Math.round((0.1 + 0.2) * 100) / 100 === 0.3) // true

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50

    :::

    ## 如何理解 JS 单线程?

    参考答�?
    ::: details
    **什么是 JavaScript 单线程?**

    JavaScript �?**单线�?* 的意思是它只有一个线程来执行代码,这意味着它一次只能执行一个任务。所有的 JavaScript 代码,默认情况下,都会按照顺序在同一个线程中依次执行。单线程的特性使�?JavaScript 相比多线程语言在处理并发时有一些限制,但它也有一套机制来处理异步操作,避免阻塞主线程�?
    **为什么是单线程?**

    JavaScript 的设计目的是为了简化开发,尤其是在浏览器环境中。单线程可以避免多线程带来的复杂性,比如线程同步、资源竞争等问题。为了不让长时间的任务阻�?UI 渲染,JavaScript 提供了异步编程的机制�?
    **如何处理并发任务�?*

    虽然 JavaScript 是单线程的,但它通过以下机制来实现并发任务的处理�?
    1. **事件循环(Event Loop�?*:JavaScript 使用事件循环来管理异步任务。通过事件循环,JavaScript 可以在任务执行时不中断主线程的执行。异步任务(比如 `setTimeout`、`Promise`、`XHR` 等)会先进入 **消息队列(Event Queue�?*,当主线程空闲时,再从队列中取出任务执行�?
    2. **Web APIs**:浏览器提供�?**Web APIs**(如 `setTimeout`、`fetch`、`DOM` 等)来处理一些异步操作。这些操作会被交给浏览器�?API 处理,处理完后通过事件循环机制将回调函数推送到消息队列,等待主线程执行�?
    3. **异步编程**:通过 **`setTimeout`**�?*`Promise`**�?*`async/await`** 等方式,JavaScript 可以非阻塞地处理 I/O 操作,避免卡住整个程序的执行�?
    :::

    ## 什么是 WebWorker 如何理解它?

    参考答�?
    ::: details

    **Web Worker** 是一种浏览器提供�?API,允许你在一个独立的线程中执�?JavaScript 代码�?*与主线程(UI 线程)分�?*。Web Worker 可以处理计算密集型任务,如数据处理、文件解析等,这些任务通常会阻塞主线程,导�?UI 卡顿。通过 Web Worker,你可以将这些耗时操作移到后台线程,确保主线程始终保持响应状态�?
    **工作原理�?*

    1. **独立线程**:Web Worker 在一个与主线程(UI 线程)分离的线程中运行,主线程和 Worker 线程之间通过消息传递(postMessage)进行通信�?2. **主线程与 Worker 通信**:主线程可以通过 `postMessage()` 方法�?Worker 发送数据,Worker 完成计算后,通过 `postMessage()` 将结果返回给主线程�?
    3. **异步操作**:由�?Worker 在后台线程中运行,因此它的执行不会阻塞主线程,所有的计算任务都是异步执行的�?
    4. **线程间通信**:Worker 无法直接访问主线程的 DOM、`window` 或�?`document` 等对象,它只能通过 `postMessage()` 与主线程进行数据交换。返回的数据是通过事件机制传递的,使�?`onmessage` 监听数据的返回�?
    **Web Worker 的优势:**

    - **性能提升**:Web Worker 可以让长时间的计算任务在后台线程中执行,避免 UI 阻塞,提升用户体验�?- **非阻塞�?*:主线程可以继续处理用户交互和渲染,而不被复杂计算所阻塞�?- **多线程处�?*:对�?CPU 密集型任务,Web Worker 可以将工作分配给多个 Worker,实现并行计算,提高性能�?
    **Web Worker 的应用场景:**

    - **大数据处�?*:例如,处理大量的数组计算、排序、数据筛选等任务�?- **图像处理**:例如,进行图像的处理和转换,而不影响 UI 渲染�?- **音视频处�?*:例如,音视频的编码、解码等计算密集型操作�?- **异步任务**:一些需要后台执行的异步任务,可以通过 Worker 来处理�?
    **Web Worker 的局限性:**

    - **无法操作 DOM**:Web Worker 在独立线程中运行,不能直接访�?DOM �?`window`,只能通过消息传递来与主线程交换数据�?- **数据传�?*:数据通过 `postMessage()` 传递时会发生深拷贝,因此传递大数据时可能会有性能开销�?- **浏览器支�?*:大多数现代浏览器支�?Web Worker,但在旧版浏览器中可能不被支持�?
    1. **创建一�?Web Worker�?*

    ```javascript
    // main.js (主线�?
    const worker = new Worker('worker.js') // 创建 Worker 实例

    worker.postMessage('Hello, Worker!') // �?Worker 发送消�?
    worker.onmessage = function (event) {
    console.log('Worker says: ', event.data) // 接收 Worker 的响�? }
  3. Worker 文件(worker.js):

    1
    2
    3
    4
    // worker.js (Worker 线程)
    onmessage = function (event) {
    console.log('Main thread says: ', event.data)
    postMessage('Hello, Main Thread!') // 发送响应到主线�? }

:::

JS 如何进行内存管理和垃圾回收?

参考答�?
::: details

JavaScript 的内存管理是自动的,主要通过 *垃圾回收(GC�? 来实现�?
*内存管理�?

  1. JavaScript 使用 自动内存管理,开发者不需要手动分配和释放内存�?2. 内存通过 堆(用于存储对象和数组等动态分配的内存)和 栈(用于存储函数调用和局部变量)进行管理�?
    *常用的垃圾回收机制有�?

  2. **标记-清除(Mark-and-Sweep�?*:标记活动对象,清除未标记对象,释放内存�?2. 引用计数:计算对象的引用次数,引用为 0 时回收。但会有循环引用的问题�?3. **生成式垃圾回�?*:通过将内存分为年轻代和老年代,优化垃圾回收频率,减少内存碎片�?
    :::

如何检�?JS 内存泄漏?内存泄漏的场景有哪些?

参考答�?
::: details

  1. **使用浏览器开发者工�?*�?

    • Chrome DevTools 中的 Memory 面板可以用来检测内存泄漏。可以查�?Heap Snapshot �?Allocation instrumentation on timeline,分析对象分配、释放情况�? - Heap Snapshot:查看对象的分配情况,并通过比较不同时间点的快照来发现泄漏�? - Timeline:在页面交互过程中,查看内存的使用情况,发现持续增长的内存占用�?
  2. 通过 performance.memory API�?

    • 在支持的浏览器中,可以通过 performance.memory API 获取当前的内存使用情况(�?JS 堆内存大小),来跟踪内存的变化�?
      1
      console.log(window.performance.memory)
  3. **手动检�?*�?

    • 通过创建和销毁对象,使用 setInterval �?setTimeout 来检测是否有对象未被回收�? - 观察垃圾回收器是否清理不再使用的对象,如果内存不断增长,可能就是内存泄漏�?
  4. **第三方工�?*�? - Valgrind�?Memory.js* 等工具可以帮助检测内存泄漏�?
    内存泄漏的常见场景:

  5. 全局变量�?

    • 意外的全局变量会导致对象无法被回收�? ```javascript
      function test() {
      leakedVar = ‘This is a global variable’ // 未声明的变量成为全局变量
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      2. **未移除的事件监听�?*�?
      - 如果事件监听器被绑定�?DOM 元素上,但没有在元素移除后正确移除,可能导致内存泄漏�? ```javascript
      const button = document.getElementById('myButton')
      button.addEventListener('click', function () {
      /* some logic */
      })
      // 如果没有 button.removeEventListener,按钮被移除后内存仍未释�? ```

      3. **闭包(Closures�?*�?
      - 闭包会保持对外部函数变量的引用,如果闭包生命周期过长,会导致外部函数的变量无法释放�? ```javascript
      function createClosure() {
      let largeObject = new Array(1000).fill('Some data')
      return function () {
      console.log(largeObject) // largeObject 被闭包引用,无法�?GC 回收
      }
      }
      let closure = createClosure()
  6. DOM 引用�?

    • 保留对已删除 DOM 元素的引用,导致内存泄漏�?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      let div = document.createElement('div')
      document.body.appendChild(div)

      // 删除 DOM
      document.body.removeChild(div)

      // �?如果还保留引用,GC 无法回收
      let cache = div

      // ✔️ 手动断开引用,GC 才能回收
      div = null
      cache = null
  7. 定时器(setInterval/setTimeout)未清除�?

    • 如果定时器没有清除,仍然会占用内存�? ```javascript
      let interval = setInterval(function () {
      console.log(‘Running’)
      }, 1000)
      // 如果没有 clearInterval(interval),定时器将一直运行,导致内存泄漏
      1
      2
      3
      4

      6. **Web Workers 和后台线�?*�? - 如果 Web Worker 或后台线程没有正确终止,可能会导致内存泄漏�? ```javascript
      const worker = new Worker('worker.js')
      // 如果没有 worker.terminate(),worker 可能导致内存泄漏

:::

如何理解 WebAssembly�?

参考答�?
::: details

*WebAssembly(Wasm�? 是一种新�?Web 技术,它允许开发者将其他编程语言(如 C、C++、Rust 等)编译成高效的二进制代码,并在浏览器中运行。WebAssembly 旨在提供接近原生性能�?Web 体验,特别适用于高性能计算任务�?
关键点:

  1. **高效�?*:WebAssembly 是一种二进制格式,比 JavaScript 的文本格式更紧凑,加载速度更快,执行速度更快,适用�?CPU 密集型任务,如图像处理、游戏开发和科学计算�?
  2. �?JavaScript 协作:WebAssembly �?JavaScript 可以协同工作,JavaScript 用于 UI 操作和事件处理,WebAssembly 负责计算密集型任务。它们通过 共享内存 �?*消息传�? 进行通信�?
  3. **跨平�?*:WebAssembly 是跨平台的,可以在所有支�?WebAssembly 的现代浏览器中运行,并且不需要针对不同操作系统和硬件做额外的修改�?
  4. **安全�?*:WebAssembly 运行在沙盒环境中,不能直接访问操作系统资源,保证�?Web 应用的安全性�?
    应用场景�?
  • **游戏开�?*:通过高效的计算,WebAssembly 可以�?Web 上的游戏运行得更流畅�?- 图像/视频处理:利�?WebAssembly 进行高效的图像处理和视频编解码�?- 科学计算:WebAssembly 能大大提�?JavaScript 在处理大数据和复杂计算时的性能�?
    :::

JS V8 Nodejs Deno Bun 这几个,他们是什么关系?

参考答�?
::: details

1. V8

  • V8 是一个开源的 JavaScript 引擎,由 Google 开发,主要用于 Chrome 浏览器和 Node.js�?- V8 �?JavaScript 代码编译成机器代码并执行,从而提�?JavaScript 的执行效率�?- 作用:V8 �?JavaScript 执行的“心脏”,负责解析和执�?JavaScript 代码�?- 关系:V8 �?Node.js �?Deno 的底层引擎。它本身不提供完整的 JavaScript 环境或库,只负责执行 JavaScript�?
    2. Node.js

  • Node.js 是一个基�?V8 引擎�?**JavaScript 运行时环�?*,使�?JavaScript 不仅可以在浏览器中运行,还可以在服务器端运行�?- 它为 JavaScript 提供�?I/O 操作、文件系统访问、网络请求等功能,这些功能通常由操作系统提供�?- 作用:Node.js 使得 JavaScript 可以用于构建服务器端应用,支持事件驱动、非阻塞�?I/O 机制�?- 关系:Node.js 使用 V8 作为�?JavaScript 引擎,除此之外,它还包含一些额外的 API(如 fshttppath 等)来提供对文件系统、网络等资源的访问�?
    3. Deno

  • Deno 是一个由 Node.js 的原始开发�?Ryan Dahl 创建的新�?JavaScript/TypeScript 运行时环境�?- 它同样使�?V8 引擎,但是与 Node.js 不同的是,Deno 内置了对 TypeScript 的支持,且具有现代化的安全特性(如权限控制)�?- 作用:Deno 旨在修复 Node.js 中存在的一些设计问题,提供更简洁和安全的运行时环境�?- 关系:Deno 使用 V8 作为 JavaScript 引擎,但它不�?Node.js 的直接继承者,而是对现�?JavaScript 运行时环境的一次重构,加入了许多新的功能和改进�?
    4. Bun

  • Bun 是一个新兴的 **JavaScript/TypeScript 运行�?*,其目标是提供更高效的性能,特别是在构建工具和服务器端应用中�?- Bun 是基�?JavaScriptCore(Safari 浏览器的 JavaScript 引擎)构建的,而不�?V8�?- 作用:Bun 具有非常快速的执行速度,提供类似于 Node.js �?API,同时它也是一个现代的构建工具(例如,能够快速打包、转译和运行 JavaScript/TypeScript 代码)�?- 关系:Bun 不是基于 V8 引擎,它使用的是 JavaScriptCore 引擎,但它与 Node.js �?Deno 类似,作为一�?JavaScript 运行时环境提供底层支持�?
    :::

有了解过WeakMap吗?WeakMap与Map的区别是什么?

参考答�?
::: details

1. 什么是WeakMap

WeakMap �?JavaScript 中的一种集合类型,它存储键值对,且键必须是对象,并且键是弱引用的。这意味着,如果键对象没有其他引用,它会被垃圾回收器回收,对应的键值对也会被自动删除�?
*2. 与Map的区�?

键的类型

  • **Map**:键可以是任意类型,包括基本数据类型(像字符串、数字等)和引用类型(如对象、函数)�?- **WeakMap**:键只能是对象,不能使用基本数据类型作为键�?
    垃圾回收机制

  • **Map**:对键所引用的对象是强引用。只�?Map 存在,键引用的对象就不会被垃圾回收,即便其他地方无该对象的引用�?- **WeakMap**:对键所引用的对象是弱引用。若对象没有其他强引用,垃圾回收时对象会被回收,WeakMap 里对应的键值对也会自动移除�?
    *可遍历�?

  • **Map**:是可迭代的,能使用 for...of 循环、forEach 方法等遍历其键值对�?- **WeakMap**:不可迭代,没有 keys()values()entries() 这些迭代方法,也不能�?for...of �?forEach 遍历�?
    *方法和属�?

  • **Map**:有 size 属性来获取键值对数量,还�?set()get()has()delete()clear() 等方法�?- **WeakMap**:只�?set()get()has()delete() 方法,没�?size 属性和 clear() 方法�?
    使用场景

  • **Map**:适用于需存储任意类型键值对,且要对这些键值对进行遍历和操作的场景,如缓存数据�?- **WeakMap**:常用于避免内存泄漏的场景,例如给对象添加私有数据,当对象被销毁时,WeakMap 里相关数据也会被清理�?
    :::

如何�?var [a, b] = {a: 1, b: 2} 解构赋值成功?

参考答�?
::: details

迭代协议�?题目问怎么能让var [a,b] = {a:1,b:2} 成立,那么我们首先要运行一下,看看它是怎么个不成立法。�?

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {�?    a:'1',�?    b:'2',�?}�?�?const [a,b] = obj�?```

运行之后打开控制台可以发现报错信息,它告诉我们obj这个对象是不可迭代的,那么我们想办法把obj变成可迭代的是不是就能解决这个问题,这要怎么做呢?想要搞明白这点我们需要先了解一下可迭代协议。�?�?可迭代协议的概念�?MDN )�?`可迭代协议允许`JavaScript`对象定义或定制它们的迭代行为,例如,在一�?for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array 或�?Map,而其他内置类型则不是(比�?Object)。�? 要成为可迭代对象,该对象必须实现 @@iterator 方法,这意味着对象(或者它原型链上的某个对象)必须有一个键�?@@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:�? [Symbol.iterator]�? 一个无参数的函数,其返回值为一个符合迭代器协议的对象。�? 当一个对象需要被迭代的时候(比如被置入一�?for...of 循环时),首先,会不带参数调用它�?@@iterator 方法,然后使用此方法返回的迭代器获得要迭代的值。​`
说人话就是,要想让obj成为一个可迭代的对象,就需要它实现 @@iterator 方法,具体表现为对象身上要有一个名为[Symbol.iterator] 的方法。而数组和Map则是一开始就有这个方法,所以它们是可迭代的。而对象身上则没有这个默认行为,所以不可迭代。真的是这样吗?我们创建一个数组,看看数组身上到底有没有[Symbol.iterator] 方法。�?
```JavaScript
const array = [1,2,3]�?console.log(array)�?```

点开原型查看

![proto](../imgs/image-iterator.png)
​发现真的有一个Symbol.iterator()方法,该方法会返回一个迭代器对象。我们来调用一�?
```JavaScript
const array = [1,2,3]�?const iterator = array[Symbol.iterator]()�?console.log(iterator)�?console.log(iterator.next())�?console.log(iterator.next())�?console.log(iterator.next())�?console.log(iterator.next())

打印iterator对象后发现在它的原型上有一个next()方法,调用next()方法,会得到一个对象value就是当前迭代的值,done则代表当前迭代器是否已经迭代完成�?
数组 解构 的本�?

1
const array = [1,2,3]�?var [a,b,c] = array�?// 本质上是�?const iterator = array[Symbol.iterator]()�?var a = iterator.next().value�?var b = iterator.next().value�?var c = iterator.next().value

解决方法�?到此为止我们可知,要想满足迭代协议需要对象身上有一个名为[Symbol.iterator]的方法。再使用for..of或者解构赋值的时候会隐式的调用这个方法,得到一个迭代对象,通过迭代对象的next方法判断当前是否完成迭代和具体迭代的值。�?也就是说我们要在obj上添加[Symbol.iterator]方法并且完成next方法的逻辑�?
最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {�?    a: '1',�?    b: '2',�?    [Symbol.iterator]() {
let index = 0
const keys = Object.keys(this)
return {
next() {
return {
value: obj[keys[index]],
done: index++ >= keys.length
}
}
}
}
}�?�?const [a, b] = obj

当然,我们也可以用for…of去循环遍历这个对象,我看谁再说for…of不能遍历对象(doge)

1
2
for(let i of obj){�?    console.log(i)�?}�?// 1�?// 2

:::

postMessage 有哪些使用场景?

参考答�?
::: details

window.postMessage 定义

window.postMessage()方法可以安全地实现跨源通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全

*用�?

可用于两个不同的Ifrom(不同源�?之间的通讯�?
语法

1
otherWindow.postMessage(message, targetOrigin, [transfer]);

参数说明

子框架传递信�?

1
<script>�?�?// 子框架向父框架发送信息�?�?function goParentIfromPostMessage(msg,parentUrl){�?�?    var parentUrl = window.parent.location.origin;�?�?        window.onload=function(){�?�?        window.parent.postMessage(msg,parentUrl);�?�?        }�?    }�? }�? �?    goParentIfromPostMessage('msgStr',parentIfromUrl)�?�?</script>

父框架接收端

1
<script>�?�?        window.addEventListener('message',function(e){�?�?            console.log(e.origin,e.data);�?�?            console.log(e.data);�?�?        })�?�?</script>

这样即可以实现简单的框架跨域通信,但是会有一些安全问题�?
安全问题

如果您不希望从其他网站接收message,请不要为message事件添加任何事件侦听器�?这是一个完全万无一失的方式来避免安全问题。�?如果您确实希望从其他网站接收message,请始终使用origin和source属性验证发件人的身份�?任何窗口(包括例�?<http://evil.example.com)都可以向任何其他窗口发送消息,并且您不能保证未知发件人不会发送恶意消息�? 但是,验证身份后,您仍然应该始终验证接收到的消息的语法�?否则,您信任只发送受信任邮件的网站中的安全漏洞可能会在您的网站中打开跨网站脚本漏洞。�?

  • 当您使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是。恶意网站可以在您不知情的情况下更改窗口的位置,因此它可以拦截使用postMessage发送的数据�?
    示例
1
/*�? * A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:�? */�?�?var popup = window.open(...popup details...);�?�?// 如果弹出框没有被阻止且加载完成�?�?// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)�?popup.postMessage("The user is 'bob' and the password is 'secret'",�?                  "https://secure.example.net");�?�?// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)�?popup.postMessage("hello there!", "http://example.org");�?�?function receiveMessage(event)�?{�?  // 我们能相信信息的发送者吗?  (也许这个发送者和我们最初打开的不是同一个页�?.�?  if (event.origin !== "http://example.org")�?    return;�?�?  // event.source 是我们通过window.open打开的弹出页�?popup�?  // event.data �?popup发送给当前页面的消�?"hi there yourself!  the secret response is: rheeeeet!"�?}�?window.addEventListener("message", receiveMessage, false);
1
/*�? * 弹出�?popup 域名�?http://example.org>,以下是script标签中的代码:�? */�?�?//当A页面postMessage被调用后,这个function被addEventListener调用�?function receiveMessage(event)�?{�?  // 我们能信任信息来源吗?�?  if (event.origin !== "http://example.com:8080")�?    return;�?�?  // event.source 就当前弹出页的来源页面�?  // event.data �?"hello there!"�?�?  // 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把event.source�?  // 作为回信的对象,并且把event.origin作为targetOrigin�?  event.source.postMessage("hi there yourself!  the secret response " +�?                           "is: rheeeeet!",�?                           event.origin);�?}�?�?window.addEventListener("message", receiveMessage, false)

:::

Typescript 面试�?

Typescript 已经全面普及,尤其大厂大型项目,前端熟悉 Typescript 是标配�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

TS 优缺点,使用场景

参考答�?
::: details

优点

  • 静态类型,减少类型错误

  • 有错误会在编译时提醒,而非运行时报�?—�?解释“编译时”和“运行时�?- 智能提示,提高开发效�?
    缺点

  • 学习成本�?- 某些场景下,类型定义会过于混乱,可读性不好,如下代码

  • 使用不当会变�?anyscript

1
2
3
4
type ModelFieldResolver<T, TKey extends keyof T = any> = (
this: T,
...params: T[TKey] extends (...args: any) => any ? Parameters<T[TKey]> : never
) => T[TKey]

适用场景

  • 大型项目,业务复杂,维护人员�?- 逻辑性比较强的代码,依赖类型更多
  • 组内要有一个熟�?TS 的架构人员,负责代码规范和质�?
    :::

PS. 虽然 TS 有很多问题,网上也有很多“弃�?TS”的说法,但目前 TS 仍然是最优解,而且各大前端框架都默认使�?TS �?

TS 基础类型有哪�?

参考答�?
::: details

  • boolean
  • number
  • string
  • symbol
  • bigint
  • Enum 枚举
  • Array 数组
  • Tuple 元祖
  • Object 对象
  • undefined
  • null
  • any void never unknown

:::

参考资�?
::: details

:::

数组 Array 和元�?Tuple 的区别是什�?

参考答�?
::: details

数组元素只能有一种类型,元祖元素可以有多种类型�?

1
2
3
4
5
// 数组,两种定义方�?const list1: number[] = [1, 2, 3]
const list2: Array<string> = ['a', 'b', 'c']

// 元组
let x: [string, number] = ['x', 10]

:::

枚举 enum 是什么?有什么使用场景?

JS 中没�?enum 枚举,只学过 JS 你可能不知道 enum 。其实在 Java �?C# 等高级语言中早就有了,TS 中也有�?
参考答�?
::: details

enum 枚举,一般用于表示有限的一些选项,例如使�?enum 定义 4 个方�?

1
2
3
4
5
6
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}

其他代码中,我们可以获取某一个方向,用于展示或存储。这样代码更具有可读性和维护行�?

1
const d = Direction.Up

:::

参考资�?
::: details

:::

keyof �?typeof 有什么区别?

参考答�?
::: details

typeof �?JS 基础用法,用于获取类型,这个很简单�?
keyof �?TS 语法,用于获取所�?key 的类型,例如

1
2
3
4
5
6
7
8
interface Person {
name: string
age: number
location: string
}

type PersonType = keyof Person
// 等价�?type PersonType = 'name' | 'age' | 'location'

可以把代码拷贝到这里来练�?https://www.tslang.cn/play/index.html

:::

参考资�?
::: details

:::

any void never unknown 有什么区�?

参考答�?
::: details

主要区别�?

  • any 任意类型(不进行类型检查)
  • void 没有任何类型,和 any 相反
  • never 永不存在的值的类型
  • unknown 未知类型(一个更安全�?any�?
    代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function fn(): void {} // void 一般定义函数返回�?
// 返回 never 的函数,必须存在无法达到的终�?function error(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}

// unknown 比直接使�?any 更安�?const a: any = 'abc'
console.log(a.toUpperCase()) // 不会报错,但不安�?
const b: unknown = 'abc'
// console.log( b.toUpperCase() ) // 会报错!!!
console.log((b as string).toUpperCase()) // 使用 as 转换类型,意思是告诉 TS 编译器:“我知道 b 的类型,我对安全负责�?```

PS:但现在 unknown 用的�?any 少很多,因为麻烦

:::

## unknown �?any 区别

参考答�?
::: details

`unknown` 是更安全�?`any` ,如下代�?
```js
const a: any = 'x'
a.toString() // 不报�?
const b: unknown = 'y'
// b.toString() // 报错
;(b as string).toString() // 不报�?```

:::

## TS 访问修饰�?public protected private 有什么作�?
参考答�?
::: details

- public 公开的,谁都能用 (默认)
- protected 受保护的,只有自己和子类可以访问
- private 私有的,仅自己可以访�?
这些规则很难用语法去具体描述,看代码示例

```ts
class Person {
name: string = ''
protected age: number = 0
private girlfriend = '小丽'

// public protected private 也可以修饰方法、getter �?
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}

class Employee extends Person {
constructor(name: string, age: number) {
super(name, age)
}

getInfo() {
console.log(this.name)
console.log(this.age)
// console.log(this.girlfriend) // 这里会报错,private 属性不能在子类中访�? }
}

const zhangsan = new Employee('张三', 20)
console.log(zhangsan.name)
// console.log(zhangsan.age) // 这里会报错,protected 属性不能在子类对象中访问,只能在子类中访问

:::

追问:# �?private 有什么区别呢�?
::: details

# �?TS 中可定义私有属�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
#salary: number
constructor(
private name: string,
salary: number
) {
this.#salary = salary
}
}

const p = new Person('xxx', 5000)
// const n = p.name // 报错
const n = (p as any).name // 可以通过“投机取巧”获取到
console.log('name', n)

// const s = p.#salary // 报错
// const s = (p as any).#salary // 报错

区别�?

  • # 属性,不能在参数中定义
  • private 属性,可通过 as any 强制获取
  • # 属性,更私�?
    :::

type �?interface 共同和区别,如何选择

type �?interface 有很多相同之处,很多人因此而产生“选择困难症”,这也�?TS 热议的话题�?
共同�?
::: details

  • 都能描述一个对象结�?- 都能�?class 实现
  • 都能被扩�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 接口
    interface User {
    name: string
    age: number
    getName: () => string
    }

    // 自定义类�?type UserType = {
    name: string
    age: number
    getName: () => string
    }

    // class UserClass implements User {
    class UserClass implements UserType {
    name = 'x'
    age = 20
    getName() {
    return this.name
    }
    }

:::

区别

::: details

  • type 可以声明基础类型
  • type 有联合类型和交差类型
  • type 可以�?typeof 赋�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // type 基础类型
    type name = string
    type list = Array<string>

    // type 联合类型
    type info = string | number

    type T1 = { name: string }
    type T2 = { age: number }
    // interface T2 { age: number } // 联合,还可以�?interface ,乱�?..
    type T3 = T1 | T2
    const a: T3 = { name: 'x' }
    type T4 = T1 & T2
    const b: T4 = { age: 20, name: 'x' }

    // typeof 获取
    type T5 = typeof b

    //【补充】还有个 keyof ,它�?typeof 完全不同,它是获�?key 类型�?type K1 = keyof T5
    const k: K1 = 'name'

:::

如何选择�?
::: details

根据社区的使用习惯,推荐使用方式

  • 能用 interface 就尽量用 interface
  • 除非必须�?type 的时候才�?type

:::

参考资�?
::: details

:::

PS. 其实你混�?type �?interface 不是你的问题,这�?TS 设计的问题,或者说 TS 设计初衷和后来演变带来的副作用�?

什么是泛型,如何使用它�?

只学�?JS 的同学不知道泛型,其实它早就�?C# �?Java 中的重要概念了。初学泛型可能会比较迷惑,需要多些代码多练习�?
泛型的定�?
::: details

泛型 Generics 即通用类型,可以灵活的定义类型而无需写死�?

1
2
3
4
5
6
7
8
const list: Array<string> = ['a', 'b']
const numbers: Array<number> = [10, 20]

interface User {
name: string
age: number
}
const userList: Array<User> = [{ name: 'x', age: 20 }]

:::

泛型的使�?
::: details

  1. 用于函数
1
2
3
4
5
6
7
8
9
10
11
// Type 一般可简写为 T
function fn<Type>(arg: Type): Type {
return arg
}
const x1 = fn<string>('xxx')

// 可以有多个泛型,名称自己定义
function fn<T, K>(a: T, b: K) {
console.log(a, b)
}
fn<string, number>('x', 10)
  1. 用于 class
1
2
3
4
5
6
7
8
9
10
class SomeClass<T> {
name: T
constructor(name: T) {
this.name = name
}
getName(): T {
return this.name
}
}
const s1 = new SomeClass<String>('xx')
  1. 用于 type
1
2
3
4
5
function fn<T>(arg: T): T {
return arg
}

const myFn: <U>(arg: U) => U = fn // U T 随便定义
  1. 用于 interface
1
2
3
4
5
6
7
8
9
10
// interface F1 {
// <T>(arg: T): T;
// }
interface F1<T> {
(arg: T): T
}
function fn<T>(arg: T): T {
return arg
}
const myFn: F1<number> = fn

:::

参考资�?
::: details

:::

什么是交叉类型和联合类�?

交叉类型 T1 & T2

交叉类型是将多个类型合并为一个类型,包含了所需的所有类型的特性。例�?T1 & T2 & T3

代码示例

::: details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface U1 {
name: string
city: string
}
interface U2 {
name: string
age: number
}
type UserType1 = U1 & U2
const userA: UserType1 = { name: 'x', age: 20, city: 'beijing' }

// 可在 userA 获取所有属性,相当于“并集�?userA.name
userA.age
userA.city

:::

注意事项

::: details

  1. 两个类型的相同属性,如果类型不同(冲突了),则该属性是 never 类型
1
2
3
// 如上代码
// U1 name:string ,U2 name: number
// �?UserType1 name �?never
  1. 基础类型没办法交叉,会返�?never
1
type T = string & number // never

:::

参考资�?
::: details

:::

联合类型 T1 | T2

一种“或”的关系。格式如 T1 | T2 | T3。代码示例如�?
::: details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface U1 {
name: string
city: string
}
interface U2 {
name: string
age: number
}

function fn(): U1 | U2 {
return {
name: 'x',
age: 20,
}
}

:::

注意事项

::: details

基础类型可以联合

1
2
3
type T = string | number
const a: T = 'x'
const b: T = 100

但如果未赋值的情况下,联合类型无法使用 string �?number 的方�?

1
2
3
function fn(x: string | number) {
console.log(x.length) // 报错
}

:::

参考资�?
::: details

:::

是否用过工具类型

TS 工具类型�?Partial Required Omit ReadOnly 等,熟练使用 TS 的人都会熟悉这些工具类型�?
参考答�?
::: details

Partial<T> 属性设置为可�?

1
2
3
4
5
6
interface User {
name: string
age: number
}
type User1 = Partial<User> // 属性全部可选,类似 `?`
const u: User1 = {}

Require<T> 属性设置为必�?(和 Partial 相反�?
Pick<T, K> 挑选部分属�?

1
2
3
4
5
6
interface User {
name: string
age: number
city: string
}
type User1 = Pick<User, 'name' | 'age'> // 只选择两个属�?const u: User1 = { name: 'x', age: 20 }

Omit<T, K> 剔除部分属性(�?Pick 相反�?
ReadOnly<T> 属性设置为只读

相当于为每个属性都设置一�?readonly

1
2
3
4
5
6
7
interface User {
name: string
age: number
}
type User1 = Readonly<User>
const u: User1 = { name: 'x', age: 20 }
// u.name = 'y' // 报错

:::

TS 这些符号 ? ?. ?? ! _ & | # 分别什么意�?

参考答�?
::: details

? 可选属性,可选参�?

1
2
3
4
5
6
7
8
9
interface User {
name: string
age?: number
}
const u: User = { name: 'xx' } // age 可写 可不�?
function fn(a: number, b?: number) {
console.log(a, b)
}
fn(10) // 第二个参数可不传

?. 可选链:有则获取,没有则返�?undefined ,但不报错�?

1
2
3
4
5
6
7
8
const user: any = {
info: {
city: '北京',
},
}
// const c = user && user.info && user.info.city
const c = user?.info?.city
console.log(c)

?? 空值合并运算符:当左侧的操作数�?null 或�?undefined 时,返回其右侧操作数,否则返回左侧操作数�?

1
2
3
4
5
6
7
8
9
10
11
const user: any = {
// name: '张三'
index: 0,
}
// const n1 = user.name ?? '暂无姓名'
const n2 = user.name || '暂无姓名' // 某些情况可用 || 代替
console.log('name', n2)

const i1 = user.index ?? '暂无 index'
const i2 = user.index || '暂无 index' // 当是 0 (或 false 空字符串等)时,就不能直接用 || 代替
console.log('index', i1)

! 非空断言操作符:忽略 undefined null ,自己把控风�?

1
2
3
function fn(a?: string) {
return a!.length // �?! 表示忽略 undefined 情况
}

_ 数字分隔符:分割数字,增加可读�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const million = 1_000_000
const phone = 173_1777_7777

// 编译�?js 就是普通数�?```

其他的本文都有讲�?
- `&` 交叉类型
- `|` 联合类型
- `#` 私有属�?
:::

## 什么是抽象�?abstract class

抽象类是 C# �?Java 的常见语法,TS 也有,但日常前端开发使用并不多�?
参考答�?
::: details

抽象类,不能直接被实例化,必须派生一个子类才能使用�?
```ts
abstract class Animal {
abstract makeSound(): void
move(): void {
console.log('roaming the earch...')
}
}

// const a = new Animal() // 直接实例化,报错

class Dog extends Animal {
// 必须要实�?Animal 中的抽象方法,否则报�? makeSound() {
console.log('wang wang')
}
}

const d = new Dog()
d.makeSound()
d.move()

:::

参考资�?
::: details

:::

如何扩展 window 属性,如何定义第三方模块的类型

参考答�?
::: details

1
2
3
4
5
6
declare interface Window {
test: string
}

window.test = 'aa'
console.log(window.test)

:::

是否有过真实�?Typescript 开发经验,讲一下你的使用体�?

开放性问题,需要结合你实际开发经验来总结。可以从以下几个方面考虑

::: details

  • �?Vue React 或其他框架使用时遇到的障碍?
  • 在打包构建时,有没有遇到 TS 语法问题而打包失败?
  • 有没有用很多 any ?如何避�?any 泛滥�?
    :::

参考资�?
::: details

:::

面试官:说说你对Git的理解?

一、是什�?

git,是一个分布式版本控制软件,最初目的是为更好地管理Linux内核开发而设�?
分布式版本控制系统的客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复

项目开始,只有一个原始版仓库,别的机器可以clone这个原始版本库,那么所有clone的机器,它们的版本库其实都是一样的,并没有主次之分

所以在实现团队协作的时候,只要有一台电脑充当服务器的角色,其他每个人都从这个“服务器”仓库clone一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交

github实际就可以充当这个服务器角色,其是一个开源协作社区,提供Git仓库托管服务,既可以让别人参与你的开源项目,也可以参与别人的开源项�?

二、工作原�?

当我们通过git init创建或者git clone一个项目的时候,项目目录会隐藏一个.git子目录,其作用是用来跟踪管理版本库的

Git 中所有数据在存储前都计算校验和,然后以校验和来引用,所以在我们修改或者删除文件的时候,git能够知道

Git 用以计算校验和的机制叫做 SHA-1 散列(hash,哈希)�?这是一个由 40 个十六进制字符(0-9 �?a-f)组成字符串,基�?Git 中文件的内容或目录结构计算出来,如下�?

1
24b9da6552252987aa493b52f8696cd6d3b00373

当我们修改文件的时候,git就会修改文件的状态,可以通过git status进行查询,状态情况如下:

  • 已修改(modified):表示修改了文件,但还没保存到数据库中�?- 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中�?- 已提交(committed):表示数据已经安全的保存在本地数据库中�?
    文件状态对应的,不同状态的文件在Git中处于不同的工作区域,主要分成了四部分:

  • 工作区:相当于本地写代码的区域,�?git clone 一个项目到本地,相当于本地克隆了远程仓库项目的一个副�?- 暂存区:暂存区是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录�?- 本地仓库:提交更新,找到暂存区域的文件,将快照永久性存储到 Git 本地仓库

  • 远程仓库:远程的仓库,如 github

三、命�?

从上图可以看到,git日常简单的使用就只有上�?个命令:

  • add
  • commit
  • push
  • pull
  • clone
  • checkout

但实际上还有很多命令,如果想要熟练使用,还有60个多命令,通过这些命令的配合使用,能够提高个人工作效率和团队协助能�?

参考文�?

面试官:说说Git �?HEAD、工作树和索引之间的区别�?

一、HEAD

git中,可以存在很多分支,其本质上是一个指向commit对象的可变指针,而Head是一个特别的指针,是一个指向你正在工作中的本地分支的指�?
简单来讲,就是你现在在哪儿,HEAD 就指向哪�?
例如当前我们处于master分支,所以HEAD这个指针指向了master分支指针

然后通过调用git checkout test切换到test分支,那么HEAD则指向test分支,如下图�?

但我们在test分支再一次commit信息的时候,HEAD指针仍然指向了test分支指针,而test分支指针已经指向了最新创建的提交,如下图�?

这个HEAD存储的位置就在.git/HEAD目录中,查看信息可以看到HEAD指向了另一个文�?

1
2
3
4
5
$ cat .git/HEAD
ref: refs/heads/master

$ cat .git/refs/heads/master
7406a10efcc169bbab17827aeda189aa20376f7f

这个文件的内容是一串哈希码,而这个哈希码正是master分支上最新的提交所对应的哈希码

所以,当我们切换分支的时候,HEAD指针通常指向我们所在的分支,当我们在某个分支上创建新的提交时,分支指针总是会指向当前分支的最新提�?
所以,HEAD指针 ——�? 分支指针 ——�? 最新提�?

二、工作树和索�?

Git管理下,大家实际操作的目录被称为工作树,也就是工作区�?
在数据库和工作树之间有索引,索引是为了向数据库提交作准备的区域,也被称为暂存区域

Git在执行提交的时候,不是直接将工作树的状态保存到数据库,而是将设置在中间索引区域的状态保存到数据�?
因此,要提交文件,首先需要把文件加入到索引区域中�?
所以,凭借中间的索引,可以避免工作树中不必要的文件提交,还可以将文件修改内容的一部分加入索引区域并提�?

三、区�?

从所在的位置来看�?

  • HEAD 指针通常指向我们所在的分支,当我们在某个分支上创建新的提交时,分支指针总是会指向当前分支的最新提�?

  • 工作树是查看和编辑的(源)文件的实际内容

  • 索引是放置你想要提交�?git仓库文件的地方,如工作树的代码通过 git add 则添加到 git 索引中,通过git commit 则将索引区域的文件提交到 git 仓库�?

参考文�?

面试官:说说你对版本管理的理解?常用的版本管理工具有哪些�?

一、是什�?

版本控制(Version control),是维护工程蓝图的标准作法,能追踪工程蓝图从诞生一直到定案的过程。此外,版本控制也是一种软件工程技巧,借此能在软件开发的过程中,确保由不同人所编辑的同一程序文件都得到同�?
透过文档控制,能记录任何工程项目内各个模块的改动历程,并为每次改动编上序�?
一种简单的版本控制形式如下:赋给图的初版一个版本等级“A”。当做了第一次改变后,版本等级改为“B”,以此类推

版本控制能提供项目的设计者,将设计恢复到之前任一状态的选择�?
简言之,你的修改只要提到到版本控制系统,基本都可以找回,版本控制系统就像一台时光机器,可以让你回到任何一个时间点

二、有哪些

版本控制系统在当今的软件开发中,被认为是理所当然的配备工具之一,根据类别可以分成:

  • 本地版本控制系统
  • 集中式版本控制系�?- 分布式版本控制系�?

本地版本控制系统

结构如下图所示:

优点�?

  • 简单,很多系统中都有内�?- 适合管理文本,如系统配置

缺点�?

  • 其不支持远程操作,因此并不适合多人版本开�?

集中式版本控制系�?

结构如下图所示:

优点�?

  • 适合多人团队协作开�?- 代码集中化管�?
    缺点�?
  • 单点故障
  • 必须联网,无法单机工�?

代表工具有SVNCVS�?

SVN

TortoiseSVN是一款非常易于使用的跨平台的 版本控制/版本控制/源代码控制软�?

CVS

CVS是版本控制系统,是源配置管理(SCM)的重要组成部分。使用它,您可以记录源文件和文档的历史记�?
老牌的版本控制系统,它是基于客户�?服务器的行为使得其可容纳多用户,构成网络也很方便

这一特性使得CVS成为位于不同地点的人同时处理数据文件(特别是程序的源代码)时的首�?

分布式版本控制系�?

结构如下图:

优点�?

  • 适合多人团队协作开�?- 代码集中化管�?- 可以离线工作
  • 每个计算机都是一个完整仓�?
    分布式版本管理系统每个计算机都有一个完整的仓库,可本地提交,可以做到离线工作,则不用像集中管理那样因为断网情况而无法工�?

代表工具为GitHG�?

Git

Git是目前世界上最先进的分布式版本控制系统,旨在快速高效地处理从小型到大型项目的所有事�?
特性:易于学习,占用内存小,具有闪电般快速的性能

使用GitGitlab搭建版本控制环境是现在互联网公司最流行的版本控制方�?

HG

Mercurial是一个免费的分布式源代码管理工具。它可以有效地处理任何规模的项目,并提供简单直观的界面

Mercurial 是一种轻量级分布式版本控制系统,采用 Python 语言实现,易于学习和使用,扩展性强

三、总结

版本控制系统的优点如下:

  • 记录文件所有历史变化,这是版本控制系统的基本能�?- 随时恢复到任意时间点,历史记录功能使我们不怕改错代码了
  • 支持多功能并行开发,通常版本控制系统都支持分支,保证了并行开发的可行
  • 多人协作并行开发,对于多人协作项目,支持多人协作开发的版本管理将事半功�?

参考文�?

面试官:说说Git常用的命令有哪些�?

一、前言

git 的操作可以通过命令的形式如执行,日常使用就如下�?个命令即�?

实际上,如果想要熟练使用,超�?0多个命令需要了解,下面则介绍下常见的的git 命令

二、有哪些

配置

Git 自带一�?git config 的工具来帮助设置控制 Git 外观和行为的配置变量,在我们安装完git之后,第一件事就是设置你的用户名和邮件地址

后续每一个提交都会使用这些信息,它们会写入到你的每一次提交中,不可更�?
设置提交代码时的用户信息命令如下�?

  • git config [–global] user.name “[name]”
  • git config [–global] user.email “[email address]”

启动

一个git项目的初始有两个途径,分别是�?

  • git init [project-name]:创建或在当前目录初始化一个git代码�?- git clone url:下载一个项目和它的整个代码历史

日常基本操作

在日常工作中,代码常用的基本操作如下�?

  • git init 初始化仓库,默认�?master 分支
  • git add . 提交全部文件修改到缓存区
  • git add <具体某个文件路径+全名> 提交某些文件到缓存区
  • git diff 查看当前代码 add后,�?add 哪些内容
  • git diff –staged查看现在 commit 提交后,会提交哪些内�?- git status 查看当前分支状�?- git pull <远程仓库�? <远程分支�? 拉取远程仓库的分支与本地当前分支合并
  • git pull <远程仓库�? <远程分支�?:<本地分支�? 拉取远程仓库的分支与本地某个分支合并
  • git commit -m “<注释>” 提交代码到本地仓库,并写提交注释
  • git commit -v 提交时显示所有diff信息
  • git commit –amend [file1] [file2] 重做上一次commit,并包括指定文件的新变化

关于提交信息的格式,可以遵循以下的规则:

  • feat: 新特性,添加功能
  • fix: 修改 bug
  • refactor: 代码重构
  • docs: 文档修改
  • style: 代码格式修改, 注意不是 css 修改
  • test: 测试用例修改
  • chore: 其他修改, 比如构建流程, 依赖管理

分支操作

  • git branch 查看本地所有分�?- git branch -r 查看远程所有分�?- git branch -a 查看本地和远程所有分�?- git merge <分支�? 合并分支
  • git merge –abort 合并分支出现冲突时,取消合并,一切回到合并前的状�?- git branch <新分支名> 基于当前分支,新建一个分�?- git checkout –orphan <新分支名> 新建一个空分支(会保留之前分支的所有文件)
  • git branch -D <分支�? 删除本地某个分支
  • git push <远程库名> :<分支�? 删除远程某个分支
  • git branch <新分支名�? <提交ID> 从提交历史恢复某个删掉的某个分支
  • git branch -m <原分支名> <新分支名> 分支更名
  • git checkout <分支�? 切换到本地某个分�?- git checkout <远程库名>/<分支�? 切换到线上某个分�?- git checkout -b <新分支名> 把基于当前分支新建分支,并切换为这个分支

远程同步

远程操作常见的命令:

  • git fetch [remote] 下载远程仓库的所有变�?- git remote -v 显示所有远程仓�?- git pull [remote] [branch] 拉取远程仓库的分支与本地当前分支合并
  • git fetch 获取线上最新版信息记录,不合并
  • git push [remote] [branch] 上传本地指定分支到远程仓�?- git push [remote] –force 强行推送当前分支到远程仓库,即使有冲突
  • git push [remote] –all 推送所有分支到远程仓库

撤销

  • git checkout [file] 恢复暂存区的指定文件到工作区

  • git checkout [commit] [file] 恢复某个commit的指定文件到暂存区和工作�?- git checkout . 恢复暂存区的所有文件到工作�?- git reset [commit] 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不�?- git reset –hard 重置暂存区与工作区,与上一次commit保持一�?- git reset [file] 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变

  • git revert [commit] 后者的所有变化都将被前者抵消,并且应用到当前分�?

    reset:真实硬性回滚,目标版本后面的提交记录全部丢失了

    revert:同样回滚,这个回滚操作相当于一个提价,目标版本后面的提交记录也全部都有

存储操作

你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作,但又不想提交这些杂乱的代码,这时候可以将代码进行存储

  • git stash 暂时将未提交的变化移�?- git stash pop 取出储藏中最后存入的工作状态进行恢复,会删除储�?
  • git stash list 查看所有储藏中的工�?- git stash apply <储藏的名�? 取出储藏中对应的工作状态进行恢复,不会删除储藏
  • git stash clear 清空所有储藏中的工�?- git stash drop <储藏的名�? 删除对应的某个储�?

三、总结

git常用命令速查表如下所示:

参考文�?

面试官:说说 git 发生冲突的场景?如何解决�?

一、是什�?

一般情况下,出现分支的场景有如下:

  • 多个分支代码合并到一个分支时
  • 多个分支向同一个远端分支推�?
    具体情况就是,多个分支修改了同一个文件(任何地方)或者多个分支修改了同一个文件的名称

如果两个分支中分别修改了不同文件中的部分,是不会产生冲突,直接合并即�?
应用在命令中,就是pushpullstashrebase等命令下都有可能产生冲突情况,从本质上来讲,都是mergepatch(应用补丁)时产生冲�?

二、分�?

在本地主分值master创建一个a.txt文件,文件起始位置写上master commit,如下:

然后提交到仓库:

  • git add a.txt
  • git commit -m ‘master first commit’

创建一个新的分支featurel1分支,并进行切换,如下:

1
git checkout -b featurel1

然后修改a.txt文件首行文字�?featurel commit,然后添加到暂存区,并开始进行提交到仓库�?

  • git add a.txt
  • git commit -m ‘featurel first change’

然后通过git checkout master切换到主分支,通过git merge进行合并,发现不会冲�?
此时a.txt文件的内容变成featurel commit,没有出现冲突情况,这是因为git在内部发生了快速合�?

如果当前分支的每一个提�?commit)都已经存在另一个分支里了,git 就会执行一个“快速向前�?fast forward)操作

git 不创建任何新的提�?commit),只是将当前分支指向合并进来的分�?
如果此时切换到featurel分支,将文件的内容修改成featrue second commit,然后提交到本地仓库

然后切换到主分支,如果此时在a.txt文件再次修改,修改成mastet second commit,然后再次提交到本地仓库

此时,master分支和feature1分支各自都分别有新的提交,变成了下图所示:

这种情况下,无法执行快速合并,只能试图把各自的修改合并起来,但这种合并就可能会有冲�?
现在通过git merge featurel进行分支合并,如下所示:

从冲突信息可以看到,a.txt发生冲突,必须手动解决冲突之后再提交

git status同样可以告知我们冲突的文件:

打开a.txt文件,可以看到如下内容:

git<<<<<<<=======>>>>>>>标记出不同分支的内容�?

  • <<<<<<< �?======= 之间的区域就是当前更改的内容
  • ======= �?>>>>>>> 之间的区域就是传入进来更改的内容

现在要做的事情就是将冲突的内容进行更改,对每个文件使�?git add 命令来将其标记为冲突已解决�?一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决然后再提交�?

  • git add a.txt
  • git commit -m “conflict fixed”

此时master分支和feature1分支变成了下图所示:

使用git log命令可以看到合并的信息:

三、总结

Git无法自动合并分支时,就必须首先解决冲突,解决冲突后,再提交,合并完成

解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提�?

参考文�?

面试官:说说Git�?fork, clone,branch这三个概念,有什么区�?

一、是什�?

fork

fork,英语翻译过来就是叉子,动词形式则是分叉,如下图,从左到右,一条直线变成多条直�?

转到git仓库中,fork则可以代表分叉、克�?出一个(仓库的)新拷�?

包含了原来的仓库(即upstream repository,上游仓库)所有内容,如分支、Tag、提�?
如果想将你的修改合并到原项目中时,可以通过�?Pull Request 把你的提交贡献回 原仓�?

clone

clone,译为克隆,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓

执行clone命令后,会在当前目录下创建一个名为xxx的目录,并在这个目录下初始化一�?.git 文件夹,然后从中读取最新版本的文件的拷�?
默认配置下远�?Git 仓库中的每一个文件的每一个版本都将被拉取下来

branch

branch,译为分支,其作用简单而言就是开启另一个分支, 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主�?
Git 处理分支的方式十分轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便�?
在我们开发中,默认只有一条master分支,如下图所示:

通过git branch 可以创建一个分支,但并不会自动切换到新分支中去

通过git checkout可以切换到另一个testing分支

二、如何使�?

fork

当你在github发现感兴趣开源项目的时候,可以通过点击github仓库中右上角fork标识的按钮,如下图:

点击这个操作后会将这个仓库的文件、提交历史、issues和其余东西的仓库复制到自己的github仓库中,而你本地仓库是不会存在任何更�?
然后你就可以通过git clone对你这个复制的远程仓库进行克�?
后续更改任何东西都可以在本地完成,如git addgit commit一系列的操作,然后通过push命令推到自己的远程仓�?
如果希望对方接受你的修改,可以通过发送pull requests给对方,如果对方接受。则会将你的修改内容更新到仓库中

整体流程如下图:

clone

github中,开源项目右侧存在code按钮,点击后则会显示开源项目url信息,如下图所示:

通过git clone xxx则能完成远程项目的下�?

branch

可通过git branch进行查看当前的分支状态,

如果给了--list,或者没有非选项参数,现有的分支将被列出;当前的分支将以绿色突出显示,并标有星号

以及通过git branch创建一个新的分支出�?

三、区�?

其三者区别如下:

  • fork 只能对代码仓进行操作,且 fork 不属�?git 的命令,通常用于代码仓托管平台的一种“操作�?- clone �?git 的一种命令,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓
  • branch 特征�?fork 很类似,fork 得到的是一个新的、自己的代码仓,�?branch 得到的是一个代码仓的一个新分支

参考文�?

说说对git pull �?git fetch 的理解?有什么区别?

一、是什�?先回顾两个命令的定义

  • git fetch 命令用于从另一个存储库下载对象和引�?- git pull 命令用于从另一个存储库或本地分支获取并集成(整合)

再来看一次git的工作流程图,如下所示:

可以看到,git fetch是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中

git pull 则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge,这样可能会产生冲突,需要手动解�?
在我们本地的git文件中对应也存储了git本地仓库分支的commit ID �?跟踪的远程分支的commit ID,对应文件如下:

  • .git/refs/head/[本地分支]
  • .git/refs/remotes/[正在跟踪的分支]

使用 git fetch更新代码,本地的库中mastercommitID不变

但是与git上面关联的那个orign/mastercommit ID发生改变

这时候我们本地相当于存储了两个代码的版本号,我们还要通过merge去合并这两个不同的代码版�?

也就是fetch的时候本地的master没有变化,但是与远程仓关联的那个版本号被更新了,接下来就是在本地merge合并这两个版本号的代�?
相比之下,使用git pull就更加简单粗暴,会将本地的代码更新至远程仓库里面最新的代码版本,如下图�?

二、用�?

一般远端仓库里有新的内容更新,当我们需要把新内容下载的时候,就使用到git pull或者git fetch命令

fetch

用法如下�?

1
git fetch <远程主机�? <远程分支�?:<本地分支�?

例如从远程的origin仓库的master分支下载代码到本地并新建一个temp分支

1
git fetch origin master:temp

如果上述没有冒号,则表示将远程origin仓库的master分支拉取下来到本地当前分�?
这里git fetch不会进行合并,执行后需要手动执行git merge合并,如下:

1
git merge temp

pull

两者的用法十分相似,pull用法如下�?

1
git pull <远程主机�? <远程分支�?:<本地分支�?

例如将远程主机originmaster分支拉取过来,与本地的branchtest分支合并,命令如下:

1
git pull origin master:branchtest

同样如果上述没有冒号,则表示将远程origin仓库的master分支拉取下来与本地当前分支合�?

三、区�?

相同点:

  • 在作用上他们的功能是大致相同的,都是起到了更新代码的作用

不同点:

  • git pull是相当于从远程仓库获取最新版本,然后再与本地分支merge,即git pull = git fetch + git merge
  • 相比起来,git fetch 更安全也更符合实际要求,�?merge 前,我们可以查看更新情况,根据实际情况再决定是否合并

参考文�?

面试官:说说你对git rebase �?git merge的理解?区别�?

一、是什�?

在使�?git 进行版本管理的项目中,当完成一个特性的开发并将其合并�?master 分支时,会有两种方式�?

  • git merge
  • git rebase

git rebase �?git merge都有相同的作用,都是将一个分支的提交合并到另一分支上,但是在原理上却不相同

用法上两者也十分的简单:

git merge

将当前分支合并到指定分支,命令用法如下:

1
git merge xxx

git rebase

将当前分支移植到指定分支或指定commit之上,用法如下:

1
git rebase -i <commit>

常见的参数有--continue,用于解决冲突之后,继续执行rebase

1
git rebase --continue

二、分�?

git merge

通过git merge将当前分支与xxx分支合并,产生的新的commit对象有两个父节点

如果“指定分支”本身是当前分支的一个直接子节点,则会产生快照合�?
举个例子,bugfix分支是从master分支分叉出来的,如下所示:

合并 bugfix分支到master分支时,如果master分支的状态没有被更改过,�?bugfix分支的历史记录包含master分支所有的历史记录

所以通过把master分支的位置移动到bugfix的最新分支上,就完成合并

如果master分支的历史记录在创建bugfix分支后又有新的提交,如下情况�?

这时候使用git merge的时候,会生成一个新的提交,并且master分支的HEAD会移动到新的分支上,如下�?

从上面可以看到,会把两个分支的最新快照以及二者最近的共同祖先进行三方合并,合并的结果是生成一个新的快�?

git rebase

同样,master分支的历史记录在创建bugfix分支后又有新的提交,如下情况�?

通过git rebase,会变成如下情况�?

在移交过程中,如果发生冲突,需要修改各自的冲突,如下:

rebase之后,masterHEAD位置不变。因此,要合并master分支和bugfix分支

从上面可以看到,rebase会找到不同的分支的最近共同祖先,如上图的B

然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件(老的提交XY也没有被销毁,只是简单地不能再被访问或者使用)

然后将当前分支指向目标最新位置D, 然后将之前另存为临时文件的修改依序应�?

三、区�?

从上面可以看到,mergerebasea都是合并历史记录,但是各自特性不同:

merge

通过merge合并分支会新增一个merge commit,然后将两个分支的历史联系起�?
其实是一种非破坏性的操作,对现有分支不会以任何方式被更改,但是会导致历史记录相对复杂

rebase

rebase 会将整个分支移动到另一个分支上,有效地整合了所有分支上的提�?
主要的好处是历史记录更加清晰,是在原有提交的基础上将差异内容反映进去,消除了 git merge所需的不必要的合并提�?

参考文�?

0%