Tiny'Wo | 小窝

网络中的一小块自留地

React 使用

React 是全球应用最广泛的框架,国内大厂多用 React

::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

React 组件生命周期

::: details 参考答�?
React 组件生命周期分为以下三个阶段�?
挂载阶段:这是组件首次被创建并插入到 DOM 中的阶段�?
更新阶段:当组件�?props �?state 发生变化时,就会触发更新阶段�?
卸载阶段:组件从 DOM 中移除时进入卸载阶段�?
函数组件是没有明确的生命周期方法,但可以通过 useEffect 来模拟生命周期行为�?
模拟挂载阶段的生命周期方法:

  • 只需要在 useEffect 的依赖数组中传入一个空数组 []。这样,该副作用只会在组件挂载后运行一次�?
    1
    2
    3
    useEffect(() => {
    console.log('代码只会在组件挂载后执行一�?)
    }, [])

模拟更新阶段的生命周期方法:

  • 通过将依赖项放入依赖数组中,useEffect 可以在依赖项更改时执行。如果你省略了依赖数组,副作用将在每次渲染后执行�? ```js
    // 注意这里没有提供依赖数组
    useEffect(() => {
    console.log(‘代码会在组件挂载后以及每次更新后执行’)
    })
    // 特定依赖更新时执�? useEffect(() => {
    console.log(‘代码会在 count 更新后执�?)
    }, [count])
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    模拟**卸载阶段**的生命周期方法:

    - �?`useEffect` 的函数中返回一个函数,该函数会在组件卸载前执行�?
    ```js
    useEffect(() => {
    return () => {
    console.log('代码会在组件卸载前执�?)
    }
    }, [])
    :::

React 父子组件生命周期调用顺序

::: details 参考答�?
函数组件的生命周期通过 useEffect 模拟,其调用顺序如下�?
挂载阶段

  • **父组�?*:执行函数体(首次渲染)
  • **子组�?*:执行函数体(首次渲染)
  • **子组�?*:useEffect(挂载阶段)
  • **父组�?*:useEffect(挂载阶段)

更新阶段

  • **父组�?*:执行函数体(重新渲染)

  • **子组�?*:执行函数体(重新渲染)

  • **子组�?*:useEffect 清理函数(如果依赖项变化�?- **父组�?*:useEffect 清理函数(如果依赖项变化�?- **子组�?*:useEffect(如果依赖项变化�?- **父组�?*:useEffect(如果依赖项变化�?
    卸载阶段

  • **父组�?*:useEffect 清理函数

  • **子组�?*:useEffect 清理函数

:::

React 组件通讯方式

::: details 参考答�?

  • *通过props向子组件传递数�?
1
2
3
4
5
6
7
8
//父组�?const Parent = () => {
const message = 'Hello from Parent'
return <Child message={message} />
}

// 子组�?const Child = ({ message }) => {
return <div>{message}</div>
}
  • *通过回调函数向父组件传递数�?
1
2
3
4
5
6
7
8
9
10
//父组�?const Parent = () => {
const handleData = (data) => {
console.log('Data from Child:', data)
}
return <Child onSendData={handleData} />
}

// 子组�?const Child = ({ message }) => {
return <button onClick={() => onSendData('Hello from Child')}>Send Data</button>
}
  • 使用refs调用子组件暴露的方法
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
import React, { useRef, forwardRef, useImperativeHandle } from 'react'

// 子组�?const Child = forwardRef((props, ref) => {
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
sayHello() {
alert('Hello from Child Component!')
},
}))

return <div>Child Component</div>
})

// 父组�?function Parent() {
const childRef = useRef(null)

const handleClick = () => {
if (childRef.current) {
childRef.current.sayHello()
}
}

return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>Call Child Method</button>
</div>
)
}

export default Parent
  • 通过Context进行跨组件通信
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
import React, { useState } from 'react'

// 创建一�?Context
const MyContext = React.createContext()

// 父组�?function Parent() {
const [sharedData, setSharedData] = useState('Hello from Context')

const updateData = () => {
setSharedData('Updated Data from Context')
}

return (
// 提供数据和更新函�? <MyContext.Provider value={{ sharedData, updateData }}>
<ChildA />
</MyContext.Provider>
)
}

// 子组�?A(引用子组件 B�?function ChildA() {
return (
<div>
<ChildB />
</div>
)
}

// 子组�?B(使�?useContext�?function ChildB() {
const { sharedData, updateData } = React.useContext(MyContext)
return (
<div>
<div>ChildB: {sharedData}</div>
<button onClick={updateData}>Update Data</button>
</div>
)
}

export default Parent
  • 使用状态管理库进行通信

    • React Context + useReducer

      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
      import React, { useReducer } from 'react'

      const initialState = { count: 0 }

      function reducer(state, action) {
      switch (action.type) {
      case 'increment':
      return { count: state.count + 1 }
      case 'decrement':
      return { count: state.count - 1 }
      default:
      throw new Error()
      }
      }

      const CounterContext = React.createContext()

      function CounterProvider({ children }) {
      const [state, dispatch] = useReducer(reducer, initialState)
      return <CounterContext.Provider value={{ state, dispatch }}>{children}</CounterContext.Provider>
      }

      function Counter() {
      const { state, dispatch } = React.useContext(CounterContext)
      return (
      <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      </div>
      )
      }

      function App() {
      return (
      <CounterProvider>
      <Counter />
      </CounterProvider>
      )
      }

      export default App
    • Redux:使�?Redux Toolkit 简�?Redux 开发�?

      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
      import { createSlice, configureStore } from '@reduxjs/toolkit'

      const counterSlice = createSlice({
      name: 'counter',
      initialState: { value: 0 },
      reducers: {
      increment: (state) => {
      state.value += 1
      },
      decrement: (state) => {
      state.value -= 1
      },
      },
      })

      const { increment, decrement } = counterSlice.actions

      const store = configureStore({
      reducer: counterSlice.reducer,
      })

      store.subscribe(() => console.log(store.getState()))

      store.dispatch(increment())
      store.dispatch(decrement())
    • MobX

    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
    import { makeAutoObservable } from 'mobx'
    import { observer } from 'mobx-react-lite'

    class CounterStore {
    count = 0

    constructor() {
    makeAutoObservable(this)
    }

    increment() {
    this.count += 1
    }

    decrement() {
    this.count -= 1
    }
    }

    const counterStore = new CounterStore()

    const Counter = observer(() => {
    return (
    <div>
    Count: {counterStore.count}
    <button onClick={() => counterStore.increment()}>+</button>
    <button onClick={() => counterStore.decrement()}>-</button>
    </div>
    )
    })

    export default Counter
    • Zustand
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import create from "zustand";

    const useStore = create((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
    decrement: () => set((state) => ({ count: state.count - 1 })),
    }));

    function Counter() {
    const { count, increment, decrement } = useStore();
    return (
    <div>
    Count: {count}
    <button onClick={increment}>+</button>
    <button onClick={decrement}>-</button>
    </div>
    );
    }

    export default Counter;
  • 使用事件总线(Event Bus)进行通信

可以使用第三方库如 pubsub-js 来实现父子组件间通信。在父组件中订阅一个事件,子组件在特定情况下发布这个事件并传递数据�?

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
import React from 'react'
import PubSub from 'pubsub-js'

const ParentComponent = () => {
React.useEffect(() => {
const token = PubSub.subscribe('childData', (msg, data) => {
console.log('Received data from child:', data)
})
return () => {
PubSub.unsubscribe(token)
}
}, [])

return <ChildComponent />
}

const ChildComponent = () => {
const sendData = () => {
PubSub.publish('childData', { message: 'Hello from child' })
}

return <button onClick={sendData}>Send data from child</button>
}

export default ParentComponent

:::

state �?props 有什么区别?

::: details 参考答�?
�?React 中,props �?state 都用于管理组件的数据和状态�?
*Props(属性)�?

props 是组件之间传递数据的一种方式,用于从父组件向子组件传递数据�?props 是只读的,即父组件传递给子组件的数据在子组件中不能被修改�?props 是在组件的声明中定义,通过组件的属性传递给子组件�?props 的值由父组件决定,子组件无法直接改变它的值�?当父组件�?props 发生变化时,子组件会重新渲染�?
*State(状态)�?

state 是组件内部的数据,用于管理组件的状态和变化�?state 是可变的,组件可以通过 setState 方法来更新和修改 state�?state 是在组件的构造函数中初始化的,通常被定义为组件的类属性�?state 的值可以由组件自身内部改变,通过调用 setState 方法触发组件的重新渲染�?当组件的 state 发生变化时,组件会重新渲染�?
*总结�?

props 是父组件传递给子组件的数据,是只读的,子组件无法直接修改它�?state 是组件内部的数据,是可变的,组件可以通过 setState 方法来修改它�?props 用于组件之间的数据传递,�?state 用于管理组件自身的状态和变化�?
:::

React 有哪些内�?Hooks �?

::: details 参考答�?
React 目前有多�?Hooks API,可以参考官方文档 Hooks,可以按照功能进行分�?

1. 状态管�?Hooks

  • useState: 用于在函数组件中添加局部状态�?- useReducer: 用于管理复杂的状态逻辑,类似于 Redux �?reducer�?
    2. 副作�?Hooks

  • useEffect: 用于在函数组件中执行副作用操作(如数据获取、订阅、手�?DOM 操作等)�?- useLayoutEffect: �?useEffect 类似,但�?DOM 更新后同步执行,适用于需要直接操�?DOM 的场景�?
    3. 上下�?Hooks

  • useContext: 用于访问 React 的上下文(Context)�?
    4. 引用 Hooks

  • useRef: 用于创建一个可变的引用对象,通常用于访问 DOM 元素或存储可变值�?
    5. 性能优化 Hooks

  • useMemo: 用于缓存计算结果,避免在每次渲染时都重新计算�?- useCallback: 用于缓存回调函数,避免在每次渲染时都创建新的回调�?
    6. 其他 Hooks

  • useDeferredValue: 延迟更新 UI 的某些部分�?- useActionState: 根据某个表单动作的结果更�?state�?- useImperativeHandle: 用于自定义暴露给父组件的实例值,通常�?forwardRef 一起使用�?- useDebugValue: 用于�?React 开发者工具中显示自定�?Hook 的标签�?- useOptimistic 帮助你更乐观地更新用户界�?- useTransition: 用于标记某些状态更新为“过渡”状态,允许你在更新期间显示加载指示器�?- useId: 用于生成唯一�?ID,可以生成传递给无障碍属性的唯一 ID�?- useSyncExternalStore: 用于订阅外部存储(如 Redux �?Zustand)的状态�?- useInsertionEffect: �?CSS-in-JS 库的作者特意打造的,在布局副作用触发之前将元素插入�?DOM �?
    :::

useEffect �?useLayoutEffect 的区�?

::: details 参考答�?
1. 执行时机

  • useEffect:

    • 执行时机: 在浏览器完成绘制(即 DOM 更新并渲染到屏幕)之后异步执行�? - 适用场景: 适用于大多数副作用操作,如数据获取、订阅、手�?DOM 操作等,因为这些操作通常不需要阻塞浏览器的渲染�?
  • useLayoutEffect:

    • 执行时机: �?DOM 更新之后,但在浏览器绘制之前同步执行�? - 适用场景: 适用于需要在浏览器绘制之前同步执行的副作用操作,如测�?DOM 元素、同步更�?DOM 等。由于它是同步执行的,可能会阻塞浏览器的渲染,因此应谨慎使用�?
      2. 对渲染的影响
  • useEffect:

    • 由于是异步执行,不会阻塞浏览器的渲染过程,因此对用户体验的影响较小�? - 如果副作用操作导致状态更新,React 会重新渲染组件,但用户不会看到中间的闪烁或不一致的状态�?
  • useLayoutEffect:

    • 由于是同步执行,会阻塞浏览器的渲染过程,直到副作用操作完成�? - 如果副作用操作导致状态更新,React 会立即重新渲染组件,用户可能会看到中间的闪烁或不一致的状态�?
      3. 总结
  • useEffect: 异步执行,不阻塞渲染,适合大多数副作用操作�?- useLayoutEffect: 同步执行,阻塞渲染,适合需要在绘制前同步完成的副作用操作�?
    :::

为何 dev 模式�?useEffect 执行两次�?

::: details 参考答�?
React 官方文档其实对这个问题进行了解答�?
在开发环境下,如果开启严格模式,React 会在实际运行 setup 之前额外运行一�?setup �?cleanup�?
这是一个压力测试,用于验证 Effect 的逻辑是否正确实现。如果出现可见问题,�?cleanup 函数缺少某些逻辑。cleanup 函数应该停止或撤�?setup 函数所做的任何操作。一般来说,用户不应该能够区�?setup 被调用一次(如在生产环境中)和调�?setup �?cleanup �?setup 序列(如在开发环境中)�?
借助严格模式的目标是帮助开发者提前发现以下问题:

  1. 不纯的渲染逻辑:例如,依赖外部状态或直接修改 DOM�?2. 未正确清理的副作用:例如,未�?useEffect 的清理函数中取消订阅或清除定时器�?3. 不稳定的组件行为:例如,组件在多次挂载和卸载时表现不一致�?
    通过强制组件挂载和卸载两次,React 可以更好地暴露这些问题�?
    :::

React 闭包陷阱

::: details 参考答�?
让我们举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Counter() {
const [count, setCount] = useState(0)

useEffect(() => {
const timer = setInterval(() => {
console.log(count) // 每次打印的都是初始�?0
}, 1000)

return () => clearInterval(timer)
}, []) // 依赖数组为空,effect 只运行一�?
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}

在这个例子中�?

  • useEffect 只在组件挂载时运行一次�?- setInterval 的回调函数形成了一个闭包,捕获了初始的 count 值(�?0)�?- 即使 count 状态更新了,setInterval 中的回调函数仍然访问的是旧的 count 值�?
    闭包陷阱的根本原因是 JavaScript 的闭包机制:

  • 当一个函数被定义时,它会捕获当前作用域中的变量�?- 如果这些变量是状态或 props,它们的值在函数定义时被“固定”下来�?- 当状态或 props 更新时,闭包中的值并不会自动更新�?
    为了避免闭包陷阱,可以将依赖的状态或 props 添加�?useEffect 的依赖数组中,这样每次状态更新时,useEffect 都会重新运行,闭包中的值也会更新�?

    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
    useEffect(() => {
    const timer = setInterval(() => {
    console.log(count) // 每次打印最新的 count �? }, 1000)

    return () => clearInterval(timer)
    }, [count]) // �?count 添加到依赖数�?```

    :::

    ## React state 不可变数�?
    ::: details 参考答�?
    �?React 中,**状态(state)的不可变�?* 是指你不能直接修改状态的值,而是需要创建一个新的值来替换旧的状态�?
    使用不可变数据可以带来如下好处:

    1. **性能优化**

    React 使用浅比较(shallow comparison)来检测状态是否发生变化。如果状态是不可变的,React 只需要比较引用(即内存地址)是否变化,而不需要深度遍历整个对象或数组�?
    2. **可预测�?*

    - 不可变数据使得状态的变化更加可预测和可追踪�?- 每次状态更新都会生成一个新的对象或数组,这样可以更容易地调试和追踪状态的变化历史�?
    3. **避免副作�?*

    - 直接修改状态可能会导致意外的副作用,尤其是在异步操作或复杂组件中�?- 不可变数据确保了状态的更新是纯函数式的,避免了副作用�?
    **关于如何实现不可变数据?**

    1. **更新对象时使用新的对�?*

    ```jsx
    // �?错误:直接修改状�?state.name = 'new name'
    setState(state)
1
2
3
// �?正确:创建新对象
setState({
...state, // 复制旧状�? name: 'new name', // 更新属�?})
  1. *更新数组时使用新的数�?
1
2
// �?错误:直接修改数�?state.items.push(newItem)
setState(state)
1
2
3
4
// �?正确:创建新数组
setState({
...state,
items: [...state.items, newItem], // 添加新元�?})
  1. 使用工具库简化不可变更新

常用的库有:

  1. Immer.js
    Immer 是一个流行的库,它允许你以可变的方式编写代码,但最终生成不可变的数据�?

    1
    2
    3
    4
    5
    6
    7
    8
    import produce from 'immer'

    setState(
    produce(state, (draft) => {
    draft.user.profile.name = 'new name' // 直接修改
    draft.items.push(newItem) // 直接修改
    })
    )
  2. Immutable.js

Immutable.js 提供了不可变的数据结构(�?ListMap 等),可以更方便地处理不可变数据�?

1
2
3
4
import { Map } from 'immutable'

const state = Map({ name: 'John', age: 30 })
const newState = state.set('name', 'Jane')

:::

React state 异步更新

::: details 参考答�?
�?React 18 之前,React 采用批处理策略来优化状态更新。在批处理策略下,React 将在事件处理函数结束后应用所有的状态更新,这样可以避免不必要的渲染�?DOM 操作�?
然而,这个策略在异步操作中就无法工作了。因�?React 没有办法在适当的时机将更新合并起来,所以结果就是在异步操作中的每一个状态更新都会导致一个新的渲染�?
例如,当你在一�?onClick 事件处理函数中连续调用两�?setState,React 会将这两个更新合并,然后在一次重新渲染中予以处理�?
然而,在某些场景下,如果你在事件处理函数之外调�?setState,React 就无法进行批处理了。比如在 setTimeout 或�?Promise 的回调函数中。在这些场景中,每次调用 setState,React 都会触发一次重新渲染,无法达到批处理的效果�?
React 18 引入了自动批处理更新机制,让 React 可以捕获所有的状态更新,并且无论在何处进行更新,都会对其进行批处理。这对一些异步的操作,如 Promise,setTimeout 之类的也同样有效�?
这一新特性的实现,核心在�?React 18 对渲染优先级的管理。React 18 引入了一种新的协调器,被称为“React Scheduler”。它负责管理 React 的工作单元队列。每当有一个新的状态更新请求,React 会创建一个新的工作单元并放入这个队列。当 JavaScript 运行栈清空,Event Loop 即将开始新的一轮循环时,Scheduler 就会进入工作,处理队列中的所有工作单元,实现了批处理�?
:::

React state 的“合并”特�?

::: details 参考答�?
React *状态的“合并”特�? 是指当使�?setState 更新状态时,React 会将新状态与旧状态进行浅合并(shallow merge),而不是直接替换整个状态对象�?
合并特性在类组件中尤为明显,而在函数组件中需要手动实现类似的行为�?

  1. *类组件中的状态合�?

在类组件中,setState 会自动合并状态对象。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'John',
age: 30,
}
}

updateName = () => {
this.setState({ name: 'Jane' }) // 只更�?name,age 保持不变
}

render() {
return (
<div>
<p>Name: {this.state.name}</p>
<p>Age: {this.state.age}</p>
<button onClick={this.updateName}>Update Name</button>
</div>
)
}
}

在这个例子中�?

  • 调用 this.setState({ name: 'Jane' }) 只会更新 name 属性,�?age 属性保持不变�?- React 会自动将新状�?{ name: 'Jane' } 与旧状�?{ name: 'John', age: 30 } 进行浅合并,结果�?{ name: 'Jane', age: 30 }�?
  1. *函数组件中的状态替�?

在函数组件中,useState �?setter 函数不会自动合并状态。如果你直接传递一个新对象,它会完全替换旧状态�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function MyComponent() {
const [state, setState] = useState({
name: 'John',
age: 30,
})

const updateName = () => {
setState({ name: 'Jane' }) // �?直接替换,age 会丢�? }

return (
<div>
<p>Name: {state.name}</p>
<p>Age: {state.age}</p>
<button onClick={updateName}>Update Name</button>
</div>
)
}

在这个例子中�?

  • 调用 setState({ name: 'Jane' }) 会完全替换状态对象,导致 age 属性丢失�?- 最终状态变�?{ name: 'Jane' },而不�?{ name: 'Jane', age: 30 }�?
  1. 如何在函数组件中实现状态合并?

在函数组件中,如果需要实现类似类组件的状态合并特性,可以手动合并状态:

方法 1:使用扩展运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function MyComponent() {
const [state, setState] = useState({
name: 'John',
age: 30,
})

const updateName = () => {
setState((prevState) => ({
...prevState, // 复制旧状�? name: 'Jane', // 更新 name
}))
}

return (
<div>
<p>Name: {state.name}</p>
<p>Age: {state.age}</p>
<button onClick={updateName}>Update Name</button>
</div>
)
}

方法 2:使�?useReducer
useReducer 可以更灵活地管理复杂状态,并实现类似合并的行为�?

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
function reducer(state, action) {
switch (action.type) {
case 'UPDATE_NAME':
return {
...state,
name: action.payload,
}
default:
throw new Error()
}
}

function MyComponent() {
const [state, dispatch] = useReducer(reducer, {
name: 'John',
age: 30,
})

const updateName = () => {
dispatch({ type: 'UPDATE_NAME', payload: 'Jane' })
}

return (
<div>
<p>Name: {state.name}</p>
<p>Age: {state.age}</p>
<button onClick={updateName}>Update Name</button>
</div>
)
}

:::

什么是 React 受控组件�?

::: details 参考答�?�?React 中,受控组件(Controlled Component�?是指表单元素(如 <input><textarea><select> 等)的值由 React 的状态(state)控制,而不是由 DOM 自身管理。换句话说,表单元素的值通过 value 属性绑定到 React 的状态,并通过 onChange 事件处理函数来更新状态�?
这是一个简单的受控组件示例�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ControlledInput() {
const [value, setValue] = useState('')

const handleChange = (event) => {
setValue(event.target.value) // 更新状�? }

return (
<div>
<input
type="text"
value={value} // 绑定状�? onChange={handleChange} // 监听输入变化
/>
<p>Current value: {value}</p>
</div>
)
}

受控组件的优点:

  1. 完全控制表单数据:React 状态是表单数据的唯一来源,可以轻松地对数据进行验证、格式化或处理�?2. 实时响应输入:可以在用户输入时实时更�?UI 或执行其他操作(如搜索建议)�?3. 易于集成:与其他 React 状态和逻辑无缝集成�?
    :::

使用 React Hook 实现 useCount

1
// count �?0 计数,每一�?+1 (可使用 setInterval�?const { count } = useTimer()

::: details 参考答�?

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
import { useState, useEffect } from 'react'

function useTimer() {
const [count, setCount] = useState(0)

useEffect(() => {
// 设置定时器,每秒钟增�?count
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1)
}, 1000)

// 清理定时�? return () => clearInterval(intervalId)
}, []) // 空数组表示仅在组件挂载时执行一�?
return { count }
}

export default function TimerComponent() {
const { count } = useTimer()

return (
<div>
<p>Count: {count}</p>
</div>
)
}

:::

使用 React Hook 实现 useRequest

1
const { loading, data, error } = useRequest(url) // 可只考虑 get 请求

::: details 参考答�?

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
import { useState, useEffect } from 'react'
import axios from 'axios'

function useRequest(url) {
const [data, setData] = useState(null) // 存储请求的数�? const [loading, setLoading] = useState(true) // 加载状�? const [error, setError] = useState(null) // 错误信息

useEffect(() => {
const fetchData = async () => {
setLoading(true) // 设置加载状态为 true
setError(null) // 清空先前的错�?
try {
const response = await axios.get(url)
if (!response.ok) {
throw new Error('请求失败!')
}
setData(response.data) // 设置数据
} catch (err) {
setError(err.message) // 捕获错误并设置错误信�? } finally {
setLoading(false) // 请求结束,设置加载状态为 false
}
}

fetchData()
}, [url]) // 依赖�?url,当 url 改变时重新发起请�?
return { loading, data, error }
}

// 使用示例
export default function RequestComponent() {
const { loading, data, error } = useRequest('https://xxx.xxxx.com/data')

if (loading) return <p>Loading...</p>
if (error) return <p>错误信息: {error}</p>
return (
<div>
<h3>请求结果:</h3>
<pre>{JSON.stringify(data)}</pre>
</div>
)
}

:::

React 项目可做哪些性能优化�?

::: details 参考答�?

  1. useMemo: 用于缓存昂贵的计算结果,避免在每次渲染时重复计算�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function ExpensiveComponent({ items, filter }) {
    const filteredItems = useMemo(() => {
    return items.filter((item) => item.includes(filter))
    }, [items, filter]) // 仅在 items �?filter 变化时重新计�?
    return (
    <ul>
    {filteredItems.map((item) => (
    <li key={item}>{item}</li>
    ))}
    </ul>
    )
    }
  2. useCallback: 用于缓存回调函数,避免在每次渲染时创建新的函数实例�? useCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ParentComponent() {
const [count, setCount] = useState(0)

const handleClick = useCallback(() => {
setCount((prevCount) => prevCount + 1)
}, []) // 空依赖数组,函数不会重新创建

return (
<div>
<ChildComponent onClick={handleClick} />
<p>Count: {count}</p>
</div>
)
}

const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent rendered')
return <button onClick={onClick}>Click me</button>
})
  1. React.memo: 是一个高阶组件,用于缓存组件的渲染结果,避免�?props 未变化时重新渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const MyComponent = React.memo(({ value }) => {
console.log('MyComponent rendered')
return <div>{value}</div>
})

function ParentComponent() {
const [count, setCount] = useState(0)

return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<MyComponent value="Hello" /> {/* 不会�?count 变化而重新渲�?*/}
</div>
)
}
  1. Suspense: 用于在异步加载数据或组件时显示加载状态,可以减少初始加载时间,提升用户体�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const LazyComponent = React.lazy(() => import('./LazyComponent'))

    function MyComponent() {
    return (
    <React.Suspense fallback={<div>Loading...</div>}>
    <LazyComponent />
    </React.Suspense>
    )
    }
  2. 路由懒加载:通过动态导入(dynamic import)将路由组件拆分为单独的代码块,按需加载。可以减少初始加载的代码量,提升页面加载速度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import React, { Suspense } from 'react'

const Home = React.lazy(() => import('./Home'))
const About = React.lazy(() => import('./About'))

function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
)
}

:::

如何统一监听 React 组件报错

::: details 参考答�?

  1. Error Boundaries(错误边界)

默认情况下,如果你的应用程序在渲染过程中抛出错误,React 将从屏幕上删除其 UI。为了防止这种情况,你可以将 UI 的一部分包装�?错误边界 中。错误边界是一个特殊的组件,可让你显示一些后�?UI,而不是显示例如错误消息这样崩溃的部分�?
要实现错误边界组件,你需要提�?static getDerivedStateFromError,它允许你更新状态以响应错误并向用户显示错误消息。你还可以选择实现 componentDidCatch 来添加一些额外的逻辑,例如将错误添加到分析服务�?

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
import * as React from 'react'

class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}

static getDerivedStateFromError(error) {
// 更新状态,以便下一次渲染将显示后备 UI�? return { hasError: true }
}

componentDidCatch(error, info) {
logErrorToMyService(
error,
// 示例“组件堆栈”:
// �?ComponentThatThrows 中(�?App 创建�? // �?ErrorBoundary 中(�?APP 创建�? // �?div 中(�?APP 创建�? // �?App �? info.componentStack,
// 仅在 react@canary 版本可用
// 警告:Owner Stack 在生产中不可�? React.captureOwnerStack()
)
}

render() {
if (this.state.hasError) {
// 你可以渲染任何自定义后备 UI
return this.props.fallback
}

return this.props.children
}
}

然后你可以用它包装组件树的一部分�?

1
2
3
<ErrorBoundary fallback={<p>Something went wrong</p>}>
<Profile />
</ErrorBoundary>

如果 Profile 或其子组件抛出错误,ErrorBoundary 将“捕获”该错误,然后显示带有你提供的错误消息的后备 UI,并向你的错误报告服务发送生产错误报告�?
2. 全局错误监听

为了捕获 Error Boundaries 无法处理的错误(如事件处理器或异步代码中的错误),可以使�?JavaScript 的全局错误监听机制�?

  • 使用 window.onerror 监听全局错误�?- 使用 window.addEventListener(‘error’, handler) 监听未捕获的错误�?- 使用 window.addEventListener(‘unhandledrejection’, handler) 监听未处理的 Promise 拒绝�?
    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
    import React, { useEffect } from 'react'

    function GlobalErrorHandler() {
    useEffect(() => {
    // 监听全局错误
    const handleError = (error) => {
    console.error('Global error:', error)
    }

    // 监听未捕获的错误
    window.onerror = (message, source, lineno, colno, error) => {
    handleError(error)
    return true // 阻止默认错误处理
    }

    // 监听未处理的 Promise 拒绝
    window.addEventListener('unhandledrejection', (event) => {
    handleError(event.reason)
    })

    // 清理监听�? return () => {
    window.onerror = null
    window.removeEventListener('unhandledrejection', handleError)
    }
    }, [])

    return null
    }

    // 在应用的根组件中使用
    function App() {
    return (
    <div>
    <GlobalErrorHandler />
    <MyComponent />
    </div>
    )
    }

注意事项�?

  1. 全局错误监听可以捕获 Error Boundaries 无法处理的错误,但无法阻止组件崩溃�?2. 需要确保在生产环境中正确处理错误信息,避免暴露敏感信息�?
    :::

React19 升级了哪些新特性?

::: details 参考答�?React 19 的更新内容可以参�?React 官方更新博客

  1. Actions 相关

按照惯例,使用异步过渡的函数被称�?“Actions”�?�?Actions 的基础上,React 19 引入�?useOptimistic 来管理乐观更新,以及一个新�?Hook React.useActionState 来处�?Actions 的常见情况。在 react-dom 中添加了 <form> Actions 来自动管理表单和 useFormStatus 来支持表单中 Actions 的常见情况�?
2. 新的 API: use

�?React 19 中,我们引入了一个新�?API 来在渲染中读取资源:use�?
例如,你可以使用 use 读取一�?promise,React 将挂起,直到 promise 解析完成�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { use } from 'react'

function Comments({ commentsPromise }) {
// `use` 将被暂停直到 promise 被解�?
const comments = use(commentsPromise)
return comments.map((comment) => <p key={comment.id}>{comment}</p>)
}

function Page({ commentsPromise }) {
// 当“use”在注释中暂停时,
// 将显示此悬念边界�? return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
  1. 服务端组件和动作

React 服务端组件现已稳定,允许提前渲染组件。与服务端动作(通过“use server”指令启用)配对后,客户端组件可以无缝调用异步服务端函数�?
此外,还有一�?React 19 中的改进�?
4. ref 作为一个属�?
�?React 19 开始,你现在可以在函数组件中将 ref 作为 prop 进行访问�?

1
2
3
4
5
6
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />
}

//...
;<MyInput ref={ref} />

新的函数组件将不再需�?forwardRef�?
5. 改进了水合错误的错误报告

  1. <Context> 作为提供�?
    �?React 19 中,你可以将 <Context> 渲染为提供者,就无需再使�?<Context.Provider> 了:
1
2
3
4
5
const ThemeContext = createContext('')

function App({ children }) {
return <ThemeContext value="dark">{children}</ThemeContext>
}

新的 Context 提供者可以使�?<Context>,我们将发布一�?codemod 来转换现有的提供者。在未来的版本中,我们将弃用 <Context.Provider>�?
更多更新请参考官方更新博客

:::

简�?Redux 单向数据�?

::: details 参考答�?这是 Redux 单向数据流的典型流程�?

1
View -> Action -> Reducer -> State -> View
  1. View�? - 用户在界面(View)上触发一个事件(如点击按钮)�?2. Action�? - 事件触发一�?action,并通过 store.dispatch(action) 分发�?3. Reducer�? - store 调用 reducer,传入当前的 state �?action,生成一个新�?state�?4. State�? - store 更新 state,并通知所有订阅了 store 的组件�?5. View�? - 组件根据新的 state 重新渲染界面�?
    Redux 单向数据流的特点

  2. **可预测�?*�? - 由于状态更新是通过纯函数(reducer)完成的,相同的 state �?action 总是会生成相同的新的 state�?2. 集中管理�? - 所有状态都存储在单一�?store 中,便于调试和管理�?3. 易于测试�? - reducer 是纯函数,没有副作用,易于测试�?4. 时间旅行调试�? - 通过记录 action �?state,可以实现时间旅行调试(�?Redux DevTools)�?


示例代码

以下是一个完整的 Redux 示例�?

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
// 1. 定义 Action Types
const ADD_TODO = 'ADD_TODO'

// 2. 定义 Action Creator
function addTodo(text) {
return {
type: ADD_TODO,
payload: text,
}
}

// 3. 定义 Reducer
function todoReducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, action.payload]
default:
return state
}
}

// 4. 创建 Store
const store = Redux.createStore(todoReducer)

// 5. 订阅 Store
store.subscribe(() => {
console.log('Current State:', store.getState())
})

// 6. 分发 Action
store.dispatch(addTodo('Learn Redux'))
store.dispatch(addTodo('Build a project'))

:::

用过哪些 Redux 中间件?

::: details 参考答�?
Redux 中间件(Middleware)允许你�?action 被分发(dispatch)到 reducer 之前或之后执行额外的逻辑。中间件通常用于处理异步操作、日志记录、错误处理等任务�?
常用�?Redux 中间件有

1. Redux Thunk

  • 描述: Redux Thunk 是最常用的中间件之一,用于处理异步操作(�?API 调用)�?- 特点:
    • 允许 action 是一个函数(而不仅仅是一个对象)�? - 函数可以接收 dispatch �?getState 作为参数,从而在异步操作完成后手动分�?action�?- 使用场景: 处理异步逻辑(如数据获取)�?- 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      const fetchData = () => {
      return (dispatch, getState) => {
      dispatch({ type: 'FETCH_DATA_REQUEST' })
      fetch('/api/data')
      .then((response) => response.json())
      .then((data) => dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data }))
      .catch((error) => dispatch({ type: 'FETCH_DATA_FAILURE', error }))
      }
      }

2. Redux Saga

  • 描述: Redux Saga 是一个基于生成器函数(Generator)的中间件,用于管理复杂的异步流程和副作用�?- 特点:

    • 使用 ES6 的生成器函数来处理异步逻辑�? - 提供强大的副作用管理(如取消任务、并发执行等)�?- 使用场景: 复杂的异步流程(如竞态条件、任务取消等)�?- 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import { call, put, takeEvery } from 'redux-saga/effects'

    function* fetchData() {
    try {
    const data = yield call(fetch, '/api/data')
    yield put({ type: 'FETCH_DATA_SUCCESS', payload: data })
    } catch (error) {
    yield put({ type: 'FETCH_DATA_FAILURE', error })
    }
    }

    function* watchFetchData() {
    yield takeEvery('FETCH_DATA_REQUEST', fetchData)
    }

3. Redux Logger

  • 描述: Redux Logger 是一个用于记�?action �?state 变化的中间件�?- 特点:
    • 在控制台中打印每�?action 的分发和 state 的变化�? - 便于调试和开发�?- 使用场景: 开发环境中的调试�?- 示例:
      1
      const store = createStore(rootReducer, applyMiddleware(logger))

4. Redux Promise

  • 描述: Redux Promise 是一个用于处�?Promise 的中间件�?- 特点:
    • 自动处理 Promise 类型�?action�? - �?Promise 完成时,自动分发成功�?action;当 Promise 失败时,自动分发失败�?action�?- 使用场景: 简单的异步操作�?- 示例:
      1
      2
      3
      4
      const fetchData = () => ({
      type: 'FETCH_DATA',
      payload: fetch('/api/data').then((response) => response.json()),
      })
      :::

你用过哪�?React 状态管理库�?

::: details 参考答�?
根据自己实际的使用情况作答:

  1. Redux

Redux 是最流行�?React 状态管理库之一。它提供了一个全局的状态容器,允许你在应用的任何地方访问和更新状态。特点包�? 单向数据流、中间件支持、时间旅行调试�?
2. MobX

MobX 是一个响应式状态管理库,它通过自动追踪状态的变化来更�?UI。特点包�? 响应式编程、简单易用、自动依赖追踪�?
3. Recoil

Recoil �?Facebook 推出的一个实验性状态管理库,专�?React 设计。特点包�? 原子状态管理、派生状态、与 React 深度集成。适用于需要细粒度状态管理的应用�?
4. zustand

Zustand 是一个轻量级的状态管理库,API 简单且易于使用。特点包�? 轻量、简单、支持中间件。适用于需要轻量级状态管理的应用�?
5. Jotai

Jotai 是一个基于原子状态管理的库,类似�?Recoil,但更加轻量。特点包�? 原子状态、简单易用、与 React 深度集成。适用于需要细粒度状态管理的应用�?
6. XState

XState 是一个基于状态机的状态管理库,适用于复杂的状态逻辑和流程管理。特点包�? 状态机、可视化工具、复杂状态管理。适用于需要复杂状态逻辑和流程管理的应用�?
:::

是否用过 SSR 服务端渲染?

::: details 参考答�?
SSR

服务端渲染(Server-Side Rendering, SSR)是一种在服务器端生成 HTML 并将其发送到客户端的技术。与传统的客户端渲染(CSR)相比,SSR 可以提供更快的首屏加载速度、更好的 SEO 支持以及更友好的用户体验�?
*SSR 的核心优�?

  1. **更快的首屏加�?*�? - SSR 在服务器端生�?HTML,用户无需等待 JavaScript 加载完成即可看到页面内容�?2. 更好�?SEO�? - 搜索引擎可以抓取服务器渲染的完整 HTML 内容,而不是空�?<div id="root"></div>�?3. **更好的用户体�?*�? - 对于低性能设备或网络较差的用户,SSR 可以提供更快的初始渲染�?
    *SSR 的基本原�?

  2. 服务器端�? - 使用 ReactDOMServer �?React 组件渲染�?HTML 字符串�? - 将生成的 HTML 字符串嵌入到 HTML 模板中,并发送给客户端�?2. **客户�?*�? - 客户端接收到 HTML 后,React 会“接管”页面(hydration),使其成为可交互的 SPA(单页应用)�?
    *React SSR 的框架支�?

最常用的框架就�?Next.js,它是一个基�?React 的全栈开发框架,集成了最新的 React 特性,内置 SSR 支持,可以帮助你快速创建全栈应用�?:::

Vue 原理

国内面试,大厂必考原理�?
::: tip

  1. 目标不在中大厂的同学,可以略过这一节�?2. �?Vue 使用尚不熟练的同学,不要在此花费太多精力,先熟悉使用再说�?
    :::

::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

什么是 MVVM

::: details 参考答�?
*MVVM(Model-View-ViewModel�? 是一种用于构建用户界面的架构模式,用于现代的前端开发框架(Vue、Angular)。它通过 数据绑定 �?视图模型 提供了高效的 UI 更新和数据同步机制�?
MVVM 模式主要�?Model (模型)�?View (视图)�?ViewModel (视图模型)三个部分组成�?

  • Model表示程序的核心数据和业务逻辑,它不关心用户界面,只负责数据的获取、存储和处理,并提供与外界交互的接口�?- View负责展示数据和用户交互,简单来说他就是我们看到的UI 组件�?HTML 页面�?- ViewModel是连�?View �?Model 的桥梁,它不直接操作视图或模型,而是通过数据绑定将两者连接起来�?
    参考下面的示例�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <div id="app">
    <input v-model="message"/>
    <p>{{ computedValue }}</p>
    </div>

    <script setup>
    const message = ref('Hello, MVVM!')

    const computedValue = computed(() => {
    return '用户输入值变�?' + message.value
    })
    </script>

上述代码展示了一个输入框,当用户输入内容的时候,输入框下面的计算值会随之变化。在这个示例中, message 变量属于 Model ,它包含了应用的核心数据。输入框与页面展示就属于View,负责展示数据和用户交互�?computed �?v-model语法糖 作为 ViewModel ,用于更新视图和数据�?

:::

什么是 VDOM 它和 DOM 有什么关�?

::: details 参考答�?
页面的所有元素、属性和文本都通过 DOM 节点表示�?VDOM(Virtual DOM,虚�?DOM) 是DOM渲染的一种优化,它是一个内存中的虚拟树,是真实 DOM 的轻量级 JavaScript 对象表示�?
VDOM主要用于优化 UI 渲染性能,它的工作流程大致如下:

  • 1️⃣创建虚拟 DOM:当组件的状态或数据发生变化时,Vue 会重新生成虚�?DOM�?- 2️⃣比较虚拟 DOM 和真�?DOM:Vue 使用一种高效的算法来比较新旧虚�?DOM 的差异(�?diff 算法)�?- 3️⃣更新 DOM:根据差异更新真实的 DOM,仅修改有变化的部分,而不是重新渲染整�?DOM 树�?
    :::

手写 VNode 对象,表示如�?DOM 节点

1
2
3
4
<div class="container">
<img src="x1.png" />
<p>hello</p>
</div>

如果你还不熟�?虚拟 DOM �?渲染函数 的概念的话,请先学习vue的渲染机制

::: details 参考答�?
Vue 模板会被预编译成虚拟 DOM 渲染函数,我们也可以直接手写渲染函数,在处理高度动态的逻辑时,渲染函数相比于模板更加灵活,因为我们可以完全地使�?JavaScript 来构造我们想要的 vnode �?
Vue 提供了一�?h() 函数用于创建 vnodes

1
h(type, props, children)
  • type: 表示要渲染的节点类型(例�?HTML 标签名或组件)�?- props: 一个对象,包含该节点的属性(例如 classstylesrc 等)�?- children: 子节点,可以是文本内容、数组或者其�?VNode�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { h } from 'vue'

    export default {
    render() {
    return h(
    'div',
    {
    class: 'container',
    },
    [
    h('img', {
    src: 'x1.png',
    }),
    h('p', null, 'hello'),
    ]
    )
    },
    }

:::

Vue 组件初始化的各个阶段都做了什么?

::: details 参考答�?从组件的创建到挂载到页面,再到组件的更新和销毁,每个阶段都有特定的任务和职责�?
🎯 组件实例创建:当我们第一次访问页面时,Vue创建组件实例,解析propsdatamethods等属性方法,在组合式API中,执行 setup()�?
🎯 响应式系统建立:基于 Proxy 实现 reactiveref,建立依赖收集和触发更新机制,props 传递时自动响应式处理�?
🎯 模板编译与渲染:�?template 编译为渲染函数,Vue 3 通过 静态提升等方式优化性能,Vite 预编�?SFC(单文件组件)�?
🎯 DOM 挂载:执行渲染函数生�?VNode,通过 Patch 算法 转换为真�?DOM 并插入页面,同时初始化子组件。mounted(Options API)或 onMounted(Composition API)触发,可进行 DOM 操作�?
🎯 响应式更新:状态变更触�?Diff 算法 计算最�?DOM 更新,beforeUpdateupdated(Options API)�?onBeforeUpdateonUpdated(Composition API)执行相应逻辑�?
🎯 组件销毁:移除 DOM,清理副作用(解绑事件、销�?watcher、清�?effect),递归卸载子组件,触发 beforeUnmountunmounted(Options API)�?onBeforeUnmountonUnmounted(Composition API)�?

Vue 3 通过 Proxy 响应式、编译优化、生命周期调整提升性能,使组件更高效�?
:::

Vue 如何实现双向数据绑定

::: details 参考答�?
Vue 实现双向数据绑定的核心是通过**响应式系�?*�?数据劫持�?**观察者模�?*来实现的�?
🎯 数据劫持

Vue 2.x 使用 Object.defineProperty 对数据对象的每个属性递归添加 getter/setter ,当数据的属性被访问时,触发 getter ,当属性被修改时,触发 setter 通知视图进行更新。通过这种方式,Vue 可以监控数据的变化,并在数据变化时通知视图更新�?

Vue 3.x 使用 Proxy通过代理对象拦截整个对象的操作,无需递归初始化所有属性,性能更好�?
🎯 *观察者模�?

Vue 的响应式系统通过 *观察者模�? 来实现数据与视图的同步更新,简化的流程如下�?

  • 依赖收集:当 Vue 组件的视图模板渲染时,它会读取数据对象的属性(例如 {{ message }})。在读取属性时,getter方法会将视图组件与该数据属性建立依赖关系�?

  • **观察者(Watcher�?*:每个依赖的数据都会对应一个观察者。观察者的作用是监听数据的变化,一旦数据发生变化,观察者会收到通知,进而触发视图的更新�?

  • **通知视图更新(Notify View Update�?*:当数据通过 setter 修改时,Vue 会触发相应的观察者,通知相关的视图组件更新�?

通过这种方式,Vue 可以监控数据的变化,并在数据变化时通知视图更新�?
:::

Vue 模板编译的过�?

::: details 参考答�?
Vue 的模板编译过程是将开发者编写的模板语法(例�?{{ message }} �?v-bind 等)转换�?JavaScript 代码的过程。它主要分为三个阶段�?模板解析**�?AST优化 �?代码生成�?
1️⃣ 模板解析

Vue 使用其解析器�?HTML 模板转换�?**抽象语法树(AST�?*。在这个阶段,Vue 会分析模板中的标签、属性和指令,生成一颗树形结构。每个节点表示模板中的一个元素或属性�?
如:

1
2
3
4
5
6
7
8
9
< div >
<
p > {
{
message
}
} < /p> <
button v - on: click = "handleClick" > 点击 < /button> < /
div >

被解析成�?AST 类似于下面的结构�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
type: 1, // 节点类型�? 表示元素节点
tag: 'div', // 元素的标签名
children: [ // 子节点(嵌套�?HTML 元素�? {
type: 1, // 子节点是一个元素节�? tag: 'p',
children: [{
type: 2, // 2 表示插值表达式节点
expression: 'message' // 表达�?'message'
}]
},
{
type: 1, // 另一个元素节�? tag: 'button',
events: { // 事件监听
click: 'handleClick' // 绑定 click 事件,执�?handleClick 方法
},
children: [{
type: 3, // 文本节点
text: '点击' // 按钮文本
}]
}
]
}

2️⃣ AST优化

Vue 在生成渲染函数前,会�?AST 进行优化。优化的核心目标是标�?**静态节�?*,在渲染时,Vue 可以跳过这些静态节点,提升性能�?

**静态节�?*指所有的渲染过程中都不变化的内容,比�?某个div标签内的静态文本

�?vue3 中,如果一个节点及其子树都不依赖于动态数据,那么该节点会被提升到渲染函数外部(静态提升),仅在组件初次渲染时创建�?
3️⃣ 代码生成

生成渲染函数是编译的最终阶段,这个阶段会将优化后的 AST 转换�?JavaScript 渲染函数�?
例如,像这样的模板:

1
<div id="app">{{ message }}</div>

最终会生成类似这样的渲染函数:

1
2
3
4
5
6
7
8
9
function render() {
return createVNode(
'div',
{
id: 'app',
},
[createTextVNode(this.message)]
)
}

渲染函数的返回值是一�?虚拟 DOM(VDOM)树 ,Vue 会根�?虚拟 DOM 来更新实际的 DOM 。由�?渲染函数 �?Vue 的响应式系统包裹,当数据发生变化时,渲染函数会被重新执行生成新的虚拟 DOM,因此页面也会实时更新�?
:::

Vue 响应式原�?

::: details 参考答�?
Vue 的响应式原理�?2.x �?3.x 中有所不同,分别基�?Object.defineProperty �?Proxy 实现�?
🎯 Vue 2.x 的实�?( Object.defineProperty )

Object.defineProperty 支持 IE9 及以上版本,兼容性非常好。它会递归遍历对象,对每个属性单独设�?getter �?setter ,但也存在以下局限性:

  • *无法监听动态属性增�?
    Vue 2.x 在新增或删除对象属性时不会触发视图更新,需通过 Vue.set �?Vue.delete 手动处理�?- 数组监听受限
    无法直接监听数组索引的修改(�?arr[0] = 1 )和 length 变化,因�?Vue 2.x 重写了数组的一些方法来解决这一问题�?- 性能开销较大
    需要递归地为每个属性设�?getter �?setter ,对深层嵌套的对象和大型数组性能较差�?- *不支�?Map/Set 等数据结�?
    只能代理普通对象和数组,不能处理像 Map �?Set 等复杂数据结构�?
    🚀 Vue 3.x 的实�?( Proxy )

为了解决 Vue 2.x 中的这些问题,Vue 3.x 采用�?Proxy ,带来了更优的性能和更全面的响应式支持�?

  • *动态属性增删支�?
    Proxy 可以直接代理整个对象,因此可以监听属性的动态增删,不再需要手动操作�?- *完美支持数组和索引修�?
    Proxy 能够监听数组索引的修改(�?arr[0] = 1 )以�?length 变化,避免了 Vue 2.x 中的重写数组方法�?- 性能更优
    Proxy 采用懒代理模式,只有在访问属性时才会递归代理子对象,避免了递归遍历的性能开销�?- 支持更多数据结构
    除了普通对象和数组�?Proxy 还可以代�?Map �?Set 等数据结构,提供了更强大的响应式能力�?
    特�? Object.defineProperty
    (Vue 2�?
    Proxy
    (Vue 3�?
    动态属性增�? �?不支持(需 Vue.set / Vue.delete �? �?支持
    数组索引修改 �?需重写方法(如 push �? �?直接监听
    性能 ⚠️ 递归初始化所有属性,性能较差 �?惰性代理,按需触发,性能更优
    数据结构支持 �?仅普通对�?数组 �?支持 Map �?Set �?
    兼容�? �?支持 IE9+ �?不支�?IE
    实现复杂�? ⚠️ 需递归遍历对象,代码冗�? �?统一拦截,代码简�?

:::

为何 v-for 需要使�?key

::: details 参考答�?
�?Vue.js 中,使用 v-for 渲染列表时,添加 key 属性是一个重要的最佳实践�?

  • 提高性能:当 Vue 更新视图时,它会根据 key 来识别哪些元素被修改、添加或移除。如果没�?key,Vue 会依赖其默认的算法(基于元素的位置)来比较元素,这样可能导致不必要的 DOM 操作。使�?key 后,Vue 能精确地找到每个项,从而减少不必要�?DOM 重排和重绘,提升性能�?- **保持组件状�?*:如果渲染的是一个组件(而不是普通的 DOM 元素),使用 key 可以确保组件在渲染更新时保持正确的状态。例如,如果列表中有表单输入框,每个输入框都有自己的状态,使用 key 可以确保输入框状态不会因列表排序或元素移除而丢失�?- 避免渲染错误:key 的存在可以帮�?Vue 确保在列表更新时,元素的顺序和内容保持稳定,避免出现不稳定的渲染或顺序错乱�?
    :::

Vue diff 算法的过�?

::: details 参考答�?
Vue的diff算法执行,依赖数据的的响应式系统:当数据发生改变时, setter 方法会让调用 Dep.notify 通知所有订阅�?Watcher ,订阅者会重新执行渲染函数,渲染函数内部通过diff 算法用于比较新旧虚拟 DOM 树的差异,并计算出最小的更新操作,最终更新相应的视图�?

diff 算法的核心算法流程如下:

  • 节点对比
    如果新旧节点类型相同,则继续比较它们的属性。如果节点类型不同(如元素和文本节点不同),则直�?*替换**整个节点�?- 属性更新:
    如果节点类型相同,接下来检查节点的属性。对于不同的属性值进行更新,移除旧属性,添加新属性�?- 子节点比对:
    对于有子节点的元素(�?div),Vue 会使用不同的策略来优化子节点更新�? 🎯 文本节点的更新:如果新旧子节点都是文本节点,直接更新文本内容�? 🎯 数组类型子节点的比对:如果新旧子节点都是数组,Vue 会通过 LIS 算法 来优化节点的重新排列,避免过多的 DOM 操作�?

:::

Vue3 diff 算法做了哪些优化�?

::: details 参考答�?

  • 静态标记与动态节点的区分
    Vue3引入�?静态标记(Static Marking) 机制,通过在模板编译阶段为静态节点添加标记,避免了对这些节点的重复比较。这使得Vue3能够更高效地处理静态内容,减少不必要的DOM操作�?- 双端对比策略
    Vue3的Diff算法采用了双端对比策略,即从新旧节点的头部和尾部同时开始比较,快速定位无序部分。这种策略显著减少了全量对比的复杂度,提升了性能�?- 最长递增子序列(LIS)优�? 在处理节点更新时,Vue3利用最长递增子序列(LIS)算法来优化对比流程。通过找到新旧节点之间的最长递增子序列,Vue3可以减少不必要的DOM操作,从而提高更新效率�?- 事件缓存与静态提�? 事件缓存:Vue3将事件缓存为静态节点,避免每次渲染时重新计算事件处理逻辑,从而减少性能开销�? 静态提升:对于不参与更新的元素,Vue3将其提升为静态节点,仅在首次创建时进行处理,后续不再重复计算�?- 类型检查与属性对�? Vue3在Diff算法中增加了类型检查和属性对比功能。如果节点类型不同,则直接替换;如果类型相同,则进一步对比节点的属性,生成更新操作�?- 动态插槽的优化
    Vue3对动态插槽进行了优化,通过动态节点的类型化处理,进一步提升了Diff算法的效�?
    :::

Vue diff 算法�?React diff 算法的区�?

::: details

Vue �?React �?Diff 算法均基于虚�?DOM,但�?实现策略 �?优化手段 �?设计哲学 上存在显著差异:

1. 核心算法策略对比

维度 React Vue 2/3
遍历方式 单向递归(同层顺序对比) 双端对比(头尾指针优化)
节点复用 类型相同则复用,否则销毁重�? 类型相同则尝试复用,优先移动而非重建
*静态优�? 需手动优化(如 React.memo �? 编译阶段自动标记静态节�?
更新粒度 组件级更新(默认�? 组件�?+ 块级(Vue3 Fragments�?

2. 列表 Diff 实现细节

*a. React 的索引对比策�?

  • **�?key �?*:按索引顺序对比,可能导致无效更�?

    1
    2
    3
    // 旧列表:[A, B, C]
    // 新列表:[D, A, B, C](插入头部)
    // React 对比结果:更新索�?0-3,性能低下
  • **�?key �?*:通过 key 匹配节点,减少移动操�?

    1
    // key 匹配后,仅插�?D,其他节点不更新

*b. Vue 的双端对比策�?

分四步优化对比效率(Vue2 核心逻辑,Vue3 优化为最长递增子序列)�?

  1. 头头对比:新旧头指针节点相同则复用,指针后移
  2. 尾尾对比:新旧尾指针节点相同则复用,指针前移
  3. 头尾交叉对比:旧�?vs 新尾,旧�?vs 新头
  4. 中间乱序对比:建�?key-index 映射表,复用可匹配节�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 旧列表:[A, B, C, D]
    // 新列表:[D, A, B, C]
    // Vue 通过步骤3头尾对比,仅移动 D 到头�?```

    **3. 静态优化机�?*

    **a. Vue 的编译时优化**

    - **静态节点标�?*�? 模板中的静态节点(无响应式绑定)会被编译为常量,跳�?Diff

    ```html
    <!-- 编译�?-->
    <div>Hello Vue</div>

    <!-- 编译�?-->
    _hoisted_1 = createVNode("div", null, "Hello Vue")
  • **Block Tree(Vue3�?*�? 动态节点按区块(Block)组织,Diff 时仅对比动态部�?
    b. React 的运行时优化

  • 手动控制更新�? 需通过 React.memo �?shouldComponentUpdate �?useMemo 避免无效渲染

1
const MemoComp = React.memo(() => <div>Static Content</div>)

*4. 响应式更新触�?

框架 机制 Diff 触发条件
React 状态变化触发组件重新渲�? 父组件渲�?�?子组件默认递归 Diff
Vue 响应式数据变更触发组件更�? 依赖收集 �?仅受影响组件触发 Diff
1
2
3
4
5
6
7
8
9
// Vue:只�?data.value 变化才会触发更新
const vm = new Vue({
data: {
value: 1,
},
})

// React:需显式调用 setState
const [value, setValue] = useState(1)

5. 设计哲学差异

维度 React Vue
控制粒度 组件级控制(开发者主导) 细粒度依赖追踪(框架主导�?
优化方向 运行时优化(Fiber 调度�? 编译时优化(模板静态分析)
适用场景 大型动态应用(需精细控制�? 中小型应用(快速开发)

:::

简�?Vue 组件异步更新的过�?

参考答�?
::: details

Vue 组件的异步更新过程是其响应式系统的核心机制,主要通过 批量更新 �?事件循环 实现高效渲染,具体流程如下:

一、触发阶段:依赖收集与变更通知

  1. 数据变更
    当组件内响应式数据(�?data �?props )被修改时,触发 setter 通知依赖(Watcher)�?

  2. Watcher 入队
    所有关联的 Watcher 会被推入 异步更新队列�?queueWatcher ),Vue 通过 id 去重,确保每�?Watcher 仅入队一次,避免重复更新�?
    二、调度阶段:异步队列处理 3. 异步执行
    Vue 将队列刷新任务放入微任务队列(优�?Promise.then ,降�?setImmediate �?setTimeout ),等待当前同步代码执行完毕后处理�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 伪代码:nextTick 实现
    const timerFunc = () => {
    if (Promise) {
    Promise.resolve().then(flushQueue)
    } else if (MutationObserver) {
    /* 使用 MO */
    } else {
    setTimeout(flushQueue, 0)
    }
    }
  3. 合并更新
    同一事件循环中的多次数据变更会被合并为一次组件更新(如循环中修改数据 100 次,仅触�?1 次渲染)�?
    三、执行阶段:虚拟 DOM �?DOM 更新 5. 组件重新渲染
    执行队列中的 Watcher 更新函数,触发组件的 render 生成新虚�?DOM(VNode)�?

  4. Diff �?Patch
    通过 Diff 算法 对比新旧 VNode,计算出最小化 DOM 操作,批量更新真�?DOM�?
    *四、核心优�?

  • 性能优化:避免频�?DOM 操作,减少重�?重绘�?- **数据一致�?*:确保在同一事件循环中的所有数据变更后,视图一次性更新到最终状态�?- **开发者友�?*:通过 Vue.nextTick(callback) �?DOM 更新后执行逻辑�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    export default {
    data() {
    return {
    count: 0,
    }
    },
    methods: {
    handleClick() {
    this.count++ // Watcher 入队
    this.count++ // 去重,仍只一�?Watcher
    this.$nextTick(() => {
    console.log('DOM已更�?', this.$el.textContent)
    })
    },
    },
    }

点击事件中两次修�?count ,但 DOM 仅更新一次, nextTick 回调能获取最�?DOM 状态�?
*总结流程�?

1
数据变更 �?Watcher 入队 �?微任务队�?�?批量执行 Watcher �?生成 VNode �?Diff/Patch �?DOM 更新

通过异步更新机制,Vue 在保证性能的同时,实现了数据驱动视图的高效响应�?
:::

参考资�?
::: details

:::

Vue 组件是如何渲染和更新�?

::: details 参考答�?
Vue 组件的渲染和更新过程涉及�?模板编译 �?虚拟 DOM �?*构建**�?*更新**和最终的实际 DOM 更新。下面是 Vue 组件渲染和更新的主要步骤�?
1️⃣ 组件渲染过程
Vue 的组件的渲染过程核心是其[模板编译](./vue-inner/#vue-模板编译的过�?过程,大致流程如下:
首先,Vue会通过其响应式系统完成组件�?data、computed �?props 等数据和模板的绑定,这个过程Vue 会利�?Object.defineProperty(Vue2) �?Proxy(Vue3) 来追踪数据的依赖,保证数据变化时,视图能够重新渲染。随后,Vue会将模板编译成渲染函数,这个渲染函数会在每次更新时被调用,从而生成虚�?DOM�?最终,虚拟DOM被渲染成真实�?DOM 并插入到页面中,组件渲染完成,组件渲染的过程中,Vue 会依次触发相关的生命周期钩子�?
2️⃣ 组件更新过程
当组件的状态(�?data、props、computed)发生变化时,响应式数据�?setter 方法会让调用Dep.notify通知所�?订阅者Watcher ,重新执行渲染函数触发更新�?

渲染函数在执行时,会使用 diff 算法(例如:双端对比、静态标记优化等)生成新的虚拟DOM。计算出需要更新的部分后(插入、删除或更新 DOM),然后对实�?DOM 进行最小化的更新。在组件更新的过程中,Vue 会依次触发beforeUpdate、updated等相关的生命周期钩子�?
:::

如何实现 keep-alive 缓存机制

::: details 参考答�?
keep-alive �?Vue 提供的一个内置组件,用来缓存组件的状态,避免在切换组件时重新渲染和销毁,从而提高性能�?

1
2
3
4
5
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>

Vue 3 �?keep-alive 的缓存机制原理如下:

  • 缓存池:keep-alive 内部使用一�?Map 存储已渲染的组件实例,键通常是组件的 key(或 name)�?- 激活与挂起:如果组件切换时已经缓存,直接复用缓存的组件实例;如果组件未缓存,则渲染并缓存新的组件实例�? 此外,keep-alive 还会激活特殊的钩子函数�?- 当组件被缓存时,会触�?deactivated 钩子�?- 当组件从缓存中恢复时,会触发 activated 钩子�?
    一个简单的实现如下�?
    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
    const KeepAliveImpl = {
    name: 'KeepAlive',
    // 已缓存的组件实例�? _cache: new Map(),
    _activeCache: new Map(),

    render() {
    const vnode = this.$slots.default()[0] // 获取动态组件的 vnode
    const key = vnode.key || vnode.type.name

    if (this._cache.has(key)) {
    const cachedVnode = this._cache.get(key)
    this._activeCache.set(key, cachedVnode)
    return cachedVnode
    } else {
    return vnode // 未缓存,直接渲染
    }
    },

    mounted() {
    const key = this.$vnode.key
    if (!this._cache.has(key)) {
    this._cache.set(key, this.$vnode)
    }
    },

    beforeDestroy() {
    const key = this.$vnode.key
    this._cache.delete(key)
    },
    }

:::

为何 ref 需�?value 属�?

::: details 参考答�?
Vue 3 中, ref 之所以需�?.value 属性,主要是因�?Vue 3 使用 Proxy 实现响应式�?Proxy 对对象或数组的每个属性进行深度代理,因此可以追踪嵌套属性的变化。�?Proxy 无法直接处理基本数据类型(如 number �?string �?boolean ),这使�?reactive 无法用于基本数据类型。为了实现基本数据类型的响应式,Vue 设计�?ref ,它将基本数据类型封装为一个包�?value 属性的对象,并通过 getter �?setter 进行依赖追踪和更新。当访问或修�?ref.value 时,Vue 会触发依赖更新�?
:::

Vue 使用

Vue 是国内最普及的前端框架,面试考察概率最高�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

🔥Vue3 �?Vue2 的区别有哪些?说几个主要�?

Vue 3 提供了更现代化、更高性能的架构,通过 Composition API �?Proxy 响应式系统等改进提升了开发体�?:tada::tada::tada:。相比于 Vue 2,它的优势如�?

::: details 参考答�?
性能优化

  • 虚拟 DOM 重构:Vue3的虚拟DOM采用了更高效�?Diff算法,减少了渲染和更新的开销�?

  • Tree-shaking 支持:Vue3的代码结构模块化,支持按需引入,减小了打包体积�?
    Composition API

  • Vue3引入了Composition API,使代码更模块化、复用性更强�?

  • 使用 setup() 方法代替了部分选项�?API,通过函数的方式组织逻辑,代码更加清晰简洁�?
    *响应式系统改�?

  • Vue3使用 Proxy 实现响应式,解决�?Vue2使用Object.defineProperty实现响应式的一些局限性,如无法监听新增属性和数组索引变化�?
    新特性和改进

  • Teleport:可以将组件的DOM渲染到指定的DOM节点之外,例如模态框、通知等�?

  • Fragment 支持:Vue3支持组件返回多个根节点,不再需要单一根节点�?

  • Vue3原生支持 TypeScript,提供更完善的类型推导和开发体验�?

  • Vue3支持为一个组件绑定多�?v-model,并且可以自定义 prop �?event 名称�?
    :::

🔥Vue 组件的通信方式有哪些?

组件之间的通信通常分为父子组件通信和跨组件通信。要注意,vue3 组件的通信方式�?vue2 有一定的区别�?
::: details 参考答�?
父子组件通信

  • Props:父组件通过 props 向子组件传递数据�?- $emit:子组件通过 $emit 向父组件发送事件,并可以传递数据�?- 获取组件实例对象,调用属性或方法�? 💡 Vue 2:通过 this.$parent 获取父组件实例,或通过 this.$children 获取子组件实例�? 💡 Vue 3:通过 ref 引用子组件,直接访问其属性和方法�?- Vue 3 组件支持多个 v-model 绑定和自定义属性名,父子之间的双向绑定更加灵活�?
    跨组件通信

  • Provide / Inject:父组件通过 provide 向后代组件传递数据,后代组件使用 inject 接收数据,适用于深层嵌套组件间的通信�?- vuex:通过全局状态管理库 Vuex 共享状态,实现跨组件通信(vue2)�?- pinia:Pinia �?Vue 3 推荐的全局状态管理库,替代了 Vuex�?- 事件总线(Vue 2):Vue 2 中可以通过Event Bus实现组件间的通信,但�?Vue 3 中不推荐使用�?- 全局事件处理器:通过在根组件$root或全局对象上监听事件,进行跨组件通信(Vue 3 推荐使用外部库,�?mitt)�? :::

🔥Vue 组件的生命周�?

Vue 组件的生命周期是指组件从创建到销毁的整个过程,包括组件的初始化、渲染、更新和销毁等阶段。在Vue2和Vue3中,组件的生命周期有一些区别�?
::: details Vue2

  • 创建阶段

1️⃣ beforeCreate:组件实例刚被创建,数据观测和事�?监听器设置之前。此时无法访�?data �?computed �?methods 等�?2️⃣ created:组件实例已创建,数据观测、事�?监听器设置完成,此时可以访问 data �?computed �?methods 等,通常用于数据初始化�?

  • 挂载阶段

3️⃣ beforeMount:在挂载开始之前,模板已编译, el �?template 已经确定,但尚未渲染�?4️⃣ mounted:组件实例挂载到 DOM 上之后,此时可以访问和操�?DOM�?

  • 更新阶段

5️⃣ beforeUpdate:数据发生变化,DOM 尚未更新。可以在这里做一些数据处理,避免不必要的渲染�?6️⃣ updated:数据变化,DOM 更新后调用。此时组件的 DOM 已经更新,可以访问和操作新的 DOM�?

  • *销毁阶�?

7️⃣ beforeDestroy:组件实例销毁之前。可以在此阶段进行清理工作,例如移除事件监听器、定时器等�?8️⃣ destroyed:组件实例销毁之后。此时,所有的事件监听器和子组件已被销毁�?
:::

::: details Vue3

  • 挂载阶段

1️⃣ onBeforeMount:等效于 Vue 2 中的 beforeMount ,在组件挂载之前调用�?2️⃣ onMounted:等效于 Vue 2 中的 mounted ,在组件挂载之后调用�?

  • 更新阶段

3️⃣ onBeforeUpdate:等效于 Vue 2 中的 beforeUpdate ,在数据更新之前调用�?4️⃣ onUpdated:等效于 Vue 2 中的 updated ,在数据更新并渲染之后调用�?

  • *销毁阶�?

5️⃣ onBeforeUnmount:等效于 Vue 2 中的 beforeDestroy ,在组件卸载前调用�?6️⃣ onUnmounted:等效于 Vue 2 中的 destroyed ,在组件卸载后调用�?
:::

::: tip setup与生命周�?
setup 作为 Vue3 �?Composition API 的一部分, 其内部函数的执行时机早于Mounted钩子�?

1
2
3
4
5
6
7
8
9
<script setup>
import { ref, onMounted } from 'vue';
console.log("setup");
onMounted(() => {
console.log('onMounted');
});
// 执行结果:setup onMounted
</script>

:::

Vue 组件在哪个生命周期发�?ajax 请求�?

�?Vue中,接口请求一般放�?created �?mounted 生命周期钩子中�?
::: details 参考答�?
created 钩子

  • 优点�? �?更快获取数据:能尽早获取服务端数据,减少页面加载时间�? �?SSR 支持:支持服务器端渲染(SSR),�?SSR 环境中不会受到限制�?
  • 缺点
    �?UI 未渲染时发起请求:如果需要操�?DOM 或渲染数据,可能导致闪屏问题

mounted 钩子

  • 优点�? �?DOM 可用:适合需要操�?DOM 或渲染数据后再发起请求的情况,避免闪屏�?
  • 缺点
    �?请求延迟:数据请求会稍微延迟,增加页面加载时间�? �?**SSR 不支�?*:mounted 只在客户端执行,不适用�?SSR 环境�?
    :::

Vue 父子组件生命周期调用顺序

::: details 参考答�?
1️⃣ 创建阶段

  • 父组件:beforeCreate ➡️ created

  • 子组件:beforeCreate ➡️ created

  • 顺序�? 父组件的 beforeCreate �?created 先执�?,子组件�?beforeCreate �?created 后执行�? > 原因:父组件需要先完成自身的初始化(如 data、computed 等),才能解析模板中的子组件并触发子组件的初始化�?
    2️⃣ 挂载阶段

  • 父组件:beforeMount

  • 子组件:beforeMount ➡️ mounted

  • 父组件:mounted

  • 顺序�? �?beforeMount �?�?beforeCreate�?�?created�?�?beforeMount�?�?mounted �?�?mounted

    原因:父组件在挂载前(beforeMount)需要先完成子组件的渲染和挂载,因为子组件是父组件模板的一部分。只有当所有子组件挂载完成后,父组件才会触发自身的 mounted�?
    3️⃣ 更新阶段

  • 父组件:beforeUpdate

  • 子组件:beforeUpdate ➡️ updated

  • 父组件:updated

  • 顺序�? �?beforeUpdate �?�?beforeUpdate �?�?updated �?�?updated

    原因:父组件的数据变化会触发自身更新流程,但子组件的更新必须在父组件更新前完成(因为子组件可能依赖父组件的数据),最终父组件的视图更新完成�?
    4️⃣ 销毁阶�?

  • 父组件:beforeDestroy

  • 子组件:beforeDestroy ➡️ destroyed

  • 父组件:destroyed

  • 顺序�? �?beforeDestroy �?�?beforeDestroy �?�?destroyed �?�?destroyed

    原因:父组件销毁前需要先销毁所有子组件,确保子组件的资源释放和事件解绑,避免内存泄漏�?
    ::: tip
    注:vue3中,setup() 替代�?beforeCreate �?created,但父子组件的生命周期顺序不变�?
    :::

🔥v-show �?v-if 的区�?

::: details 参考答�?

  • *渲染方式�?
    💡v-if:条件为 true 时才会渲染元素,条件�?false 时销毁元素�? 💡v-show:始终渲染元素,只是通过 CSS 控制 display 属性来显示或隐藏�?- *适用场景�?
    💡v-if:适用于条件变化不频繁的场景�? 💡v-show:适用于条件变化频繁的场景�?
    :::

为何v-if和v-for不能一起使用?

v-if �?v-for 不能直接一起使用的原因,主要是因为它们�?*解析优先�? �?逻辑处理 上存在冲突�?
::: details 参考答�?
由于v-for 的解析优先级高于 v-if,同时使�?v-if �?v-for,Vue 首先会循环创建所有dom元素,然后根据条件来判断是否渲染每个元素,这种方式可能导�?Vue 进行大量�?DOM 操作,性能较差。其次,v-for 会为每个循环项创建一个新的作用域,�?v-if 的条件如果依赖于这个作用域内的数据,可能导致判断逻辑异常�?
为避免上述问题,vue官方推荐我们�?v-if 放到 v-for 外层,或者将 v-if 放置�?v-for 内部的单个节点上�?

1
2
3
<div v-if="show">
<div v-for="item in list" :key="item.id">{{ item.name }}</div>
</div>

:::

computed �?watch 有什么区�?

::: details 参考答�?
computed用于计算基于响应式数据的值,并缓存结�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<p>原始值:{{ count }}</p>
<p>计算后的数值:{{ doubledCount }}</p>
</div>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

// 计算属�?const doubledCount = computed(() => count.value * 2)
</script>

watch用于监听数据变化并执行副作用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<p>原始数值:{{ count }}</p>
<button @click="count++">增加数�?/button>
</div>
</template>

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

// 监听�?watch(count, (newVal, oldVal) => {
console.log(`数值从 ${oldVal} 变为 ${newVal}`)
})
</script>
特�? computed watch
*用�? 用于计算基于响应式数据的值,并缓存结�? 用于监听数据变化并执行副作用操作
*返回�? 返回计算结果 不返回值,执行副作�?
缓存机制 只有在访问时才会计算,会缓存计算结果,仅当依赖变化时重新计算 数据变化时立即执行回调,不缓存,每次数据变化都会触发回调
适用场景 计算派生数据,避免不必要的重复计�? 执行异步操作、处理副作用操作,如 API 请求
性能 性能较好,避免重复计�? 每次数据变化时都会执行回调函�?

:::

🔥watch �?watchEffect 的区�?

watch �?watchEffect 都是 Vue 3 中用于响应式数据变化时执行副作用�?API,它们的使用场景和工作机制存在区别:

::: details 参考答�?

  • 依赖追踪方式

watch :需要显式声明依赖,监听指定的数据源;可以监听多个数据源或进行深度监听�?

1
2
3
4
5
6
7
8
9
import { watch, reactive } from 'vue'
const state = reactive({
count: 0,
})
watch(
() => state.count, // 显式声明监听的依�? (newCount, oldCount) => {
console.log(`新�?${newCount} 老�?${oldCount}`)
}
)

watchEffect :会自动追踪 **作用域内所有的响应式依�?*,不需要显式声明依赖�?

1
2
3
4
5
6
7
import { watchEffect, reactive } from 'vue'
const state = reactive({
count: 0,
})
watchEffect(() => {
console.log(`Count 变化�? ${state.count}`) // 自动追踪 `state.count`
})
  • 执行时机

watch :在监听的响应式数据变化后立即执行�?
watchEffect :在 *组件挂载�? 执行一次副作用,并�?*依赖发生变化�? 再次执行�?

  • 适用场景

watch :适用�?监听特定数据 变化并执行副作用的场景,�?API 请求、保存操作等。适合需�?*访问新值和旧�? 进行比较的场景�?
watchEffect :不需要访问旧值,适用�?*自动追踪多个响应式依�? 的副作用,如渲染、自动保存等�?
:::

Vue官方API�?watchEffect

🔥Vue3 ref �?reactive 如何选择�?

ref �?reactive 都是 Vue 3 中用来创建响应式数据�?API,他们的区别及使用场景如下�?
::: details 参考答�?

  • reactive的实现:
    reactive 通过 Proxy 对对象或数组的每个属性进行深度代理,实现响应式。这种设计使�?reactive 能自动追踪所有嵌套属性的变化,但由于 Proxy 无法直接处理基本数据类型(如 number �?string �?boolean ),因此�?reactive 不适用于基本数据类型�?
  • ref的实现:
    为了实现基本数据类型的响应式,Vue 设计�?ref �?ref 会将基本数据类型封装为一个包�?value 属性的对象,通过 getter �?setter 实现响应式依赖追踪和更新。当访问或修�?ref.value 时,Vue 内部会触发依赖更新。此外,对于复杂数据类型(如对象或数组)�?ref 的内部实现会直接调用 reactive ,将复杂数据类型变为响应式�?
    ::: tip 如何选择

Vue官方建议使用 ref() 作为声明响应式状态的主要,因�?reactive 存在以下局限性:

💡有限的值类型:它只能用于对象类�?(对象、数组和�?Map、Set 这样的集合类�?。它不能持有�?string、number �?boolean 这样的原始类型�?💡不能替换整个对象:由�?Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失�?

1
2
3
4
5
6
7
8
9
let state = reactive({
count: 0,
})

// 上面�?({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失�?
state = reactive({
count: 1,
})

💡对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连�?

1
2
3
4
5
6
7
8
9
10
const state = reactive({
count: 0,
})
// 当解构时,count 已经�?state.count 断开连接
let { count } = state
// 不会影响原始�?state
count++

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变�?// 我们必须传入整个对象以保持响应�?callSomeFunction(state.count)

:::

什么是动态组件?如何使用它?

::: details 参考答�?
动态组件是 Vue 提供的一种机制,允许我们根据条件动态切换渲染的组件,而不需要手动修改模板�?在Vue中,我们可以通过 <component> 标签�?:is 属性指定需要渲染的组件�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<!-- 动态渲染组�?-->
<component :is="currentComponent"></component>

<!-- 控制组件切换 -->
<button @click="currentComponent = 'ComponentA'">显示组件A</button>
<button @click="currentComponent = 'ComponentB'">显示组件B</button>
</div>
</template>

<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

// 当前显示的组�?const currentComponent = ref('ComponentA')
</script>

<component> 标签�?:is 属性值可以是�?

  • 被注册的组件�?- 导入的组件对�?- 一般的 HTML 元素

当使�?<component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。如果需要保留动态组件状态,使用 <KeepAlive> 组件即可�?
:::

什么是 slot ,有什么应用场景?

slot �?Vue 中的一种用�?组件内容分发 的机制。它允许父组件向子组件插入内容,从而使组件更加灵活和可复用�?
::: details 参考答�?
在Vue中,插槽的使用方式可以分为四种:默认插槽�?*具名插槽**�?*条件插槽**�?*作用域插�?*�?

  • 默认插槽

默认插槽是最简单的插槽形式,它允许我们将组件的内容传递到组件内部的一个占位符中�?
子组�?MyComponent.vue

1
2
3
4
5
6
<template>
<div>
<p>我是子组件的标题</p>
<slot></slot>
</div>
</template>

父组�?

1
2
3
4
5
<template>
<MyComponent>
<p>这是插槽内容,由父组件传�?/p>
</MyComponent>
</template>

输出结果�?

1
2
3
4
<div>
<p>我是子组件的标题</p>
<p>这是插槽内容,由父组件传�?/p>
</div>
  • 具名插槽

当子组件需要多个插槽时,可以为插槽命名,并由父组件指定内容放置到哪个插槽�?
子组�?MyComponent.vue

1
2
3
4
5
<template>
<slot name="header">默认标题</slot>
<slot>默认内容</slot>
<slot name="footer">默认页脚</slot>
</template>

父组�?

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<MyComponent>
<template v-slot:header>
<h1>我来组成头部</h1>
</template>
<!-- 隐式的默认插�?-->
<p>我来组成身体</p>
<template v-slot:footer>
<p>我来组成尾部</p>
</template>
</MyComponent>
</template>

输出结果�?

1
2
3
4
5
<div>
<h1>我来组成头部</h1>
<p>我来组成身体</p>
<p>我来组成尾部</p>
</div>

v-slot 有对应的简�?# ,因�?<template v-slot:header> 可以简写为 <template #header> 。其意思就是“将这部分模板片段传入子组件�?header 插槽中”�?

  • 条件插槽

我们可以根据插槽是否存在来渲染某些内�?

子组�?MyComponent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>

<div v-if="$slots.default" class="card-content">
<slot />
</div>

<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
  • *作用域插�?

作用域插槽可以让子组件在渲染时将一部分数据提供给插槽,从而实现父组件的插槽访问到子组件的状态�?
子组�?MyComponent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<ul>
<!-- 定义作用域插槽,并将 items 数据传递给父组�?-->
<slot :items="items"></slot>
</ul>
</template>

<script setup>
import { ref } from 'vue'

// 定义数据 items
const items = ref(['华为', '小米', '苹果'])
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<MyComponent>
<!-- 使用作用域插槽,接收子组件传递的 items 数据 -->
<template #default="{ items }">
<li v-for="(item, index) in items" :key="index">
{{ item }}
</li>
</template>
</MyComponent>
</template>

<script setup>
import MyComponent from './MyComponent.vue'
</script>

输出结果�?

1
2
3
4
5
<ul>
<li>华为</li>
<li>小米</li>
<li>苹果</li>
</ul>

Vue官方API�?插槽 Slots

::: tip 应用场景

💡灵活的组件内容插入:
插槽允许我们将内容插入组件中,而无需修改子组件内部逻辑,极大提高了组件的灵活性�?
💡构建通用组件�?比如开发卡片、模态框、列表等组件,使用插槽可以轻松实现内容的自定义。模态框组件可通过插槽自定义标题、正文和按钮�?
💡减少重复代码�?通过插槽,将公共逻辑封装到子组件中,而在父组件中只需插入变化的内容�?
:::

🚀Vue 项目可做哪些性能优化�?

🔍�?Vue 项目中,我们可以利用 Vue 特有的功能和机制实现性能优化�?
::: details 参考答�?
1️⃣ *模板和指令优�?

  • 合理的使�?v-if �?v-show 指令,避免不必要的渲染�?- 使用 v-for 时,尽量提供唯一�?key ,避免重复渲染�?- 使用 v-once 指令,只渲染一次,避免不必要的计算�?- 使用 v-memo 指令,对使用v-for生成的列表进行渲染优化。(vue3.2新增)

2️⃣ 组件优化

  • 合理使用 keep-alive 组件,缓存组件实例,避免重复渲染�?- 使用异步组件加载,减少首屏加载时间�?

    1
    const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'))
  • 配合 Vue Router 使用路由懒加载,实现路由页面按需加载�?- 合理划分组件,提升复用性和渲染性能�?
    3️⃣ *响应式优�?

  • 使用 Object.freeze 冻结对象,避免不必要的响应式�?- 使用 stop 停止 不必要的watchEffect副作用执行,以减少性能消耗�?- watch的优�?
    💡 避免滥用深度监听,降低性能开销�?
    💡 对于频繁触发的响应式数据变化,可以通过防抖和节流优化监听逻辑�?

    1
    2
    3
    4
    5
    6
    7
    8
    import { debounce } from 'lodash'

    watch(
    () => searchQuery,
    debounce((newQuery) => {
    fetchSearchResults(newQuery)
    }, 300)
    )

💡 可以通过返回函数只监听具体的依赖,减少不必要的触发�?

1
2
3
watch([() => user.name, () => user.age], ([newName, newAge]) => {
//...
})

💡 当监听器在某些条件下不再需要时,可以通过返回�?stop 方法手动停止监听,以节省资源

1
2
3
4
5
6
7
8
const stop = watch(
() => data.value,
(newValue) => {
if (newValue === 'done') {
stop() // 停止监听
}
}
)

💡 当多个监听器的回调逻辑类似时,可以合并监听

1
2
3
watch([() => user.name, () => user.age], ([newName, newAge]) => {
//...
})

:::

什么是 nextTick 如何应用�?

::: details 参考答�?
�?Vue.js 中, nextTick 是一个核心工具方法,用于处理 DOM 更新时机问题。它的核心作用是�?在下�?DOM 更新循环结束后执行回调,确保我们能操作到最新的 DOM 状态�?
它的使用场景如下�?

  • 数据变化后操�?DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
async function increment() {
count.value++
// DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
</script>

<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>
  • 在生命周期钩子中操作 DOM
1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
import { ref, onMounted, nextTick } from 'vue'
// 创建 DOM 引用
const element = ref(null)

onMounted(() => {
// 直接访问可能未渲染完�? console.log(element.value.offsetHeight) // 0 或未定义
// 使用 nextTick 确保 DOM 已渲�? nextTick(() => {
console.log(element.value.offsetHeight) // 实际高度
})
})
</script>

注意,在vue2中和vue3的选项�?API中,我们使用this.$nextTick(callback)的方式调用�?

1
2
3
this.$nextTick(() => {
console.log(this.$refs.text.innerText) // "更新后的文本"
})

:::

使用 Vue3 Composable 组合式函数,实现 useCount

::: tip

�?Vue 应用的概念中,�?*组合式函�?*�?Composables) 是一个利�?Vue 的组合式 API 来封装和复用有状态逻辑的函数。它和自定义 React hooks 非常相似�?
:::

使用组合式函数实现如下需求:useCount 是一个计数逻辑管理的组合式函数,它返回一�?count 变量和增加、减少、重置count的方法�?
::: details 参考答�?

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
<script setup>
import { ref } from 'vue'

// 实现 useCount 组合式函�?function useCount() {
const count = ref(0)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
return {
count,
increment,
decrement,
reset,
}
}

// 使用 useCount 组合式函�?const { count, increment, decrement, reset } = useCount()
</script>

<template>
<div>
<h2>计数�? {{ count }}</h2>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
</div>
</template>

:::

使用 Vue3 Composable 组合式函数,实现 useRequest

1
const { loading, data, error } = useRequest(url) // 可只考虑 get 请求

::: details 参考答�?

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
import { ref, computed } from 'vue';
import axios from 'axios';

// 实现 useRequest 组合式函�?function useRequest(url) {
const loading = ref(false); // 请求状�? const data = ref(null); // 响应数据
const error = ref(null); // 错误信息

const fetchData = async () => {
loading.value = true;
error.value = null;
try {
const response = await axios.get(url); /
data.value = response.data;
} catch (err) {
error.value = err.message || '请求失败'; /
} finally {
loading.value = false;
}
};

// 自动触发请求
fetchData();

return {
loading,
data,
error,
};
}

export default useRequest;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import useRequest from './useRequest'
const url = 'https://www.mianshipai.com/'
const { loading, data, error } = useRequest(url)
</script>
<template>
<div>
<h2>请求数据</h2>
<div v-if="loading">加载�?..</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<p>{{ data }}</p>
</div>
</div>
</template>

:::

自定义组件如何实�?v-model

v-model 可以在组件上使用以实现双向绑定�?
::: details vue2
在vue2中,自定义组件使�?v-model ,需要在组件内部定义 value prop,然后通过 this.$emit('input', newValue) 触发更新即可�?

1
2
3
4
5
6
7
8
9
10
<!-- CustomInput.vue -->
<template>
<input :value="value" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
props: ['value'],
}
</script>

使用方式�?

1
<CustomInput v-model="searchText" />

:::

::: details vue3
与vue2类似,vue3自定义组件使�?v-model ,需要在组件内部定义 modelValue prop,然后通过 emit('update:modelValue', newValue) 触发更新

1
2
3
4
5
6
7
8
9
<!-- CustomInput.vue -->
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

使用方式�?

1
<CustomInput v-model="searchText" />

👉注意,从 Vue 3.4 开始,官方推荐的实现方式是使用 defineModel() 宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Child.vue -->
<script setup>
const model = defineModel()

function update() {
model.value++
}
</script>

<template>
<div>父组件的 v-model 值为: {{ model }}</div>
<button @click="update">Increment</button>
</template>

父组件使�?v-model 绑定一个值:

1
2
<!-- Parent.vue -->
<Child v-model="countModel" />

defineModel 是一个便利宏,其返回的值是一�?ref 。它可以像其�?ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用�?

  • 它的 .value 和父组件�?v-model 的值同步;
  • 当它被子组件变更了,会触发父组件绑定的值一起更新�? 根据 defineModel 的特性,我们可以�?v-model 把这�?ref 绑定到一个原�?input 元素上:
1
2
3
4
5
6
7
<script setup>
const model = defineModel()
</script>

<template>
<input v-model="model" />
</template>

此外,v-model 可以接受自定义参数、添加修饰符,组件也可以绑定多个 v-model ,具体用法请参�?> 官网文档:组件 v-model

:::

如何统一监听 Vue 组件报错

::: details 参考答�?
�?Vue 3 中,可以通过 全局错误处理�?(errorHandler) �?生命周期钩子(例�?onErrorCaptured )来统一监听和处理组件中的错误�?

  • 通过全局错误处理�?app.config.errorHandler
1
2
3
4
5
6
7
8
9
import { createApp } from 'vue';
const app = createApp(App);
// 设置全局错误处理�?app.config.errorHandler = (err, instance, info) => {
console.error('捕获到组件错�? ', err);
console.log('发生错误的组件实�? ', instance);
console.log('错误信息: ', info);
};

app.mount('#app');
  • *局部错误捕获(onErrorCaptured�?

onErrorCaptured 钩子可以捕获后代组件传递过程中的错误信�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
import { onErrorCaptured } from 'vue'

onErrorCaptured((err, instance, info) => {
console.error('局部捕获到错误: ', err)
console.log('错误来源组件: ', instance)
console.log('错误信息: ', info)

// 这个钩子可以通过返回 false 来阻止错误继续向上传递�? return false // 如果需要让错误冒泡到全局,省略或返回 true
})
</script>

<template>
<div>
<h2>局部错误捕获示�?/h2>
<ErrorProneComponent />
</div>
</template>

Vue官方API�?onErrorCapturederrorHandler

:::

Vuex �?mutation �?action 有什么区别?

�?Vuex 中, mutation �?action 是用于管理状态的两种核心概念�?
::: details 参考答�?mutation 可以直接修改 store 中的 state值,它只支持同步操作�?Action 不能直接修改 state,而是通过调用 mutation 来间接修改,它用于处理异步操作�?

1
2
3
4
5
6
7
8
9
10
11
12
const store = createStore({
state: {
count: 0, // 定义状�? },
mutations: {
// Mutation 示例(同步)
increment(state, payload) {
state.count += payload
},
},
})

// 组件中调�?this.$store.commit('increment', 5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const store = createStore({
state: {
count: 0, // 定义状�? },
mutations: {
// Mutation:同步修改状�? increment(state, payload) {
state.count += payload
},
},
actions: {
// Action:异步操作,延迟1秒后调用 mutation
asyncIncrement({ commit }, payload) {
setTimeout(() => {
commit('increment', payload) // 提交 mutation 修改状�? }, 1000)
},
},
})

// 组件中调�?this.$store.dispatch('asyncIncrement', 5)

*总结�?

特�? Mutation Action
是否同步 �?同步 �?异步(也可以处理同步�?
是否直接修改 state �?直接修改 �?通过调用 mutation 修改
调用方式 commit('mutationName') dispatch('actionName')
适用场景 简单的状态修�? 异步操作(如 API 调用�?
调试支持 完全支持,易于追�? 依赖�?mutation 的日�?

::: tip ⚠️ 为什么要有这样的区分�?

  • 数据可预测性:通过强制 Mutation 同步修改 State,使得状态变更可追踪
  • 调试友好性:DevTools 可以准确捕捉每次状态快�?- 代码组织:将同步逻辑与异步逻辑分离,提高代码可维护�?
    :::

参考文章:VueX用法快速回顾

Vuex �?Pinia 有什么区别?

::: details 参考答�?
Pinia �?Vuex 都是 Vue 的专属状态管理库,允许用户跨组件或页面共享状态�?

  • 区别
特�? Vuex Pinia
版本支持 Vue 2 �?Vue 3 仅支�?Vue 3(基�?Composition API �?
API 风格 基于传统的对象式 API 基于 Composition API,类似于 setup
语法
模块管理 支持模块化(modules),但语法较复杂 模块化简单,*每个 store 就是一个独立模�?
TypeScript 支持 TypeScript 支持不完善,需手动定义类型 开箱即用的 TypeScript 支持,类型推导更强大
性能 更适合大型项目,但冗余代码较多 更加轻量,性能更好,支持按需加载
状态持久化 需要额外插�? 插件系统更加灵活,支持状态持久化插件
  • 代码对比

📝 vuex

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
// store.js
import { createStore } from 'vuex'

const store = createStore({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++
},
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
})

export default store

vue组件中使�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
export default {
// 计算属�? computed: {
count() {
return this.$store.state.count
},
doubleCount() {
return this.$store.getters.doubleCount
},
},
methods: {
// 同步增加
increment() {
this.$store.commit('increment')
},
// 异步增加
asyncIncrement() {
this.$store.dispatch('asyncIncrement')
},
},
}
</script>

📝Pinia

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// store.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
async asyncIncrement() {
setTimeout(() => {
this.increment()
}, 1000)
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
})

组件中使用:

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { useCounterStore } from './store'
const counter = useCounterStore()
</script>

<template>
<h1>Count的计算�?{{ counter.count }}</h1>
<h2>Double的计算�?{{ counter.doubleCount }}</h2>
<button @click="counter.increment">同步增加</button>
<button @click="counter.asyncIncrement">异步增加</button>
</template>
  • *如何选择�?

对于vue3项目,官方推荐使用pinia。因为它更轻量、TypeScript 支持更好、模块化更简单且拥有更强�?DevTools 支持�?
:::

Vue-router 导航守卫能用来做什么?

::: details 参考答�?
vue Router �?*导航守卫用于在路由跳转过程中对导航行为进�?*拦截**�?*控制。这些守卫在路由进入、离开或更新时执行,可以用于多种场景,确保应用的导航逻辑符合预期。以下是常见的用途:

  • 认证和授�?
    用于检查用户的登录状态或权限,防止未授权用户访问受限页面�?

    1
    2
    3
    4
    5
    6
    7
    8
    router.beforeEach((to, from, next) => {
    const isAuthenticated = !!localStorage.getItem('token')
    if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login') // 未登录,跳转到登录页
    } else {
    next() // 已登录,正常导航
    }
    })
  • 数据预加�?
    在进入路由前预加载必要的数据,确保页面渲染时数据已准备好�?

    1
    2
    3
    4
    5
    router.beforeEach(async (to, from, next) => {
    if (to.name === 'userInfo') {
    await store.dispatch('fetchUserData') // 预加载用户数�? }
    next()
    })
  • 动态修改页面标�?
    根据路由信息动态更改浏览器标签页的标题,提升用户体验�?

    1
    2
    3
    router.afterEach((to) => {
    document.title = to.meta.title || '自定义标�?
    })
  • 动画和加载效�?
    在路由切换时展示加载动画或过渡效果,提升用户体验�?

    1
    2
    3
    4
    5
    6
    7
    router.beforeEach((to, from, next) => {
    store.commit('setLoading', true) // 开始加载动�? next()
    })

    router.afterEach(() => {
    store.commit('setLoading', false) // 结束加载动画
    })
  • 日志记录和分�?
    在路由切换时记录用户行为,用于分析或调试�?

    1
    2
    3
    router.afterEach((to, from) => {
    console.log(`用户�?${from.fullPath} 跳转�?${to.fullPath}`)
    })
  • 防止访问不存在的页面

通过守卫检查路由是否存在,避免导航到无效页面�?

1
2
3
4
5
6
7
8
router.beforeEach((to, from, next) => {
const routeExists = router.getRoutes().some((route) => route.name === to.name)
if (!routeExists) {
next('/404') // 跳转�?404 页面
} else {
next()
}
})

关联文章:5min带你快速回顾、学习VueRouter的使用!
:::

双越老师 1v1 面试咨询

专业解决下面的问题:

  • 初入职场不知道如何写简历,如何写出内容和亮�?- 不知道如何准备面试题,搜出很多资料,但无从下�?- 工作几年,项目都是重复性的,写不出亮点和成�?- 工作�?10 年了,但还是一线开发人员,如何体现个人经验�?- 业余不学习,基础知识很差,面试没信心
  • 工作多年只会 Vue ,不懂算法,没有技术广度和深度
  • 在一个公司呆久了,不知道该如何面试了
  • 刚毕业,没实际项目经�?- ……

作者介�?

大家好我是双越,wangEditor 作者,前百度、滴滴资深前端工程师,PMP,慕课网金牌讲师,博客总流�?500w ,代表作品:

  • wangEditor 开�?Web 富文本编辑器,GitHub Star 18k
  • 划水AI Node 全栈 AIGC 知识库,AI 智能写作,多人协同编�?- 前端面试派 系统专业的面试导航,大厂面试流程,开源免�?
    我从 2017 年开始在慕课网讲授前端面试课程,是全网第一个做前端面试课程的讲师,至今已服务学�?5w+ 看过简�?2000+ 收到很多好评。而且,我此前一直是大厂面试官,非常清楚前端面试的流程和要求�?

缘起

2023 �?5 月开始,我推出了一个简单的 1v1 技术咨询服务,可以聊任何技术话题。陆续有很多人找我咨询,但绝大部分人都是为了优化简历,发掘亮点�?
于是,我就改变了咨询方向,专门进行前端简历优化,帮助发掘个人、项目亮点。没想到效果非常好,他们给出了很好的评价,我也赚到了一些零花钱�?

再后来咨询的人多了,我发现大家除了简历优化之外,还有很多其他面试相关的问题。于是我就根据个人的工作、授课、咨询的经验,制定了一�?*系统全面的面试准备解决方�?*。可以帮大家快速全面的准备面试�?

服务内容

更多详细内容、价格、报名方式等,可 加群 咨询群主 双越

加群讨论答疑

加群方式 https://github.com/mianshipai/mianshipai-web/issues/1

PS. 如果加群失败,可加作�?vx fe-wfp ,备�?面试派


加群可以

  • 讨论面试问题

  • 分享面试经历

  • 围观面试技�?
    加群纪律

  • 禁止刷屏灌水,否则踢�?- 禁止发广�?软文,否则踢�?- 禁止非友善话语,否则踢出

内推工作

提交你的工作机会

如果你的公司有内推机会,你提交给 双越老师 或前端面试派网站,我们帮你推广。你收取简历,你挣�?*推荐�?*(内推成功后,公司一般会给你推荐费几千元)�?

内容格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14

### 标题,格式“城市,公司,职位简称,核心技术栈�?
- base 城市
- 公司:名�?+ 官网
- 职位名称:如高级前端工程师(AI 方向�?- 薪资范围(可选)
- 核心技术栈:如 React umijs (整理几个最核心的,别写太多�?- 学历要求:(如“本科及以上”,没有则写“不限”)
- 工作经验要求:(如�?年以上工作经验”,没有则写“不限”)
- 微信号:(让别人在微信中搜到你,联系你)
- 邮箱:(你的邮箱,最好是工作邮箱,更专业,更吸引人)
- 发布时间(可写当天)和有效期(可选)
- **是否急聘** (不急聘则删掉这一行)

备注:xxx (总结一些重要的备注和要求,套话就别写了,如没有就删掉这一行)

提交方式

  • 专业方式:给前端面试派提交 PR ,首页有提交方�?- 简洁方式:联系作�?双越老师(微�?fe-wfp,备�?提供内推)把内容发给�?

注意事项

  • 提交的内推机会要经过自己的筛选,感觉成功率比较高,不好无脑乱提交
  • 收到简历以后,要自�?review 看是否合适,感觉合适再内推
  • 无论是否内推,都要及时给求职者回复邮件,反馈你的信息

工作机会

  • 此列表按提交时间逆序排序
  • 看到合适的直接发简历到岗位邮箱,简历可要好好写�?- 关注双越老师(微�?fe-wfp,备�?看内推)朋友圈可第一时间看到新内�?
    (在此提交新的内推机会)

北京 京东 前端工程�?金融风控 3年及以上经验 框架不限

  • base 城市
  • 公司:京�?www.jd.com
  • 职位名称:高级前端工程师(金融风控方向)
  • 薪资范围 20-40k
  • 核心技术栈:React Vue Webpack nodeJS
  • 学历要求:不�?- 工作经验要求�?年以上工作经�?- 微信号:17276467721
  • 邮箱:guanhaobin.1@jd.com
  • 发布时间 2026-01-07

北京 京东 高级前端工程�?AI大模型方�?5年及以上经验 框架不限

  • base 北京
  • 公司:京�?www.jd.com
  • 职位名称:高级前端工程师(AI大模型方向)
  • 薪资范围 20-50k
  • 核心技术栈:React Vue Webpack nodeJS
  • 学历要求:不�?- 工作经验要求�?年以上工作经�?- 微信号:17276467721
  • 邮箱:guanhaobin.1@jd.com
  • 发布时间 2026-01-07
    备注:精通Nodejs开发,有VSCode插件、VSCode/Electron/Theia等开发经验者优�?

京东 前端工程�?3年及以上经验 框架不限

  • base 北京 上海 深圳均有岗位
  • 公司:京�?www.jd.com
  • 职位名称:前端工程师
  • 薪资范围 20-50k
  • 核心技术栈:React Vue Webpack nodeJS
  • 学历要求:不�?- 工作经验要求�?年以上工作经�?- 微信号:17276467721
  • 邮箱:guanhaobin.1@jd.com
  • 发布时间 2026-01-07

北京、武汉,大前端,NodeJS/AI

  • base 北京、武�?- 公司:奇安信网神
  • 职位名称:高级前端工程师(NodeJS/AI 方向�?- 核心技术栈:Vue3、TS、Vite、NestJS、LangChain
  • 学历要求:本科及以上
  • 工作经验要求�?年以上工作经�?- 微信号:15549446040
  • 邮箱:shuiqing1991@gmail.com
  • 发布时间�?026/01/04

上海,上海华测导航,高级安卓开发工程师,Java\Kotlin

  • base 上海
  • 上海华测导航:https://www.huace.cn/
  • 职位名称:高级安卓开发工程师
  • 核心技术栈:Android、Java、Kotlin
  • 学历要求:本科及以上
  • 工作经验要求�?年以上Android程序开发经�?- 微信号:Xutaotao1108
  • 邮箱:taotao_xu@huace.cn
  • 发布时间: 2025.11.13

工作内容�?
1、主导产品开发和技术开发项目的需求分析、技术可行性分析及详细设计
2、负责产品子业务系统、模块开发,保证交付成果的进度和质量
3、负责产品维护和技术支撑,保障用户问题及时解决
4、完成领导安排的工作任务以及带教新人

任职资格

1、本科及以上学历,计算机、测绘、GIS专业优先,7年以上Android程序开发经验;
2、掌握常用的设计模式和Android开发框架;熟悉MVC、MVP、MVVM架构�?3、熟悉串口通信,CAN通信,Socket通信和使�?4、熟悉java语言开�?kotlin语言开�?加分:熟悉全站仪知识、GNSS知识,LBS、GIS、地图类APP开�?

武汉 前端专家 React

  • base 武汉
  • 公司:创业公司,创始人是前腾讯美国公司总经理,公司总部在美国,武汉是国内的base�?- 职位名称:前端专�?- 薪资范围:年�?0-60w+期权
  • 核心技术栈:React
  • 邮箱:(15905167024@163.com,微信同号)
  • 发布时间�?025/11/10
  • 是否急聘

JD 岗位描述�?

  • 领导与架构师:负责定义并持续优化Pear前端应用的架构设计,确保平台在扩展过程中
    具备良好的可扩展性、模块化特性和可维护性�?- 提升质量与可靠性:制定测试、可访问性、代码审查和部署卫生的工程标准。推动减
    少前端相关生产问题�?- 自主前端卓越:构建并扩展共享UI系统、组件库和框架,确保商店、事件和内部工具
    之间的一致用户体验�?- 导师与人才发展:通过代码审查、结对编程和结构化反馈指导并培养前端团队。识别每
    位工程师的优势、不足和成长路径�?- 跨部门协作:与产品、设计和后端团队合作,将业务目标转化为可实现、高效和可持�? 的技术策略�?

北京 前端 React RN

  • base 北京
  • 公司:AI 独角兽企业,智谱 AI
  • 职位:前端工程师,也要实习生
  • 核心技术栈:React,React native
  • 邮箱:h532408092@163.com
  • 发布时间�?025.11

JD 岗位描述�?
1、参与公司核�?AI 多模态产品的前端开发工作,保障系统的稳定性与扩展性;
2、与产品经理、UI/UX 设计师协作,高质量完成页面重构与交互实现�?3、推动前端工程化建设,提升构建效率和开发体验;
4、持续优化前端性能,提升用户加载速度与交互体验;
5、关注前沿技术发展,推动团队技术氛围与能力成长�?6、有小程序、跨端框架(�?React Native)经验者优先考虑�?
任职要求�?
1、计算机相关专业本科及以上学历,2 年以上前端开发经验;
2、精�?H5相关技术,熟悉 ES6+ 规范�?3、熟悉主流前端框�?Vue / React / Angular 至少一种;
4、熟�?性能优化手段,了解浏览器渲染机制�?5、熟�?RESTful API、HTTP 协议及前后端协作流程�?6、具备良好的沟通能力和团队协作意识,责任心强,有自我驱动力�?
加分项:

1、有大型 TOC 类项目(电商、社交、直播、短视频等)开发经验;
2、有用户体验敏感度,关注细节和交互体验�?3、有开源社区贡献经�?

杭州,饿了么&淘宝闪购,前端开发专家,React\React Native

上海/杭州/成都 蚂蚁集团 前端

  • base 上海、杭州、成�?- 公司:蚂蚁集�?- 职位名称:前端开�?(AI 技术产品方�?-支付�?- 薪资范围:面试聊
  • 核心技术栈:React,AI Agent
  • 学历要求:本科及以上
  • 工作经验要求�? 年以上工作经�?- 微信号:Careteen
  • 邮箱:wangketing.wkt@antgroup.com
  • 发布时间�?025 �?10 �?- 是否急聘 �?
    备注�?职位描述 1.负责蚂蚁 Al Coding 产品或体验产品的全栈研发工作 2.Al Coding 产品方向:业界最新一代的前端搭建 IDE 、基于大模型�?U!生成、跨栈跨端的演染技术、一句话生成网页的在�?AI 平台、极致性能的前端工�?3.体验产品方向:负责智能 UI、体验巡检、数据可视化、体验分�?Copilot 等技术基�?4.探索 AI 时代前端工程师的领域上限

职位要求 1.精通各种前端技�?包括 HTML5/CSS/JavaScript/Node.js 等。我们招聘前端工程师以及专业 Node 工程�?2.聪明、有好奇心、有求知欲、不喜欢重复的工作、不给自己打「前端」的标签,我们推类产品工程师角色 3.�?Al Coding 产品、低代码平台、DevOps、大流量 Node 应用等经验可作为加分�?有优质的技术组件产出或开源产品者优先�?.具备良好的沟通和团队协作能力,具备提升团队研发效率和性能的能力,具备用户体验的交互优化能力,具备充分的产品负责人意识和项目把控能力�?

【米哈游】协同文档领域前端工程师

  • base 上海
  • 公司:米哈游
  • 职位名称:如高级前端工程师(协同文档方向�?- 薪资范围:面试聊
  • 核心技术栈:React、多人协同、编辑器
  • 学历要求:本科及以上
  • 工作经验要求�?年以上工作经验或者优秀者不�?- 微信号:shiang_zhang
  • 邮箱�?1118728@qq.com
  • 发布时间 2025 �?10 �?- 是否急聘 �?
    备注:多人协同编辑领域前端复杂度够,技术成长天花板高,欢迎来挑战,前后端都�?HC

成都 央企 前端 React

  • base 成都
  • 公司:某央企,稳定不卷,早九晚五,假期超�?- 职位名称:前端工程师
  • 薪资范围:年�?8w
  • 核心技术栈:React
  • 邮箱:(15905167024@163.com,微信同号)
  • 发布时间�?025/10/11

岗位职责:
负责前端展示系统、可视化大屏等项目模块、组件的设计和实�?1�?.负责前端网页的设计、开发和为主,指导、协助团队完成前端UI视觉效果,并与后端进行接口对�?3、负责前端项目的兼容性优化、性能优化、体验优化、攻克技术难�?遵循并优化产品前端代码标准及规范;5、精通浏览器的渲染机制以及J8的性能瓶颈,对于前端系统的性能有评测手段和优化技�?6、研究前沿的前端技术和解决方案,跟踪前端技术和行业发展趋势
要求:统�?11硕士起步,毕�?年及以上

深圳 前端

  • base 深圳
  • 公司 富�?- 职位 前端开发工程师
  • 薪资范围 15-30k
  • 核心技术栈:React、Vue
  • 学历要求:本科及以上学历
  • 工作经验要求�?-3年工作经�?- 邮箱:shawnwang@futunn.com
  • 发布时间�?025-10-10
  • 备注:腾讯系(腾讯战略投资)

职位诱惑�?

  1. 行业内有竞争力的薪酬福利和办公环境;标配 Macbook Pro,前端开发必备利器;

  2. 有挑战的工作,有人性的工作量;

  3. 开放的技术心态,有一定自由度的技术尝试空间;

  4. 公平、开放的团队氛围�?
    岗位职责:

  5. 负责公司研发效能平台的前端开发工作,包括但不限于:性能测试平台,容量管理平台,监控平台等�?2. 负责设计平台的前端系统架构,不断提升开发效率及系统性能和稳定性�?3. 持续的优化前端体验和页面响应速度,提升web界面的友好性和易用性;

任职要求�?

  1. 两年以上web前端开发经验,能独立编写复杂web前端应用
  2. 对计算机基础、数据结构和算法有一定了解;
  3. 能够熟练运用HTML、CSS、JavaScript构建高性能的web应用程序�?4. 熟悉常见前端类库/框架,如Vue/React�?5. 思路清晰,具备良好的沟通能力和团队协作精神�?

广州 乐趣无限 前端 Vue

  • base 广州
  • 公司 乐趣无限
  • 职位 前端开发工程师
  • 薪资范围 15-25k
  • 核心技术栈:Vue
  • 学历要求:专科及以上学历
  • 工作经验要求�?年以上工作经�?- 邮箱:lishizhe@spreadfun.com
  • 发布时间�?025-09-23 有效期:长期
  • 备注:海外社交产品,千万级用户,HC 多,福利�?

深圳 店小�?前端 Vue

  • base 深圳
  • 公司:店小秘 https://www.dianxiaomi.com/
  • 职位名称:高级前端工程师
  • 薪资范围�?8-30K * 13
  • 核心技术栈:Vue, ElementPlus
  • 学历要求:专科及以上学历
  • 工作经验要求�?年以上工作经�?- 邮箱:zhangteng@dianxiaomi.com
  • 发布时间: 2025-08-19
  • 备注:跨境Saas独角兽,HC多,面试流程�?

南京 京东五星电器 前端 React / 后端 Java

  • base 南京
  • 公司:京东五星电�?- 职位名称:前端开发工程师、后端开发工程师
  • 薪资范围�?5-23K * 15
  • 前端核心技术栈:React、Vue
  • 后端核心技术栈:Spring、Spring MVC、Spring Data JPA、Spring Boot、Spring Cloud
  • 邮箱:chenxin559@jd.com
  • 发布时间�?025-08-15 有效期:2025-10-31

JD 岗位描述�?
前端岗位职责

1.负责与产品经理、后端工程师紧密协作,按照公司开发规范,进行桌面端及移动端各类应用的前端开发;

2.参与公司各项目中的前端功能设计及改进�?
3.与设计人员配合,实现高保真设计中的视觉效果;

4.负责前端应用的测试、部署、监控、运维,及持续调优�?
前端任职资格

1.本科以上学历,计算机相关专业�?
2.三年以上前端开发经验;

3.熟练掌握HTML/CSS/JS(ES6+):三年以上React或Vue技术栈的使用经验;

4.熟悉iOS或安卓原生开发的优先,有零售/电商相关行业经验优先�?
后端岗位职责

1.参与项目的需求分析、概要设计、详细设计,技术文档的编写�?
2.按照公司开发规范,负责相应软件系统的功能开发及测试�?
3.负责软件系统的部署、线上运维,服务器监控,管理,调优等�?
4.负责技术架构的持续完善和改进�?
后端任职资格

1.本科以上学历,计算机相关专业�?
2.三年以上Java开发经历;

3.精通Java及Spring,对Spring MVC、Spring Data JPA了解其原理和机制,熟悉Spring Boot、Spring Cloud框架�?
4.精通SQL,熟练使用MySQL、Oracle等数据库,并有一定的SQL优化能力,熟悉Redis等常用NoSQL解决方案,了解各自的优缺点以及使用场景;

5.熟悉Linux操作系统,熟悉Tomcat等应用服务器的部署和配置�?

北京/杭州 阿里/高德 前端/后端/算法

  • base 北京、杭�?- 公司�?阿里巴巴-高德地图
  • 职位名称�?高级前端工程师、高级后端工程师、高级算法等
  • 核心技术栈�?React、Vue
  • 学历要求�?本科及以�?- 工作经验�?2 年以�?- 微信号: i3983761
  • 邮箱�?likangning@live.com
  • 发布时间 2025-09-10 长期

备注:HC 很多�?面试流程快�?

广州 集换�?前端

  • base 广州
  • 公司:集换社
  • 职位名称:高级前端工程师
  • 薪资范围�?8-30k 14�?- 核心技术栈:React/Next.js
  • 学历要求:本科及以上
  • 工作经验要求�?年以上工作经�?- 微信号:heinzz
  • 邮箱:hcxw_2016@163.com
  • 发布时间�?025.09.15

备注:上班时�?2点到18点,6小时工作制,真wlb

上海 众安财产保险 前端

  • base 上海
  • 公司:众安在线财产保险股份有限公�?- 职位名称:前端开�?- 薪资范围�?20-30k * 15�?- 核心技术栈:如 React 、node
  • 学历要求:本科及以上
  • 工作经验要求�?�?- 5�?- 微信号:Big-Zh_97
  • 邮箱:zhouhang@zhongan.com
  • 发布时间: 2025.09.15
  • 内推码:NTA4N8s
  • 内推官网�?https://app.mokahr.com/su/xegsig

东莞 全栈 前端

上海 阿里�?前端技术专�?

  • base 上海
  • 公司:阿里云
  • 职位名称:前端专家岗�?- 核心技术栈:React umijs NextJS webpack/vite�?NodeJS
  • 学历要求:本科及以上
  • 工作经验要求�?年及以上工作经验
  • 微信号:yichanya1
  • 邮箱�?589768261@qq.com
  • 发布时间�?025.9.11

具有大型系统架构设计,有 AI agent 项目架构经验优先,有 vscode 或谷歌插件开发经验加分�?

北京/上海 瓴岳科技 技术岗

  • 北京/上海 瓴岳科技
  • 内推投递链接:https://app.mokahr.com/su/18G8y2
  • 官网投递内推码:NTAgIwh
  • 职位名称�?技术类岗位(Java / Web / AI / 大数�?/ 测试 / 网络安全 / SRE 等)、产品、风控等�?- 学历要求:本科及以上
  • 工作经验要求:校�?社招
  • 微信号:wxid_rttjvta43v3e22
  • 发布时间�?025.9.9

上海 拼多�?前端、后端、算法、客户端

  • base 上海
  • 公司:拼多多
  • 职位名称:前端、后端、算法、客户端
  • 学历要求:本科及以上
  • 工作经验要求:不�?- 邮箱:a2944938071@163.com
  • 微信�?a2944938071
  • 发布时间�?025/09/04

备注:【拼多多集团-PDD校园招聘】内推链接:https://careers.pddglobalhr.com/campus/grad?t=wmllKZBPFx,内推码:wmllKZBPFx。期待你的加入!我们一起,无拼不青春!(通过此链接投递计入内推,内推简历优先筛选~)可以联系邮箱帮忙看进度

南京 前端 Vue

  • base 南京
  • 公司:南数集�?- 职位名称:前端工程师
  • 核心技术栈:Vue3 TS
  • 学历要求:本�?- 工作经验�?年以�?- 邮箱�?38247132@qq.com
  • 微信�?8241518968
  • 发布时间�?025/8/29

深圳 物业系统创业公司 前端 Vue uniapp

  • base 深圳
  • 公司:深圳物业系统创业公�?- 职位名称:前端工程师
  • 核心技术栈:vue3 uniapp
  • 微信号:CccccxyHide
  • 邮箱�?369276645@qq.com
  • 有效期:2025�?�?6号截�?- 薪资面议,需到场面试,不接受线上面试
  • 急聘

JD 岗位描述�?

  1. 要求能熟练使用AI进行开�?2. 具备2�?3年前端开发经验,熟练掌握HTML、JS、CSS等基础知识,能熟练掌握vue3相关语法和uniapp,并有实际项目研发经�?3. 具备较强的分析和解决问题的能力,良好的沟通能�?
    备注:另招聘市场部经理(女)

深圳 物业系统创业公司 后端

  • base 深圳
  • 公司:深圳物业系统创业公�?- 职位名称:IoT 物联网工程师
  • 核心技术栈:Java C C++
  • 微信号:CccccxyHide
  • 邮箱�?369276645@qq.com
  • 有效期:2025�?�?6号截�?- 薪资范围�?5K-20K
  • 需到场面试,不接受线上面试
  • 急聘

JD 岗位描述�?

  1. 计算机相关行�?2. 2年以上物联网/IOT行业经验, 有一定的C, C++语言能力
  2. 与硬件团队紧密合作,参与部分现场硬件设备的安装指�?

北京 AI教育创业团队 前端/Android/后端

  • base 北京
  • 公司:AI教育创业公司
  • 职位名称:前端工程师
  • 核心技术栈:React
  • 邮箱:hr@xiaoluxue.com
  • 微信�?3146669585

备注:

  • 团队核心成员来自字节跳动、阿里巴巴等头部公司
  • 熟悉 AI 开发的优先
  • 同时招聘 Android �?Golang 后端开发人�?

长沙/北京/重庆 前端 蚂蚁金服外包

  • base 暂时: 长沙,北京,重庆 (上海后期可能会有)
  • 公司:博彦科技外包蚂蚁金服
  • 职位名称:前端工程师
  • 薪资范围:看地区
  • 核心技术栈:如 React umijs ,移动端
  • 邮箱�?29877449@qq.com
  • 微信:JavaScript____c
  • 急聘

上海/深圳 React 急聘

  • base 上海/深圳
  • 公司:某3D打印巨头 https://makerworld.com.cn/zh
  • 职位名称:高级前端工程师(to c业务方向�?- 薪资范围:base40-70k�?5-16薪,另有丰厚奖金
  • 核心技术栈:React
  • 微信:L__220321
  • 邮箱:(15905167024@163.com�?- 发布时间�?025/8/26
  • 急聘

JD 岗位描述�?
https://m.zhipin.com/mpa/html/weijd/weijd-job/2475116958c0989503N709-9E1BX?date8=20250826&sid=qr_self_jd&openWeapp=1

要求:统�?11/985本科起步,一线互联网大厂北京,加分项:热情,野心勃勃,富有创业精�?

杭州 恒生电子 前端

备注�?

  • 前端工程师需�?Vue 移动�?- 技术专家需�?Vue React Nodejs

杭州 前端

  • base:杭�?前期需驻场 3-4 个月临汾)
  • 公司:杭州尚情数据技术有限公�?- 职位名称:前端工程师
  • 薪资范围:面议(根据能力与经验,支持股权/期权激励)
  • 核心技术栈:React、Vue
  • 邮箱:sky_zhang_jobs@163.com
  • 发布时间�?025-08-21
  • 是否急聘: �?

北京 瓴岳科技 前端

  • base 北京
  • 公司:瓴岳科技
  • 职位名称:前端工程师
  • 核心技术栈:如 React Vue
  • 邮箱:wry70948@gmail.com
  • 发布时间�?025-08-21 有效期:2025-10-31

JD 岗位描述�?

  • 计算机相关专业,2年以上互联网Web开发经验;能用自身的经历阐述,技术是如何赋能业务的;

  • 熟练掌握JS/CSS、浏览器原理、HTTP协议、网络安全等前端必要的基础知识�?- 精通React或Vue技术原理,对前端工程化与模块化开发有深入理解�?- 对前端稳定性建设、前端性能优化、前端工程效率有一定的理解和相关实践;

  • 具备结果导向思维、强大自驱力、追求卓越精神,具有良好的沟通能力和团队协作精神�?- 有团队管理经验者优先,能带领技术小组不断提升业务理解,进行技术规划,执行关键技术决策,输出高质量的业务代码�?
    加分项:

  • 985�?11本科及以上学历优先;

  • 有前端微服务、渐进式重构、B端组件库搭建、低代码平台落地经验�?- 金融科技、数字信贷、出海业务从业经验;

  • 对AI在前端工作流中的落地有见解、有实践�?
    PS:也有其他技术职位,Java后端、测试、运维、数据仓库工程师等,详情见链接:https://app.mokahr.com/su/18AJPt

杭州 IOS

  • base 杭州
  • 公司:杭州铭师堂
  • 职位名称:iOS开发工程师
  • 核心技术栈:Objective-C 、Swift 、Flutter
  • 邮箱:tangbei@mistong.com
  • 是否急聘:是

JD 岗位描述:https://app.mokahr.com/su/ux33E

还有几个其他技术类的岗位招聘:https://app.mokahr.com/su/6lO2o

上海 科大讯飞 前端 Vue React

  • base 上海
  • 公司:科大讯�?- 职位名称:前端开发工程师(司法)
  • 核心技术栈:Vue�?React
  • 薪资范围�?根据职级定级情况�?0K左右
  • 邮箱:lccong@iflytek.com
  • 发布时间 2025-08-13

JD 岗位描述�?
1、负责项目Web前端核心模块的代码开发,主导复杂功能的技术实现,性能优化等核心问题�?2、主导项目项目Web前端关键模块详细设计,及应用系统架构中涉及web前端部分设计�?3、主导项目前端技术选型、框架搭建,制定跨端(Web/H5/小程�?App)解决方案�?3、负责协同技术平台,推动前端工程化建设,包括脚手架、构建工具链、组件库等,提升团队开发效率�?4、主导制定并推广前端开发规范(代码风格、组件化标准)、文档体系,定期进行技术评审与代码审查�?4、接受短期驻场支撑项目建设,负责快速定位并解决客户现场技术问题的能力,保障项目顺利实施�?任职要求�?1、计算机相关专业本科以上学历�?2�?年以上大前端或前�?nodejs开发经�?主导过至少两个大型项目�?3、精通HTML5/CSS3/ES6+,深入理解浏览器渲染机制、网络协议(HTTP/WebSocket)及性能优化手段�?4、至少熟练掌握React/Vue/Angular中一种框架及其生态(如Vuex/Redux),具备大型项目前端架构设计经验�?5、熟悉Webpack/Vite等构建工具配置优化,具备模块化拆包、Tree Shaking等工程化实践经验�?6、具备跨端开发能力(小程�?Hybrid App/Electron),熟悉移动端适配与性能调优�?7、优秀的沟通与推动能力,能协调多方资源达成技术目标,适应短期出差支持项目�?8、加分项:熟悉微前端架构(qiankun/Module Federation)或具备低代码平台开发经验�?9、加分项:有后端开发经验(Java/Python),理解分布式系统设计原理�?10、加分项:对AI工程化、大数据可视化等前沿领域有实践经验�?
PS. 后端 Java 和测试也有岗位,base 合肥/武汉

南京 锐捷网络 前端 Vue React

  • base 南京
  • 公司:锐捷网�?- 职位:高�?资深前端工程�?- 核心技术栈:Vue React
  • 邮箱:qiyubu@ruijie.com.cn
  • 发布时间�?025.08.11

上海 鸣鸣很忙 前端 React Vue

JD 岗位描述

1、开�?CDP 数据可视化看板(用户分群分析、营销效果监测);
2、实现低代码营销活动页面配置系统(拖拽生�?H5/邮件模板);
3、对接埋点数据校验工具及行为事件可视化回放;
4、优化前端性能(大数据量渲染、实时数据更新)�?任职资格:
1、五年以�?React/Vue 经验,熟练使�?ECharts/D3.js�?2、有 CDP/BI 工具前端开发经验,熟悉 WebSocket 实时通信�?3、掌握前端监控体系(Sentry/埋点 SDK 集成);
4、了�?Google Analytics/Adobe Analytics API 对接�?

广州 鸣鸣很忙 前端 React

  • base 广州
  • 公司:鸣鸣很�?https://www.hnlshm.com/
  • 职位:高级前端开发工程师(新零售 O2O 方向�?- 薪资范围�?0K - 30K
  • 核心技术栈:React
  • 邮箱�?7621713119@163.com
  • 发布时间�?025.08.07

上海 前端 React RN

  • base: 上海(杨浦区创智天地�?- 公司:上海即果信息技术有限公�?- 职位:小宇宙前端开发工程师,前端开发工程师(AI 项目),AI插件全栈工程师(偏前端)
  • 核心技术栈:React,RN,flutter,node.js
  • 邮箱 �?zhanghao@iftech.io
  • 发布时间�?025.08.07
  • JD 岗位描述�? 职位描述
    计算机或相关专业毕业�? 年以上前端开发工作经�? 熟练掌握HTML、JS/TS、CSS 等基础知识
    掌握 React 及相关技术栈,并有实际项目研发经�? 负责 Chrome 插件和网页的业务前端开发及优化迭代职位要求
    具备较强的分析和解决问题的能力,良好的沟通能�? 有良好的编程习惯,对代码交付质量有追求,有持续优化、勇于重构和积极探究的热�? 有从 0 �?1 的前端工程实施经验,有从技术侧主动优化策略、规避风险、发现和解决问题、总结经验的意�? 对前端技术有持续的学习热�? 加分�? 具有开源精神,关注、参与过开源项�? 了解大语言模型应用开发,尝试�?Prompt 编写和模型调用。有独立完成 AI 小项目更�? 具有全栈技术知识储�? 具有大局观,能够从全局的角度出发,积极提出建议或想法,只为做对的事
    足够理性,且兼具共情力
    具有良好的产品品味,热爱观察、思考各类产品,对产品有自己的见�?

深圳 转转 前端实习�?

JD 岗位描述,投递简历:https://neitui.italent.cn/zhuanzhuan/sharejobs/detail?shareId=8d46ba55-85b9-4377-8212-1c749772ba67&language=zh_CN

长沙 鸣鸣很忙 前端 React Vue

北京/上海 百度 前端 React Vue

JD 岗位描述�?

  • 参与大数据产品需求调研,理解业务需求,提供前端技术解决方�?- 负责Web前端系统的架构设计、开发、测试与维护,确保代码质量和性能
  • 推动团队前端技术标准化,提升前端开发效率与产品质量
  • 与产品经理、UI/UE设计师、后端工程师紧密合作,共同推进项目进展,解决技术难�?
    岗位要求�?
  • 计算机及相关专业本科及以上学历,具备足够扎实的前端基础
  • 熟悉 HTML5、CSS3、JavaScript、Typescript,熟悉前端工程化和模块化开�?- 熟悉 React、Vue �?Angular 中至少一种主流前端框架,具备实际项目开发经�?- 良好的沟通能力和团队协作精神,具有独立分析问题解决问题的能力
  • 加分项:在开源社区或技术论坛有贡献者优先;熟悉Linux系统,有一定的后端开发经验(�?Java、Go、Python 等)者优先;有流程可视化、流程编排工具开发经验者优�?
    备注:另招服务端工程师和算法工程师,可联系上面的邮箱

上海 即刻 前端 React

北京 前端 React RN

  • base 北京
  • 公司:AI 独角兽企�?- 职位:前端工程师,也要实习生
  • 核心技术栈:React,React native
  • 邮箱:h532408092@163.com
  • 发布时间�?025.08.07

JD 岗位描述�?
1、参与公司核�?AI 多模态产品的前端开发工作,保障系统的稳定性与扩展性;
2、与产品经理、UI/UX 设计师协作,高质量完成页面重构与交互实现�?3、推动前端工程化建设,提升构建效率和开发体验;
4、持续优化前端性能,提升用户加载速度与交互体验;
5、关注前沿技术发展,推动团队技术氛围与能力成长�?6、有小程序、跨端框架(�?React Native)经验者优先考虑�?
任职要求�?
1、计算机相关专业本科及以上学历,2 年以上前端开发经验;
2、精�?H5相关技术,熟悉 ES6+ 规范�?3、熟悉主流前端框�?Vue / React / Angular 至少一种;
4、熟�?性能优化手段,了解浏览器渲染机制�?5、熟�?RESTful API、HTTP 协议及前后端协作流程�?6、具备良好的沟通能力和团队协作意识,责任心强,有自我驱动力�?
加分项:

1、有大型 TOC 类项目(电商、社交、直播、短视频等)开发经验;
2、有用户体验敏感度,关注细节和交互体验�?3、有开源社区贡献经�?

杭州 游侠客国际旅�?前端 Vue

  • base 杭州
  • 公司:游侠客国际旅游 https://www.youxiake.com/
  • 职位:前端开发工程师(C端:旅游APP方向�?- 核心技术栈:Vue
  • 邮箱:whw@youxiake.com
  • 发布时间�?025.08.07

JD 岗位描述�?
1、实现旅游类C端产�?Web/APP/小程�?精美页面:包�?banner、内容页、详情页、交互地图等模块�?2、打磨视觉动效细节:像素级还原、CSS 动画、Lottie/Canvas、SVG/Three.js 等;
3、结合旅行场景提升性能体验:确保界面在网络不稳、离线环境下依然流畅�?4、与设计师紧密协作,从技术和审美双角度优化视觉方案;
5、跟踪前端前沿技术,推动旅游产品在视觉表现上保持行业领先�?
岗位要求:

1、全日制统招本科及以上学历,计算机或相关专业�?2�?-3�?Web/移动端前端开发经验,至少有一个C端项目上线经验;
3、熟�?Vue3+TypeScript,精通HTML5/CSS3/Javascript(Es6+)�?4、精�?CSS 布局(Flex/Grid)、动�?CSS3 动画库、SVG/Canvas)�?5、对 UI 视觉和动效有敏锐触觉,能精准复刻设计稿�?

长沙 得物 前端 React

JD 岗位描述:https://poizon.jobs.feishu.cn/s/ZOJoP4xWLPk

长沙 得物 前端 React

  • base:长�?- 公司:得�?https://www.dewu.com/
  • 职位:前端开发工程师(数�?交易/汇金�?- 核心技术栈:React
  • 邮箱:o_zhuotuo@dewu.com
  • 发布时间�?025.08.07
  • 急聘

JD 岗位描述:https://poizon.jobs.feishu.cn/s/NjtITcaAHFs

长沙 得物 前端 React

JD 岗位描述:https://poizon.jobs.feishu.cn/s/hUgkDsw1ZSI

反问面试�?

在每个面试环节都可以反问面试官,我们统一放在这里写�?
要根据面试情况,看面试过程是否顺畅,如感觉不好,就别反问了�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

团队使用的技术栈是什么?用什么框架?

和自己的技术栈是否匹配?是否需要自己提前学习熟悉?

如果我加入,我将会负责什么产品和业务�?

判断是不是这个公司的主流业务线?新业务线有机遇也有风险,边角的业务线不要�?

项目团队是否有完善的角色人员�?

如前端、服务端、客户端、QA、UI、PM、运营等…

判断是否是正规部门,别是一个附属部门、附属产�?

9 点以后(或周末)打车是否可以报销�?

就是问会不会经常加班?周末是否双休?

这关系到你的业余生活和学习时�?

对我后续的技术提升有什么建议?

可以观察到面试官对自己的评价

交叉面试

二面结束以后,有可能会再找隔壁部门的高级/资深工程师交叉面试。交叉面试会综合考察候选人的技术能力�?
注意,不一定所有面试都会有交叉面试,但这些面试题还是要刷一遍的,都是常考题�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

求两个数组的交集和并�?

给两个数组,求数组的交集和并�?

1
2
3
4
5
6
7
8
9
10
const arr1 = [1, 3, 4, 6, 7]
const arr2 = [2, 5, 3, 6, 1]

function getIntersection(arr1, arr2) {
// 交集...
}

function getUnion(arr1, arr2) {
// 并集...
}

参考答�?
::: details

要点

  • 交集,转换为 Set ,因�?Set has 比数�?includes 快很多(前�?O(1) 后�?O(n)�?- 并集,直�?add 即可,利�?Set 去重特�?
    代码
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
const arr1 = [1, 3, 4, 6, 7]
const arr2 = [2, 5, 3, 6, 1]

// 交集
function getIntersection(arr1, arr2) {
const res = new Set()
const set2 = new Set(arr2)
for (let item of arr1) {
if (set2.has(item)) {
// 注意,这里要�?Set has 方法,比数组�?includes 快很�? res.add(item)
}
}
return Array.from(res)
}

// 并集
function getUnion(arr1, arr2) {
const res = new Set(arr1)
for (let item of arr2) {
res.add(item) // 利用 Set 自动去重的特�? }
return Array.from(res)
}

// 测试
console.log('交集', getIntersection(arr1, arr2))
console.log('并集', getUnion(arr1, arr2))

:::

数组转树

通常我们有一个包含父子关系的数组,目标是将其转化为树形结构�?
示例数据�?

1
2
3
4
5
6
const arr = [
{ id: 1, parentId: null, name: 'Root' },
{ id: 2, parentId: 1, name: 'Child 1' },
{ id: 3, parentId: 1, name: 'Child 2' },
{ id: 4, parentId: 2, name: 'Grandchild 1' },
]

目标生成�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const tree = [
{
id: 1,
name: 'Root',
children: [
{
id: 2,
name: 'Child 1',
children: [{ id: 4, name: 'Grandchild 1', children: [] }],
},
{
id: 3,
name: 'Child 2',
children: [],
},
],
},
]

参考答�?

::: details

实现思路�?

  1. 遍历数组,将每个元素存储到一个以 id 为键�?Map 中�?2. 再次遍历数组,根�?parentId 将子节点挂载到父节点�?children 属性上�?3. 提取 parentId �?null 的顶层节点作为树的根�?
    代码实现�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function arrayToTree(arr) {
    const idMap = new Map()
    const result = []

    // 初始�?Map
    arr.forEach((item) => {
    idMap.set(item.id, { ...item, children: [] })
    })

    // 构建�? arr.forEach((item) => {
    const parent = idMap.get(item.parentId)
    if (parent) {
    parent.children.push(idMap.get(item.id))
    } else {
    result.push(idMap.get(item.id))
    }
    })

    return result
    }

    console.log(JSON.stringify(arrayToTree(arr), null, 2))

注意点:

  • 确保 parentId �?null 的节点是根节点�?- 避免循环依赖:输入数据需要合法,否则会导致死循环�? :::

树转数组

将树形结构扁平化为数组,保留原有的层级关系�?
示例数据�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const tree = [
{
id: 1,
name: 'Root',
children: [
{
id: 2,
name: 'Child 1',
children: [{ id: 4, name: 'Grandchild 1', children: [] }],
},
{
id: 3,
name: 'Child 2',
children: [],
},
],
},
]

目标生成�?

1
2
3
4
5
6
const arr = [
{ id: 1, name: 'Root', parentId: null },
{ id: 2, name: 'Child 1', parentId: 1 },
{ id: 3, name: 'Child 2', parentId: 1 },
{ id: 4, name: 'Grandchild 1', parentId: 2 },
]

参考答�?

::: details

实现思路�?

  1. 使用递归遍历树�?2. 在每次递归中记录当前节点的 parentId�?3. 将节点及其子节点逐一添加到结果数组中�?
    代码实现�?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function treeToArray(tree, parentId = null) {
    const result = []

    tree.forEach((node) => {
    const { id, name, children } = node
    result.push({ id, name, parentId })
    if (children && children.length > 0) {
    result.push(...treeToArray(children, id))
    }
    })

    return result
    }

    console.log(JSON.stringify(treeToArray(tree), null, 2))

注意点:

  • 递归中需避免重复引用�?- 树节点的 children 属性需要有效(可以为空数组但不能为 undefined)�?
    :::

参考答案:
:::details

区别�?| *特�? | Cookie | LocalStorage | SessionStorage |
|——————-|—————————–|————————–|————————–|
| 写入方式 | 服务端和前端都可写入,不过http-only情况下只允许服务端写�?| 前端 | 前端 |
| 存储大小 | 4KB 左右 | 510MB | 510MB |
| 生命周期 | 手动设置,默认关闭浏览器失效 | 长期保留,直至用户手动清理缓�?| 当前会话,关闭页面清�?|
| 服务器交�?| *�? 随请求发送到服务�?| 不会 | 不会 |
| 数据共享 | 同域下所有页面共�?| 同域下所有页面共�?| 当前页面及子页面共享 |

应用场景�?

  • Cookie :小数据量、需与服务器交互的场景,如保存会话标识(�?token)�?- LocalStorage :需持久化存储、跨页面共享的数据,如用户设置、主题偏好�?- SessionStorage :页面刷新或跳转时临时保存的数据,如表单填写进度�? :::

前端会有哪些安全问题?该如何预防�?

先从应用架构的角度来看,前端可以分为多个核心模块,每个模块都有可能成为攻击目标�?
:::details

  1. 用户界面与数据展示层
  • 攻击风险 :跨站脚本攻击(XSS)、HTML注入、点击劫�?- 防御措施 �? - 严格过滤和转义用户输入,防止恶意代码注入
    • 使用安全模板渲染,避免直接操�?DOM(例如避免使�?innerHTML�? - 配置内容安全策略(CSP),限制脚本来源
    • 设置 X-Frame-Options �?CSP �?frame-ancestors 指令防止点击劫持
  1. 业务逻辑�?
  • 攻击风险 :业务逻辑漏洞导致未授权访问或功能滥用
  • 防御措施 �? - 实现完善的权限校验和身份验证机制
    • 定期进行代码审查和安全测试,及时发现逻辑漏洞
  1. 数据交互�?
  • 攻击风险 :跨站请求伪造(CSRF)、中间人攻击、数据窃�?- 防御措施 �? - 使用 HTTPS 加密数据传输,防止数据在传输过程中被窃取或篡�? - 在请求中加入 CSRF Token,并在服务器端验证请求合法�? - 配置严格�?CORS 策略,确�?API 调用来源可信
  1. 数据存储�?
  • 攻击风险 :本地存储数据泄露(LocalStorage、SessionStorage)、Cookie 劫持
  • 防御措施 �? - 避免在前端存储敏感信息,如必须存储应进行加密处理
    • �?Cookie 设置 HttpOnly、Secure 等属性,降低被脚本读取的风险
  1. 资源加载与依赖管理层
  • 攻击风险 :第三方依赖库漏洞、供应链攻击、外部资源篡�?- 防御措施 �? - 定期更新和审查第三方依赖,及时修补已知漏�? - 使用子资源完整性(SRI)校验机制,确保加载的外部资源未被篡�? - 仅从可信源加载资源,杜绝未知或不受信任的代码注入
  1. 构建与部署流�?
  • 攻击风险 :构建工具或 CI/CD 流程被攻击,导致恶意代码注入
  • 防御措施 �? - 加强构建环境安全管理,定期更新和审查构建工具及依�? - 采用代码签名、版本管理和自动化安全测试,确保发布版本的完整性和可追溯�? - 将安全检测纳�?CI/CD 流程,实现自动化的安全漏洞扫�?
    综上所述,只有从整体应用架构的角度出发,针对各个模块的不同攻击面采取多层次防御措施,才能真正保障前端系统的安全性和稳定性�?:::

常见�?git 命令有哪些?

::: details

  1. 克隆远程仓库 :每个开发者首先需要将远程仓库克隆到本地,以获取项目的最新代码�?

    1
    git clone <远程仓库URL>
  2. 创建并切换到功能分支 :在主分支(�?main �?master)上创建一个新的功能分支,以便在该分支上进行开发,避免直接在主分支上工作�?

    1
    git checkout -b feature-branch
  3. 进行开发并提交更改 :在功能分支上进行开发,完成后将更改添加到暂存区并提交�?

    1
    2
    git add .
    git commit -m "描述本次提交的内�?
  4. *同步远程主分支的最新更�? :在推送之前,先拉取远程主分支的最新更改,以避免推送时发生冲突�?

    1
    2
    git fetch origin main
    git merge origin/main
  5. 推送功能分支到远程仓库 :将本地的功能分支推送到远程仓库,以便其他团队成员可以访问�?

    1
    git push origin feature-branch
  6. *创建 Pull Request(PR�? :在 GitHub 等平台上,从功能分支向主分支发起 PR,请求将功能分支的更改合并到主分支�?

  7. *代码审查与合�? :团队成员对 PR 进行审查,提出修改建议或直接在网页上进行评论。审查通过后,项目管理员或有权限的成员�?PR 合并到主分支�?

  8. 删除已合并的功能分支(可选) :为保持仓库整洁,合并后可考虑删除远程和本地的功能分支�?

    1
    2
    git branch -d feature-branch
    git push origin --delete feature-branch

注意事项 �?

  • 解决冲突 :在拉取远程主分支的更改时,可能会遇到冲突。此时,需要手动解决冲突,然后提交解决后的更改�?
  • 保持分支同步 :在开发过程中,定期从远程主分支拉取最新的更改,以保持本地分支的同步,减少合并时的冲突�?
  • 提交规范 :遵循统一的提交规范,如使用清晰的提交信息,方便团队成员理解每次提交的目的和内容�?
    :::

如何使用 git 多人协作开发?

根据项目的规模、性质和团队需求有不同的安排�?
::: details

  1. 小型项目 (例如:1-3 人的小型开发团队)
  • 共享仓库模型 :大家都对同一个远程仓库进行操作�?- 策略 �? - 可以直接使用 master/main 分支,所有成员都可以在此分支上工作,避免复杂的分支管理�? - 每个开发者都在本地创建自己的功能分支进行开发,完成后合并回 main �?master�? - 提交时保持简洁,并且在每�?push 前与远程仓库同步(git pull --rebase)�?- 具体流程 �? 1. git clone 克隆远程仓库�? 2. git checkout -b feature-branch 创建并切换到自己的功能分支�? 3. 完成功能开发后,git add .git commit -m "Description" 提交本地修改�? 4. 使用 git pull --rebase 更新远程仓库,解决冲突�? 5. 使用 git push 推送到远程仓库�? 6. 其他成员拉取最新的修改,确保项目同步�?
  1. 中型项目 (例如:3-10 人的团队�?
  • *基于分支的协�? :主分支用于发布,功能开发分支(feature branch)和修复分支(bugfix branch)被广泛使用�?- 策略 �? - main �?master 作为生产分支,稳定且可以随时部署�? - 开发人员通过功能分支进行开发,提交合并请求(Pull Requests)前进行代码审查�? - 通过 develop 分支进行日常开发,feature 分支�?develop 分支创建,开发完成后合并�?develop�?- 具体流程 �? 1. git clone 克隆远程仓库�? 2. 切换�?develop 分支并保持更新(git pull)�? 3. 创建自己的功能分�?git checkout -b feature-branch�? 4. 开发完成后,将功能分支推送到远程 git push origin feature-branch�? 5. 创建 Pull Request (PR),请求代码审查并合并�?develop 分支�? 6. 定期�?develop 分支合并�?main �?master 分支进行发布�?
  1. 大型项目 (例如:10 人以上的团队�?
  • Git Flow :这是一个非常适合大团队协作的模型。通过多个分支策略进行管理,确保版本发布和功能开发的平稳过渡�?- 策略 �? - main �?master 用于发布稳定版本�? - develop 分支用于日常开发,所有新功能都在此基础上开发�? - 使用 feature 分支进行独立的功能开发�? - 使用 release 分支准备发布版本,包�?Bug 修复和最后的稳定性验证�? - hotfix 分支用于快速修复生产环境的 bug�?- 具体流程 �? 1. git clone 克隆仓库,切换到 develop 分支�? 2. 创建并切换到新的功能分支 git checkout -b feature/feature-name�? 3. 在功能分支上开发,完成后推送并创建 PR 合并�?develop 分支�? 4. �?develop 分支合并后,测试团队测试新的功能,确保没有问题�? 5. 若需发布新版本,�?develop 创建 release 分支,进行最后的 bug 修复和稳定性测试�? 6. 发布后将 release 分支合并�?main �?develop 分支�? 7. 快速修�?bug 时,�?main 分支创建 hotfix 分支,修复后合并�?main �?develop�?
  1. *开源项�?
  • Fork & Pull Request 模式 :开源项目通常采用这种模式,每个贡献者通过自己�?Fork 进行开发,并通过 Pull Request 提交贡献�?- 策略 �? - 贡献�?Fork 项目仓库到自己的 GitHub(或其他平台)账户�? - �?Fork 的仓库中开发新的功能或修复 bug�? - 完成开发后,创�?Pull Request 提交到原仓库进行审查�? - 项目维护者负责合并经过审查的代码,确保项目稳定�?- 具体流程 �? 1. git fork 仓库到自己的 GitHub 账户�? 2. git clone 自己 Fork 后的仓库�? 3. 创建一个功能分�?git checkout -b feature-name�? 4. 在功能分支上进行开发,提交修改并推送到自己�?Fork 仓库�? 5. 提交 PR 请求合并到原仓库�?main �?develop 分支�? 6. 原项目维护者审查代码,若通过则合并;如果有问题,贡献者根据反馈修改代码�?
  1. 闭源项目
  • 私有仓库 :闭源项目通常使用私有仓库进行管理,团队协作模式与开源项目类似,但可能不需要开放给外部贡献者�?- 策略 �? - 仅限团队内部访问,所有成员都在相同的权限范围内操作�? - 使用与中型项目类似的 Git Flow 或其他基于分支的工作流�?- 具体流程 �? 1. 创建私有仓库并初始化 main �?master 分支�? 2. 开发人员从 develop 分支创建功能分支进行开发�? 3. 完成后提�?PR 进行代码审查�? 4. 审查通过后,合并�?develop 分支并准备发布�? 5. 发布前测试人员验证,发布后合并到 main�?
    总结

  • 小型项目 :共享仓库模型,简单的开发流程�?- 中型项目 :功能分支管理,使用 develop �?feature 分支�?- 大型项目 :Git Flow 模式,多分支管理,发布和修复分支分开�?- *开源项�? :Fork & Pull Request 模式,社区贡献,开放和审查�?- 闭源项目 :私有仓库,常用 Git Flow 或类似工作流,团队内部管理�?
    :::

是否熟悉 Linux 系统,常见的 Linux 命令有哪些?

::: details

  1. *文件与目录操�?
  • ls:列出当前目录下的文件和目录�? - ls -l:显示详细信息(如权限、大小、修改时间)�? - ls -a:显示所有文件,包括隐藏文件�?- cd:切换当前目录�? - cd /path/to/directory:跳转到指定目录�? - cd ..:跳转到上级目录�? - cd ~:跳转到用户的主目录�?- pwd:显示当前工作目录的完整路径�?- mkdir:创建目录�? - mkdir new-directory:创建名�?new-directory 的目录�?- rm:删除文件或目录�? - rm file.txt:删除文件�? - rm -rf directory/:递归删除目录及其内容�?- cp:复制文件或目录�? - cp source.txt destination.txt:复制文件�? - cp -r source-directory/ destination/:递归复制目录�?- mv:移动文件或目录,或重命名�? - mv oldname.txt newname.txt:重命名文件�? - mv file.txt /path/to/destination/:移动文件�?
  1. *文件内容查看与编�?
  • cat:查看文件内容�? - cat file.txt:查�?file.txt 文件的内容�?- less:分页查看文件内容,支持上下翻页�? - less file.txt:分页查看文件内容�?- head:查看文件的前几行�? - head -n 10 file.txt:查看文件前 10 行�?- tail:查看文件的后几行�? - tail -n 10 file.txt:查看文件后 10 行�? - tail -f file.txt:实时查看文件新增的内容(常用于查看日志文件)�?- nano �?vim:命令行文本编辑器�? - nano file.txt:用 nano 编辑文件(易用)�? - vim file.txt:用 vim 编辑文件(功能强大,学习曲线较陡)�?
  1. 权限管理
  • chmod:更改文件或目录的权限�? - chmod 755 file.txt:给文件设置读、写、执行权限�? - chmod +x script.sh:给脚本文件增加执行权限�?- chown:更改文件或目录的所有者�? - chown user:group file.txt:将文件的所有者改�?user,所属组改为 group�?
  1. *Git 与版本控�?
  • git clone:克隆远�?Git 仓库到本地�?- git pull:从远程仓库拉取最新的更新�?- git push:将本地的提交推送到远程仓库�?- git commit:提交代码�?- git status:查看当前工作区的状态�?- git branch:列出所有本地分支�?- git checkout:切换到其他分支�?- git merge:合并分支�?- git log:查看提交历史�?
  1. *系统管理与监�?
  • top:查看系统的实时进程和资源使用情况�?- htoptop 的增强版,图形化界面(需要安装)�?- ps:查看当前正在运行的进程�? - ps aux:查看所有进程�?- kill:终止进程�? - kill PID:杀死指�?PID 的进程�?- df:查看磁盘空间使用情况�? - df -h:以人类可读的格式显示磁盘空间�?- free:查看内存使用情况�? - free -h:以人类可读格式显示内存使用情况�?
  1. 网络操作
  • ping:测试网络连接�? - ping google.com:测试与 Google 的网络连接�?- curl:获取网页或 API 响应�? - curl https://api.example.com:获取指�?URL 的内容�?- wget:从网络下载文件�? - wget http://example.com/file.zip:下载文件�?- netstat:查看网络连接�? - netstat -tuln:查看监听的端口�?- ssh:远程连接到其他服务器�? - ssh user@hostname:通过 SSH 连接到远程服务器�?
  1. 日志查看
  • tail -f /var/log/nginx/access.log:实时查�?Nginx 访问日志�?- journalctl -u service-name:查看特定服务的日志�?- grep:在文件中查找特定的文本模式�? - grep "ERROR" /var/log/nginx/error.log:查�?Nginx 错误日志中的所�?ERROR�?
  1. *包管�?
  • apt(Debian/Ubuntu 系)�? - apt update:更新软件包列表�? - apt upgrade:升级已安装的软件包�? - apt install package-name:安装指定软件包�? - apt remove package-name:卸载指定软件包�?- yum(CentOS/RHEL 系)�? - yum install package-name:安装指定软件包�? - yum update:更新软件包�?
  1. *前端开发相�?
  • Node.js 项目管理 �? - npm install:安装项目依赖�? - npm run build:执行构建命令(如构建生产环境的代码)�? - npm start:启动开发服务器�?- 查看端口占用情况 �? - lsof -i :3000:查看是否有进程在使�?3000 端口�? - kill $(lsof -t -i :3000):杀掉占�?3000 端口的进程�?- 使用 pm2 管理 Node.js 应用 �? - pm2 start app.js:使�?pm2 启动 Node.js 应用�? - pm2 restart app:重启已启动的应用�? - pm2 logs:查看应用日志�?
  1. 自动化与调度
  • cron:定时任务调度�? - 编辑定时任务:crontab -e�? - 查看当前定时任务:crontab -l�?- at:设置一次性任务�? - at now + 5 minutes:在 5 分钟后执行任务�?
    :::

如何调试前端代码�?

::: details
调试前端代码的关键考点是调试流程和工具的掌握�?
调试的目标是识别和修复代码中的问题�?
我们可以从以下几个方面来看:

  1. 调试流程 �?
    • 重现问题 :确保你能准确重现问题,了解问题发生的条件�? - 隔离问题范围 :通过分段注释、简化代码等方式,将问题范围逐渐缩小,便于定位问题�? - *检查错误信�? :查看浏览器的控制台,检查是否有报错或警告,通常浏览器会提供比较详细的错误信息和堆栈跟踪�?
  2. 常用调试工具 �?
    • *浏览器开发者工�? :Chrome、Firefox 等浏览器都自带强大的开发者工具,可以用来检�?DOM、样式、网络请求、控制台输出等�? - 断点调试 :通过在代码中设置断点,逐步执行程序,查看各个变量的值,帮助定位问题�? - 调试输出 :使�?console.log() 或更专业的调试工具(�?debugger)来输出变量或执行状态�?
  3. 调试策略 �? - 逐步排查 :将代码分解成小模块,逐个排查,确认是哪个环节出了问题�? - *日志与错误追�? :在应用中嵌入日志功能,尤其是在生产环境中,利用�?Sentry 这样的工具收集并分析错误信息�?
    最终,调试的关键是高效的定位和快速的修复。你在进行调试时,首先要了解应用的整体架构,然后再根据问题定位具体的模块或环节进行深入分析�?
    :::

移动�?H5 如何抓包网络请求�?

::: details

抓包网络请求的关键考点是如何能够在移动端环境下监控和捕获网络流量�?
尤其是在 H5 应用中,通常需要处理多种协议(�?HTTP、HTTPS)和不同的网络层(如请求、响应、WebSocket 等)�?
我们可以从以下几个方面来进行分析�?

  1. 抓包工具的选择 �?
    • Charles Proxy :一款功能强大的跨平台抓包工具,支持 HTTP、HTTPS 等协议的抓包,能够分析移动端应用的网络请求。可以通过在手机上设置代理,将手机的流量转发到电脑上进行抓包�? - Fiddler :类似于 Charles,也是一个常用的抓包工具,可以抓取本地和移动设备�?HTTP �?HTTPS 请求。通过配置代理服务器,将移动设备的流量通过它进行捕获�? - Wireshark :适用于更低层次的网络分析,能够捕捉各种网络协议的数据包,但需要更高的网络知识�? - Chrome DevTools :如果是调试 Web 移动版的 H5 页面,可以使�?Chrome 的开发者工具,直接通过远程调试功能抓取网络请求�?
  2. 如何抓包 �?
    • 设置代理 :一般需要在移动端设备上设置代理,指向本地电脑的抓包工具。这样,所有移动设备的流量就会通过你的电脑转发,可以在抓包工具中查看和分析�? - 在手机的 Wi-Fi 设置中修改代理设置,填写电脑�?IP 地址和抓包工具的端口(通常�?8888 �?8889)�? - HTTPS 证书问题 :为了抓�?HTTPS 流量,抓包工具(�?Charles �?Fiddler)通常需要安装它们的根证书。安装后,它们就能解�?HTTPS 请求,从而显示加密流量内容�? - *查看和分析数�? :通过抓包工具,你可以看到每个请求的详细信息,如请求头、响应头、请求体、响应体等,帮助你诊断请求问题或调试接口�?
  3. *调试时注意事�? �? - *隐私和安全�? :确保你只在合法的环境下抓包,避免捕获到敏感信息或违反用户隐私�? - *网络状�? :抓包时可以模拟不同的网络环境(如模�?3G�?G、Wi-Fi 等),帮助你排查网络质量对请求的影响�?
    总结来说,抓包的关键是理解代理机制和 HTTPS 解密的过程,掌握合适的工具和流程,才能有效地分析移动端 H5 应用的网络请求�?
    :::

网页重绘 repaint 和重�?reflow 有什么区�?

::: details

网页 *重绘(Repaint�? �?*重排(Reflow�? 的区别可以从性能开销、触发原因和渲染机制三个关键点来分析�?
*1. 定义与触发条�?

  • *重绘(Repaint�?
    重绘是指当元素的样式发生改变,但不影响布局时触发的渲染过程�? 触发条件 :颜色、背景、边框等视觉样式的变化�?
    示例 �?

    1
    element.style.backgroundColor = "red";
  • *重排(Reflow�?
    重排(又称回流)是指当页面布局或结构发生变化时,浏览器重新计算元素的位置和几何尺寸的过程�? 触发条件 :DOM 节点的增删、元素位置的变化、盒模型属性(�?width, height, padding 等)的修改�?
    示例 �?

    1
    element.style.width = "300px";

2. 性能开销

  • *重绘(Repaint�? �? 相对较轻,只需要更新像素信息,不需要重新计算布局�?

  • *重排(Reflow�? �? 开销较大,可能会影响整个页面的渲染,尤其是当涉及到根节点或复杂嵌套布局时�?
    3. 如何优化

  • 减少重绘和重排的方法 �? - 合并样式更改 :通过一次性设置多个样式属性,避免多次触发重排�? - 使用 class 替代内联样式 :批量管理样式更高效�? - *避免频繁读取和写�?DOM 属�? :将读取操作和写入操作分开�? - *使用 CSS3 硬件加�? :如 transform: translateZ(0); 以减少重排�?
    总结来说,重绘和重排的关键区别在于是否涉及布局计算。前者只影响视觉样式,后者会改变页面结构,性能开销也显著不同�?
    :::

网页多标签页之间如何通讯?和 iframe 如何通讯�?

::: details
网页多标签页�?iframe 通讯的关键考点是跨窗口和跨域通信模型的选择,以及不同场景下的适用方法。可以从以下几个方面分析�?
1. 多标签页之间的通讯方法

  • BroadcastChannel API
    同源的多个标签页可以使用 BroadcastChannel 进行消息广播,简单方便�? 示例 �?

    1
    2
    3
    4
    5
    const channel = new BroadcastChannel('my_channel')
    channel.postMessage('Hello from another tab!')
    channel.onmessage = (event) => {
    console.log('Received message:', event.data)
    }
  • LocalStorage + Storage 事件监听
    不同标签页可以共�?localStorage,通过监听 storage 事件实现通讯�? 示例 �?

    1
    2
    3
    4
    5
    6
    window.addEventListener('storage', (event) => {
    if (event.key === 'my_key') {
    console.log('Received message:', event.newValue)
    }
    })
    localStorage.setItem('my_key', 'Hello from another tab!')
  • Service Worker
    通过 Service Worker 作为中介,实现跨标签页通讯。适合 PWA 场景�?

  • WebSocket
    通过服务器中转实现实时通讯,适合跨域或需要长连接的场景�?


2. iframe 通讯方法

  • postMessage API
    最常用的方式,可以跨域发送消息。父页面�?iframe 双向通信都支持�? 示例(父页面�?iframe 发送消息) �?

    1
    2
    3
    4
    5
    const iframe = document.querySelector('iframe')
    iframe.contentWindow.postMessage('Hello iframe!', '*')
    window.addEventListener('message', (event) => {
    console.log('Received from iframe:', event.data)
    })
  • URL Hash 传参
    通过修改 iframe �?URL 哈希来传递参数。适用于简单场景�? 示例 �?

    1
    iframe.src = 'https://example.com#message=Hello'
  • 共享 Cookie �?LocalStorage
    在同源环境下可以通过共享存储机制间接通讯�?


3. 注意事项

  • 安全性考虑 �? - 使用 postMessage 时要指定目标源,避免消息被恶意网站接收�? - 避免直接信任外部传入的数据,做好验证与校验�?- 兼容性与性能 �? - 优先选择现代 API(如 BroadcastChannel)�? - 避免频繁存取 localStorage 导致性能问题�?
    总结来说,选择通讯方法的关键在于是否同源、跨域需求、实时性要求等因素�?:::

什么是 axios 拦截器,能用来做什么?

Axios 拦截器的关键是提供了网络请求生命周期的可控节点,能够有效地简化和规范前端网络请求的管理�?
::: details

*Axios 拦截�? 的关键考点在于它是请求与响应流程中的中间层,用来在网络请求前后进行处理,满足业务需求和性能优化。可以从以下几个方面分析其用途和实践方法�?

1. 什么是 Axios 拦截器?

Axios 拦截器是 Axios 库提供的功能,可以在请求发出之前和响应数据返回之后进行拦截和处理�?拦截器主要分为两类:

  • *请求拦截器(Request Interceptors�?
  • *响应拦截器(Response Interceptors�?

2. Axios 拦截器的应用场景

  • *添加通用请求�?
    可以统一为所有请求添加认�?Token、语言信息等�?

    1
    2
    3
    4
    axios.interceptors.request.use((config) => {
    config.headers['Authorization'] = 'Bearer my-token'
    return config
    })
  • 全局错误处理
    统一处理服务器返回的错误,如用户未登录、网络错误等�?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    axios.interceptors.response.use(
    (response) => response,
    (error) => {
    if (error.response.status === 401) {
    alert('Unauthorized, please login')
    }
    return Promise.reject(error)
    }
    )
  • *数据格式预处�?
    对服务器返回的数据进行格式化,比如从 API 响应中提取有效数据部分�?

    1
    2
    3
    axios.interceptors.response.use((response) => {
    return response.data.result
    })
  • *请求节流与取�?
    防止短时间内重复请求�?

    1
    2
    3
    4
    5
    6
    7
    8
    const CancelToken = axios.CancelToken
    const source = CancelToken.source()

    axios.get('/some-url', {
    cancelToken: source.token,
    })

    source.cancel('Request canceled')

3. 注意事项

  • *拦截器顺�? �? 请求拦截器会按添加顺序执行,而响应拦截器按相反顺序执行�?- 错误处理机制 �? 错误处理函数需要显式调�?Promise.reject(error),否则错误可能被吞掉�?
    :::

是否熟悉 Performance API ,是否了解常见的性能指标�?

::: details

Performance API 的关键考点是提供了一�?Web 标准接口来获取和分析页面性能数据,帮助开发者定位性能瓶颈并优化体验。其中涉及到的常见性能指标�?FP、FCP、LCP 等能直观反映用户的视觉加载体验�?

*1. 什么是 Performance API�?

Performance API 是浏览器提供的内置接口,用于测量网页的加载时间、资源性能和用户体验�?
常用接口

  • performance.now():返回相对于页面加载时间的高精度时间戳�?- performance.mark() �?performance.measure():创建和测量自定义性能标记�?- performance.getEntriesByType():获取特定类型的性能数据,如资源加载或导航时间�?

2. 常见性能指标

  • FP (First Paint)
    首次绘制 ,指用户第一次看到页面内容时的时间点(通常是背景颜色)�?- FCP (First Contentful Paint)
    首次内容绘制 ,页面中首个内容(如文字、图片)被绘制的时间点�? 优化思路 :减�?CSS 阻塞、优化首屏加载内容�?
  • LCP (Largest Contentful Paint)
    *最大内容绘�? ,页面中最大内容元素(如主标题、图片)绘制完成的时间点�? 优化思路 :使用延迟加载策略、优化图像加载�?
  • CLS (Cumulative Layout Shift)
    累积布局偏移 ,页面加载过程中视觉内容意外变化的总量�? 优化思路 :设置明确的宽高,避免懒加载导致布局移动�?
  • FID (First Input Delay)
    首次输入延迟 ,用户第一次交互(如点击按钮)与浏览器响应之间的时间间隔�? 优化思路 :减少主线程阻塞�?
  • TTI (Time to Interactive)
    *可交互时�? ,页面完成加载并能够快速响应用户交互的时间�?

3. 如何使用 Performance API

示例:获�?FCP

1
2
3
4
5
6
7
8
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
entries.forEach((entry) => {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime)
}
})
}).observe({ type: 'paint', buffered: true })

4. 注意事项

  • *兼容�? :不�?API 在浏览器中的支持情况可能不同,需合理降级处理�?- 数据分析 :结�?Lighthouse 等工具进行系统化分析,而不是依赖单一指标�?
    :::

sourcemap 有何作用,如何配置?

::: details

1. Source Map 的作用及配置

Source Map 是一种将压缩、混淆后的代码映射回源代码的文件,用于调试和定位错误。它的主要作用如下:

  • 调试优化 :在开发者工具中看到源代码而非压缩后的代码�?- 错误定位 :在生产环境中准确定位代码错误�?- 性能分析 :配合性能工具对源代码进行优化分析�?
    配置方法
  1. *Webpack 中配�?

    1
    2
    3
    4
    module.exports = {
    mode: 'production',
    devtool: 'source-map', // 生成 Source Map
    }

    常见选项�?

    • source-map: 完整映射,适合生产环境�? - cheap-module-source-map: 生成更快,但映射不包括列信息�? - eval-source-map: 适合开发环境,生成速度快�?
  2. Vite 配置

    1
    2
    3
    4
    5
    export default {
    build: {
    sourcemap: true,
    },
    }

:::

什么是 HTTPS 中间人攻击,如何预防

::: details

*中间人攻击(MITM, Man-In-The-Middle�? 是指攻击者拦截客户端与服务器之间的通信,获取敏感信息或篡改数据�?
攻击原理

攻击者通过伪造证书或劫持网络流量,冒充服务器或客户端,使通信双方无法察觉中间人的存在�?
预防措施

  1. 启用 HTTPS 和强证书验证

    • 配置 TLS 并购买可信的 SSL 证书�? - 使用 HSTS(HTTP Strict Transport Security)强�?HTTPS 访问�?
  2. *证书固定(Certificate Pinning�?
    确保客户端只接受特定 CA 签发的证书�?3. 开�?CORS 配置
    配置严格的跨域策略,减少不必要的网络暴露�?

  3. 安全头部配置

    • 设置 Content-Security-Policy 防止资源篡改�? - 设置 Strict-Transport-Security 强制使用 HTTPS�?
  4. *客户端验�?
    通过双向 TLS(Mutual TLS)验证客户端身份�?
    :::

什么是 OOP ,面向对象三要素是什么?

::: details

1. 什么是 OOP (Object-Oriented Programming)?

面向对象编程(Object-Oriented Programming,简�?OOP)是一种编程范式,通过将程序中的功能和数据封装为对象来实现模块化和复用。对象是具有属性(状态)和方法(行为)的实体,能够与其他对象进行交互。OOP 的核心思想�?通过模拟现实世界的模型来提高软件开发的灵活性与维护�? �?
2. 面向对象的三要素

  1. *封装(Encapsulation�?

    • 概念 :将数据和操作数据的方法绑定在一起,对外隐藏对象的内部实现细节�? - 作用 �? - 提高代码安全性,避免外部直接修改数据�? - 便于代码维护,减少不同模块之间的耦合�? - 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Person {
      constructor(name, age) {
      this._name = name // 私有变量(约定形式)
      this._age = age
      }

      get name() {
      return this._name
      }

      set name(newName) {
      if (newName) this._name = newName
      }
      }
      const person = new Person('Alice', 25)
      console.log(person.name) // Alice
  2. *继承(Inheritance�?

    • 概念 :子类继承父类的属性和方法,从而避免重复代码�? - 作用 �? - 代码复用,减少重复�? - 建立层次结构,实现多态�? - 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class Animal {
      speak() {
      console.log('Animal sound')
      }
      }

      class Dog extends Animal {
      speak() {
      console.log('Woof!')
      }
      }
      const dog = new Dog()
      dog.speak() // Woof!
  3. *多态(Polymorphism�?

    • 概念 :不同对象可以以不同的形式执行相同的方法调用�? - 作用 �? - 增强代码的灵活性�? - 提高系统的扩展性�? - 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      class Shape {
      draw() {
      console.log('Drawing shape')
      }
      }

      class Circle extends Shape {
      draw() {
      console.log('Drawing circle')
      }
      }

      const shapes = [new Shape(), new Circle()]
      shapes.forEach((shape) => shape.draw())

:::

前端常见的设计模式有哪些?以及应用场�?

::: details

  1. *单例模式(Singleton Pattern�?
  • 概念 :保证一个类只有一个实例,并提供全局访问点�?- 应用场景 �? - 全局状态管理,例如 Vuex �?Redux Store�? - 浏览器缓存管理或全局配置�?- 示例 �? ```javascript
    class Singleton {
    constructor() {
    if (!Singleton.instance) {
    Singleton.instance = this
    }
    return Singleton.instance
    }
    }
    const instance1 = new Singleton()
    const instance2 = new Singleton()
    console.log(instance1 === instance2) // 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

    ---

    2. **工厂模式(Factory Pattern�?*

    - **概念** :通过工厂方法创建对象,而不是直接实例化�?- **应用场景** �? - 动态创�?UI 组件�? - 根据配置动态生成实例�?- **示例** �?
    ```javascript
    class Button {
    render() {
    console.log('Render Button')
    }
    }

    class Input {
    render() {
    console.log('Render Input')
    }
    }

    class Factory {
    static createElement(type) {
    switch (type) {
    case 'button':
    return new Button()
    case 'input':
    return new Input()
    default:
    throw new Error('Unknown type')
    }
    }
    }

    const button = Factory.createElement('button')
    button.render() // Render Button

  1. *观察者模式(Observer Pattern�?
  • 概念 :一个对象(观察者)订阅另一个对象(被观察者)的变化�?- 应用场景 �? - 数据绑定和事件系统,例如 Vue 的响应式系统、EventEmitter�? - 实现消息推送功能�?- 示例 �?
    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 Subject {
    constructor() {
    this.observers = []
    }

    subscribe(observer) {
    this.observers.push(observer)
    }

    notify(data) {
    this.observers.forEach((observer) => observer.update(data))
    }
    }

    class Observer {
    update(data) {
    console.log(`Received: ${data}`)
    }
    }

    const subject = new Subject()
    const observer1 = new Observer()
    subject.subscribe(observer1)
    subject.notify('Hello!') // Received: Hello!

  1. *策略模式(Strategy Pattern�?
  • 概念 :将一组算法封装到独立的类中,使得它们可以互换�?- 应用场景 �? - 表单验证策略�? - 动态路由匹配�?- 示例 �?
    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 AddStrategy {
    execute(a, b) {
    return a + b
    }
    }

    class MultiplyStrategy {
    execute(a, b) {
    return a * b
    }
    }

    class Calculator {
    constructor(strategy) {
    this.strategy = strategy
    }

    calculate(a, b) {
    return this.strategy.execute(a, b)
    }
    }

    const calculator = new Calculator(new MultiplyStrategy())
    console.log(calculator.calculate(2, 3)) // 6

  1. *代理模式(Proxy Pattern�?
  • 概念 :通过代理控制对对象的访问�?- 应用场景 �? - API 请求的缓存代理�? - 数据过滤或格式化�?- 示例 �?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const apiProxy = new Proxy(
    {},
    {
    get(target, property) {
    if (property in target) {
    return target[property]
    } else {
    console.log(`Fetching ${property} from API...`)
    // 模拟 API 请求
    return `Data for ${property}`
    }
    },
    }
    )

    console.log(apiProxy.user) // Fetching user from API...

  1. *装饰器模式(Decorator Pattern�?
  • 概念 :在不修改原始对象的情况下动态扩展功能�?- 应用场景 �? - 动态扩展类的功能,例如权限控制�? - React 的高阶组件(HOC)�?- 示例 �?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function withLogging(fn) {
    return function (...args) {
    console.log(`Calling ${fn.name} with`, args)
    return fn(...args)
    }
    }

    function add(a, b) {
    return a + b
    }

    const loggedAdd = withLogging(add)
    console.log(loggedAdd(2, 3)) // Calling add with [2, 3]

  1. *中介者模式(Mediator Pattern�?
  • 概念 :通过一个中介对象来管理不同对象之间的交互,避免对象之间的直接引用�?- 应用场景 �? - 模块之间的解耦,例如前端组件通信�?- 示例 �?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Mediator {
    constructor() {
    this.channels = {}
    }

    subscribe(channel, fn) {
    if (!this.channels[channel]) {
    this.channels[channel] = []
    }
    this.channels[channel].push(fn)
    }

    publish(channel, data) {
    if (this.channels[channel]) {
    this.channels[channel].forEach((fn) => fn(data))
    }
    }
    }

    const mediator = new Mediator()
    mediator.subscribe('event', (data) => console.log(`Received: ${data}`))
    mediator.publish('event', 'Hello from Mediator!') // Received: Hello from Mediator!

  1. *命令模式(Command Pattern�?
  • 概念 :将请求封装为对象,以便参数化不同的请求�?- 应用场景 �? - 撤销与重做功能�? - 任务队列管理�?- 示例 �?
    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 Command {
    constructor(execute, undo) {
    this.execute = execute
    this.undo = undo
    }
    }

    class Light {
    turnOn() {
    console.log('Light is ON')
    }

    turnOff() {
    console.log('Light is OFF')
    }
    }

    const light = new Light()
    const turnOnCommand = new Command(
    () => light.turnOn(),
    () => light.turnOff()
    )
    turnOnCommand.execute() // Light is ON
    turnOnCommand.undo() // Light is OFF

  1. *适配器模式(Adapter Pattern�?
  • 概念 :将一个类的接口转换成另一个接口,以便兼容不同系统�?- 应用场景 �? - 前端组件库的接口适配�? - 数据格式转换�?- 示例 �?
    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
    class OldAPI {
    getData() {
    return 'Old API Data'
    }
    }

    class NewAPI {
    fetchData() {
    return 'New API Data'
    }
    }

    class Adapter {
    constructor(api) {
    this.api = api
    }

    getData() {
    if (this.api instanceof OldAPI) {
    return this.api.getData()
    } else if (this.api instanceof NewAPI) {
    return this.api.fetchData()
    }
    }
    }

    const adapter = new Adapter(new NewAPI())
    console.log(adapter.getData()) // New API Data

  1. *组合模式(Composite Pattern�?
  • 概念 :将对象组合成树形结构,以表示“部�?整体”的层次结构�?- 应用场景 �? - UI 组件树�? - 文件系统管理�?- 示例 �?
    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
    class Component {
    constructor(name) {
    this.name = name
    }

    display() {
    console.log(this.name)
    }
    }

    class Composite extends Component {
    constructor(name) {
    super(name)
    this.children = []
    }

    add(child) {
    this.children.push(child)
    }

    display() {
    console.log(this.name)
    this.children.forEach((child) => child.display())
    }
    }

    const root = new Composite('Root')
    const leaf1 = new Component('Leaf 1')
    const leaf2 = new Component('Leaf 2')

    root.add(leaf1)
    root.add(leaf2)
    root.display()

:::

观察者模式和发布订阅模式的区�?

::: details

核心区别

  • 观察者模式: 两个对象,通知者和观察者,直接关联�?- *发布订阅模式�? 三个对象:事件中心、发布者、订阅者,彼此解耦�?
    详细解释

*1. 定义与结�?

  • *观察者模�? �?
    • 简介: 被观察者(Subject)维护一个观察者列表,状态变化时直接通知观察者(Observers)�? - *结构�? 被观察者与观察者直接关联�? - 简单类比: 像微信群,群主发消息直接通知所有成员�?
  • 发布订阅模式 �? - 简介: 通过事件中心(Event Bus)解耦发布者和订阅者。发布者将消息交给事件中心,事件中心分发给订阅者�? - *结构�? 发布者、事件中心、订阅者三者解耦�? - 简单类比: 像电台广播,听众订阅不同频道,电台播出节目后,只有订阅该频道的听众收到�?
    2. 示例代码

#*观察者模�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Subject {
constructor() {
this.observers = []
}

addObserver(observer) {
this.observers.push(observer)
}

notify(data) {
this.observers.forEach((observer) => observer.update(data))
}
}

class Observer {
update(data) {
console.log(`Received: ${data}`)
}
}

const subject = new Subject()
subject.addObserver(new Observer())
subject.notify('Hello')

#发布订阅模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class EventBus {
constructor() {
this.events = {}
}

subscribe(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
}

publish(event, data) {
;(this.events[event] || []).forEach((callback) => callback(data))
}
}

const eventBus = new EventBus()
eventBus.subscribe('greet', (data) => console.log(`Received: ${data}`))
eventBus.publish('greet', 'Hello Subscribers!')

区别总结

特�? 观察者模�? 发布订阅模式
依赖关系 被观察者直接通知观察�? 发布者与订阅者解�?
中介角色 �? 事件中心
适用场景 状态变化通知 广播消息,模块解�?
耦合�? �? �?
触发方式 被观察者主动触�? 发布者通过事件中心触发

选择建议

  • 观察者模式: 适合对象依赖明确的场景,如模型与视图同步�?- *发布订阅模式�? 适合模块解耦的场景,如前端事件总线�?
    实践应用

  • 观察者模式: Vue 2.x 的响应式系统�?- *发布订阅模式�? Node.js �?EventEmitter、Vue 3.x 的事件总线�?
    总结类比帮助记忆

  • 观察者模式: 直接通知像微信群消息通知所有人�?- *发布订阅模式�? 广播消息像电台分发到不同订阅频道�?
    :::

后端返回 10w 条数据,前端该如何处理?

::: details

当前如果后端一次返�?10w�?00,000)条数据,直接在前端全部加载和渲染会导致内存占用高、页面卡顿、响应缓慢等性能问题。因此,处理这类大数据集时应采用以下策略�?

  1. 服务器端分页和过�?
  • *服务器端分页�? 最理想的方法是让后端只返回当前页面所需的数据,通过接口传递分页参数(例如 page �?limit)。这样前端只处理少量数据,减轻渲染压力�?- 服务器端过滤和排序: 在后端做数据筛选,只返回满足条件的数据,进一步减少前端接收的数据量�?
  1. 前端虚拟化技�?
  • 虚拟滚动(Virtual Scrolling): 如果一定要在前端加载大数据集,采用虚拟列表技术仅渲染当前可见区域的数据项。例如使�?React �?react-window �?react-virtualized;对�?Vue 也有类似的虚拟列表组件�?- 懒加载(Lazy Loading): 仅在用户滚动或交互时动态加载数据,避免一次性加载所有数据�?
  1. 异步数据处理
  • *Web Worker�? 若前端需要对数据进行复杂计算,可以使�?Web Worker 将计算放到后台线程,避免阻塞主线程�?- *分批渲染�? 将数据分为小批次,逐步渲染到页面上,从而分摊渲染开销,保�?UI 流畅�?
  1. 数据缓存与状态管�?
  • *数据缓存�? 对于已加载的数据可以使用内存缓存或�?IndexedDB 缓存,避免重复请求,提升用户体验�?- 状态管理工具: 使用 Redux、Vuex 等状态管理工具,结合分页、懒加载策略管理大数据集的状态,避免全量数据占用内存�?
    总结

最佳实践是尽量避免一次性将所�?10w 条数据传输到前端显示,优先在服务器端进行分页和过滤;如果确实需要在前端处理大量数据,则应采用虚拟化、懒加载和异步处理等技术以保障页面性能和用户体验�?
这种综合策略能有效平衡用户体验与数据处理能力,避免前端资源耗尽的问题�?
:::

一个网页,一开始很流畅,越用越卡顿,你怎么办?

::: details

*1. 内存泄漏检�?

  • *表现�? 内存占用持续上升,不释放�?- *解决方案�?
    • 使用 Chrome DevTools �?Memory 面板 ,录制快�?(Heap Snapshot),查�?DOM 节点、事件监听器是否未被正确清理�? - 确保组件卸载时清除定时器、事件监�?(removeEventListener) 和订阅�? - 避免闭包导致无法释放变量�?

2. 不必要的状态和数据堆积

  • *表现�? 前端状态或数据管理混乱,状态持续增长�?- *解决方案�?
    • 检查状态管理工具(Redux、Vuex)中的数据,避免存储大规模冗余数据�? - 使用数据分页、懒加载策略,减少前端数据体积�? - 清理过期缓存数据,避�?IndexedDB �?LocalStorage 无限堆积�?

*3. 节流和防抖优�?

  • *表现�? 频繁用户交互导致过多重渲染或计算�?- *解决方案�?
    • 使用 throttle �?debounce 控制滚动、输入等高频事件�? - 框架层面可使�?React.memo、Vue �?computed 属性等避免不必要渲染�?

4. DOM 操作与渲染性能优化

  • *表现�? 频繁重排重绘,页面渲染卡顿�?- *解决方案�?
    • 检查是否有频繁�?DOM 操作,优化为批量更新�? - 使用虚拟 DOM 或虚拟滚动(virtual scrolling)技术�? - 尽量避免触发 Layout Throttle 属性(�?offsetWidthgetBoundingClientRect())�?

5. 垃圾回收 (GC) 问题

  • *表现�? 短时间内频繁的卡顿现象�?- *解决方案�?
    • 检查大对象频繁创建导致�?GC 开销�? - 优化对象复用策略,减少不必要的内存分配�?

6. 资源管理优化

  • *表现�? 资源加载越来越慢�?- *解决方案�?
    • 使用 IntersectionObserver 实现懒加载,避免图片和第三方资源过早加载�? - 确保 WebSocket 连接、第三方 SDK 及时关闭�?

*7. 工具与监�?

  • *工具�?
    • *Chrome DevTools�? 内存分析(Heap)、性能分析(Performance)�? - Lighthouse: 检查性能瓶颈�? - 前端监控平台(如 Sentry): 收集卡顿和性能数据�?
      通过系统化分析和优化,逐步解决页面卡顿问题,提升用户体验�?
      :::

一�?web 系统,加载很慢,交给你来优化,你会怎么办?

::: details

*1. 性能分析与瓶颈定�?

  • 使用 Chrome DevTools �?Performance �?Network 面板 ,分析页面加载的时间消耗,找出以下关键瓶颈�? - 白屏时间 (First Paint)过�? - 首屏渲染时间 (Largest Contentful Paint, LCP)过�? - 阻塞资源 导致延迟加载
    • API 请求过多或响应时间长

2. 资源加载优化

  • *减少 HTTP 请求数量�?
    • 合并 CSS、JS 文件,或者采�?Tree Shaking 去掉无用代码�? - 使用雪碧图(Sprites)处理小图片,或者直接改�?SVG�?- 压缩与优化资源:
    • 压缩图片(使�?WebP),优化视频大小�? - 压缩 JS、CSS、HTML 文件,开�?Gzip �?Brotli 压缩�?- *Lazy Loading�?
    • 延迟加载图片和视频,使用 loading="lazy" 属性�? - 采用懒加载策略来加载非首屏模块�?

*3. 静态资源缓�?

  • 启用浏览器缓存:
    设置 Cache-ControlETag 等响应头,缓存静态资源�?
  • *使用 CDN�?
    静态资源分发到 CDN 节点,减少服务器负载�?

4. 网络传输优化

  • 启用 HTTP/2 �?HTTP/3: 并行加载资源,降低传输延迟�?- 减少跨域请求: 优化 API 接口分布,避免预检请求(OPTIONS)�?- 开�?DNS 预解�? 提前解析第三方域名�?

5. 渲染与框架性能优化

  • 服务端渲染(SSR�?静态生成(SSG):
    减少客户端渲染时间,提升首屏性能�?- 组件懒加�? 分离路由和组件,按需加载代码�?- 虚拟化列�? 渲染大量数据时使�?react-window 或类似方案�?

6. 后端�?API 优化

  • 数据库优化:

    • 建立索引,优化查询�? - 数据库结果分页返回�?
  • 接口合并与优化:
    减少多次 API 调用,使�?GraphQL 或批�?API�?

  • *缓存策略�?
    使用 Redis 等缓存热点数据,减轻数据库查询压力�?


7. 用户体验提升

  • 骨架�? 在加载内容前显示占位图,减少白屏时间�?- Loading 动画: 提升用户感知体验�?

*8. 监控与持续优�?

  • 引入性能监控工具:
    • Lighthouse 进行性能分析�? - Sentry 捕获性能问题�? - Web Vitals(FCP、LCP、CLS)实时监控�?
      通过系统化分析和持续优化,可以显著提�?Web 系统的加载性能,带来更流畅的用户体验�?
      :::

你知道哪些前端或 JS 工具链?它们分别什么作用?

::: details

前端�?JavaScript 工具链中包含构建、打包、编译、优化等工具,它们为前端开发提供高效的开发和生产环境支持。以下是一些常见工具及其作用:

*1. 构建与打包工�?

Webpack

  • *作用�? 模块打包工具,支持各种静态资源(JS、CSS、图片等)的处理�?- *特点�? 插件�?Loader 丰富,适用于大型复杂项目�?- *场景�? 传统企业项目、需要自定义复杂配置的大型项目�?
    Vite

  • *作用�? 现代前端开发工具,基于原生 ES 模块,提供极速开发服务器�?- *特点�? 开发阶段几乎无需打包,HMR 快速;生产环境基于 Rollup 打包�?- *场景�? 适合 Vue、React 等现代框架项目�?
    Rollup

  • *作用�? 模块打包工具,擅长打包库和工具类代码�?- *特点�? 输出体积小、支�?ES 模块优化�?- *场景�? 用于打包 JS 库,如工具函数库�?


*2. 编译与转译工�?

Babel

  • *作用�? 将现�?JavaScript 转译为兼容旧浏览器的代码�?- *特点�? 支持最�?JS 特性的编译,如 ES6、TypeScript�?- *场景�? 需要兼容低版本浏览器的项目�?
    SWC (Speedy Web Compiler)

  • *作用�? 超高�?JavaScript �?TypeScript 编译器�?- *特点�? 性能�?Babel 高出数倍,基于 Rust 编写�?- *场景�? 追求编译速度的项目,如大�?React 应用�?
    esbuild

  • *作用�? 超快速构建工具,支持打包与编译�?- *特点�? 性能极高,支�?TypeScript �?JSX 转译�?- *场景�? 极简配置、需要高性能构建的项目�?


*3. 包管理工�?

npm (Node Package Manager)

  • *作用�? 管理项目依赖和包�?- *特点�? 官方 Node.js 包管理工具�?
    Yarn

  • *作用�? 更高效、更安全的包管理工具�?- *特点�? 并行安装依赖,比 npm 更快�?
    pnpm

  • *作用�? 高性能包管理工具�?- *特点�? 去重依赖管理,占用磁盘空间少�?


*4. 静态代码检查与格式�?

ESLint

  • *作用�? 检测和规范 JavaScript 代码风格�?
    Prettier

  • *作用�? 自动格式化代码,保持一致的代码风格�?


*5. 任务自动化工�?

Gulp

  • *作用�? 自动化任务运行器,用于构建流程管理(如压缩、编译等)�?- *特点�? 基于流的构建方式�?
    Parcel

  • *作用�? 零配置的打包工具�?- *特点�? 自动处理依赖关系,适合快速开发原型�?


6. 测试工具

Jest

  • *作用�? JavaScript 单元测试框架�?
    Cypress

  • *作用�? 前端端到端测试工具�?


总结

选择工具需要结合项目规模、性能要求和团队技术栈,例如:

  • 快速开发: 选择 Vite + esbuild�?- *大型复杂项目�? 使用 Webpack + Babel�?- 库开发: Rollup 是不错的选择�?- *追求编译性能�? 考虑 SWC �?esbuild�?
    :::

前端 Leader 面试

二面结束以后,前端团�?Leader 面试,不再关注技术细节,更关注项目组织和设计能力�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

浏览器从输入 url 到显示网页的全过�?

::: tip
这个问题在任何面试环节都有可能被考察,要提前准备�?:::

这个问题的答案内容比较多,回答的时候不一定要答的多细致,但一定不要错过关键步骤�?
关键步骤如下

::: details

  • DNS 解析�?IP 地址
  • 建立 TCP 连接
  • 客户端发�?HTTP 请求
  • 服务端响�?HTTP 请求
  • 浏览器解�?HTML CSS
  • 渲染 DOM
  • 执行 JS 代码,可能会 ajax 加载内容,再次渲�?DOM
  • 加载媒体资源
  • 浏览器缓存机�?
    :::

参考资�?
::: details

:::

�?0 搭建一个前端项目,需要考虑哪些方面

现在创建一�?Vue React 一般都是拿脚手�?cli 一键生成项目,这是最基础的�?
在实际工作中还需要考虑更多的内容。参考答案如�?
::: details

  • 代码仓库,发布到哪个 npm 仓库(如有需要)
  • 技术选型 Vue React �?- 代码目录规范
  • 打包构建 webpack 等,做打包优�?- eslint prettier commit-lint
  • husky pre-commit
  • 单元测试 + 集成测试
  • CI/CD 流程,自动测试,自动发布测试环境,自动部�?- 开发环境,预发布环�?- 开发文档,研发规范

:::

如何实现 ajax 并发请求控制�?

现有 30 个异步请求需要发送,但由于某些原因,我们必须将同一时刻并发请求数量控制�?5 个以内,同时还要尽可能快速的拿到响应结果。应该怎么做?

解题思路

::: details

  • Promise.all 是需要固定数量的,并不适用这个题目的场�?- 可使�?Promise + 递归调用来实现,一个请求完成后继续下一�?
    :::

参考资�?
::: details

:::

PS. 在一般的 web 项目中用不到这个需求,因为浏览器会自带并发请求数量的控�?
::: details

:::

React �?Vue 有什么区别?更擅长哪一个?

主要的区�?
::: details

  • Vue 默认使用 Vue template 模板语法(也支持 JSX);React 默认使用 JSX 语法�?- Vue 对初学者更友好,因为它提供了更多语法糖;�?React 需要良好的 JS 基础�?- React 函数组件�?TS 的支持更加友好,因为它本身就是个 TS 函数;Vue �?setup script 是它的自定义语法�?- React 使用 Hooks ,有调用顺序、闭包陷阱等心智负担;Vue3 使用 composables 组合�?API ,心智负担少�?- React 使用 state ,Vue3 使用 ref ,后者一直有 .value 的心智负�?- 服务端组件方面,React 技术栈�?Next.js �?Remix 做的更好一些;Vue 技术栈�?Nuxt.js 也在发展之中�?
    :::

参考资�?
::: details

:::

这两个框�?lib 虽然有些区别,但在国内的实际工作中使用都没问题,擅长哪个就用哪个�?
但,你如果两个都熟悉,能让你增加很多面试机会,React �?Vue 的都可以去面试�?

如何做好技术选型�?

当在工作中选择一�?语言/框架/工具 时,需要考虑什么?

参考答�?
::: details

  • 社区热门程度,搜索引擎和 StackOverflow 上能搜出多少相关资料�?- 创办时间和发展时间,不要用太新的技术,首先要求�?- 看使用人数,参�?GitHub star 数量�?npm 下载�?- 看社区生态的完善程度,第三方�?UI 、组件、插件等,都是否完善
  • 团队成员的学习成�?—�?这一点很重要,很多人会忽�?
    :::

另,回答这个问题的时候,尽量举一个你实际工作中的例子,会更有说服力�?

如何理解技术方案设计?是否做过技术方案设计?

所谓技术方案设计,就是把你要开发的内容,先写个文档�?PPT 说明一下自己要怎么开发,并和团队成员讨论一下�?
如果就是简单的功能�?bug �?0 几分钟就能搞定的,那不用技术方案设计。但复杂的工作,技术方案设计是很有必要的。因�?
::: details

  • 如果你真的成竹在胸,觉得它很简单,那你写个技术方案设计的文档应该也花不了多少时间,可�?1h 就写完了,项目不会因此而延期的�?- 如果你憋半天写不出一篇技术方案设计文档,那你开发的时候就能很顺利?不可能的。它正好能验证你是不是眼高手低�?- 技术方案评审,多人参加,会更容发现一些:功能重复、性能瓶颈、安全隐患等�?
    :::

技术方案设计一般包含如下部�?
::: details

  • state 数据结构,如会存储在 vuex �?redux �?- 组件 UI 结构,嵌套关系,属性如何传�?- 会用到哪些服务端�?API ,哪些是现有的,哪些需要新开�?- 如有复杂逻辑,说明计算过程,时间复杂�?- 是否有性能隐患�?- 是否有安全隐患?

:::

线上出了严重 bug 你该如何解决�?

正确的处理步�?
::: details

  • 回滚,及时止�?—�?这一步最重要,很多人不知道这一步!!!
  • 通知项目组成员,看谁最近有过上线?—�?线上 bug 一般是最近一次上线导致的
  • 在本地或测试环境浮现 bug,查找原�?- 修复,测试,重新上线
  • 复盘会议,以后如何规避此类问�?—�?复盘会议,也是很多人不知道的

:::

另,如果你项目没有监控报警的话,最好加一个,这样线上�?bug 会及时报警�?

你参与的项目,研发流程是怎样的?

如果是新项目研发,会采用传统的瀑布流形�?
::: details

  • 项目立项
  • 确认需求,编写需求文档,UI 设计�?- 制定技术方�?- 制定研发计划
  • 开�?- 多端联调
  • 测试,修�?bug
  • 上线
  • 总结

:::

如果是已有项目的维护和升级,一般采用敏捷开发方�?
::: details

  • 项目有一个统一的任务列表,分优先级排列
  • 开发人员领取一个任务,负责设计、开发、提测等流程,每一步都会在任务看板中显示步骤和进展
  • 待这个任务上线以后,再领取下一个任�?

:::

你如何保障代码质量?

代码质量和系统稳定性有直接关系,保障代码质量也是高级程序员的必备技能�?
参考答�?
::: details

  • 配置统一�?eslint �?prettier 规则,规范代码格�?- 每次代码合并,都进行 code review ,外加每周一次团�?code review
  • 编写单元测试,提�?commit 时自动触发单元测�?- 使用 Sentry 等平台进行线上错误报警,并及时修复问�?
    :::

是否写过单元测试�?e2e 测试�?

单元测试,是针对某个小型单元(组件,函数),常见的工�?Jest Vitest

e2e 端到端测试,是针对一个系�?UI 和行为进行测试,常见的工�?Cypress Puppeteer

参考资�?
::: details

:::

自定�?DSL 流程�?

请自定义 XML 来描述这个流程图

参考答�?
::: details

1
2
3
4
5
6
7
8
9
10
11
12
13
<chart>
<start-end id="start">开�?/start-end>
<flow id="flow1">流程1</flow>
<judge id="judge1">评审</judge>
<flow id="flow2">流程2</flow>
<start-end id="end">结束</start-end>
<arrow from="start" to="flow1"></arrow>
<arrow from="flow1" to="judge1"></arrow>
<arrow from="judge1" to="flow2">Y</arrow>
<arrow from="judge1" to="end">N</arrow>
<arrow from="flow2" to="end"></arrow>
</chart>
<!-- 另,每个节点还可以加�?x y 的定位信息,尺寸,边框、颜色等 -->

:::

最近在看什么书?或者学什么新技术吗�?

如果你的简历写了自我评价,且在自我评价中写了“热爱技术�?“持续学习”等描述,很可能会被问到这个问题。当然,你不写也不一定就不会被问到,还是提前准备好吧�?
现在和前端相关的一些新技术,有如下方�?

  • Node 全栈,服务端渲染,如 Next.js 框架,这两年发展很迅�?- AI 相关的产品和工具,如各种 AI 聊天、AI 写作,还�?Copilot Cursor �?AI 工具

也可以从你感兴趣的其他领域去说,只要能自圆其说即可�?

你的缺点是什么?

注意,这是个坑!不要傻乎乎的真把自己的缺点说出来,暴露给别人看�?
正确的方式是:说缺点,也要说自己的补救措施,最终体现自己是一个爱学习、要求进步的人�?
推荐的表达方�?
::: details

  • 我觉得自己目前在 xxx 方面还有所欠缺
  • 但我已经开始通过 xxx 学习这方面的知识了,计划 xxx 天以后即可完�?
    :::

项目难点/成绩

面试必考环节,现在应届生都需要有一定的项目经验,实习或者个人项目�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::

项目介绍

恨不得一口气把所有项目细节全部说完�?

如何选择项目

  • 选择一个最能代表你综合技术能力的项目,它就是你的代表作,你的职场脸面
  • 选择贴合当前你面试公司业务的项目,更能激发面试官的兴�?- 不一定是最新做的,1-2 年之内的都可以(2 年前的就太久了)
  • 如果是职场新人,尽量选择大型项目,哪怕自己参与了一小部�?- 如果是职场“老人”,尽量选择自己独立负责的项目,体现自己独挡一面的能力

如何介绍

按照如下模板,用自然平和的预期,�?90s 之内说完 —�?是的,别超过 2min

  • 项目的背景,名称,服务对象,主要功能和流�?- 你在这个项目中的角色和职责,做了哪写主要的工作,取得了哪写成就(简单说,别深入细节�?- 项目的技术栈:框�?+ 重要的第三方库或服务

不要擅自深入细节。如果你非常想表达技术细节,要先把上述内容讲完,然后问面试官一句:这就是我的项目,你看我表达清楚了吗?其实还有一些技术细节,如果你有兴趣的话,我想再说说。

面试,是相互的沟通,不是你单方面的倾诉�?

注意事项

  • 对面试官来说,你是个陌生人,你的项目他也不知道。首先,你要让他知道这个项目是个啥!
  • 不要一上来就讲技术,当我连背景、功能都不知道的情况下,技术我不可能听明白
  • 不要着急深入细节,不要想着一口气把能说的全部说完,说太多太快对方根本听不进去

项目成绩

没啥成绩,我感觉在公司这几年啥也没干

项目成绩是什么?

思考这个问题很简单,从你入职到现在,你为项目/公司贡献了哪些东西?主要的东西,太细节的就忽略了。例�?

  • 开发了 a b c 几个项目/功能
  • �?xxx 模块进行优化
  • 攻坚 xxx 难题
  • 获得�?xxx 奖励

公司每月给你开那么多工资,你怎么可能没有成绩呢?公司不养闲人。你认真总结一下,内容肯定很多�?

如何表达

  • 概括说出你所有的成绩
  • 找一个最具有代表性的,详细说
    • 背景,需�? - 遇到的问�? - 解决方案,技术方�? - 结果
  • 最后总结:自己是个有价值的�?

注意事项

  • 不要下意识的以为自己没有成绩,每个人都有自己的价�?- 这个问题更多的是考验你的总结和表达能力,并不是非得说出一些高大上的成�?- 日常工作中要写日报、周报、月报,多记录,就便于最后整理输�?

项目难点

没啥难点,都是增删改�?

项目难点是什么?

任何让你工作不顺利的事情,都可以总结为项目难点。例�?

  • 遇到一�?bug 加班到深夜才排查出来
  • 一个功能非常复杂,两周才开发完
  • 一个项�?模块,需要开会召集各个部门的人,多次协调方案才最终通过

如何表达

推荐使用 STAR 模型�?

  • Situation 背景和需�?- Task 任务和目�?- Action 技术方�?- Result 结果

面试之前建议准备 2 个项目难点,并用 STAR 模型写出大纲�?

注意事项

  • 人,对于做过的事情,都会觉得很简单,不要被这种本能所误导
  • 如果是职场新人,多从具体的技术方向寻找难�?- 如果是职场“老人”,多从宏观的项目、架构、流程、解决方案这个层面来总结
0%