前端面试题大全:JavaScript + Vue3 + React + TypeScript + 工程化 + 性能优化

本文整理前端高频面试题,按照 JavaScript / ES6+、Vue3、React、TypeScript、Webpack / Vite / 工程化、前端性能优化、CSS / HTML 等模块分类。

推荐学习方式:先理解原理,再记忆标准回答,最后结合项目经验进行扩展。


📚 目录


一、JavaScript / ES6+

1. JavaScript 事件循环:代码输出顺序

题目

js 复制代码
console.log(1)

async function fn() {
  await 100
  console.log(4)
}

fn()

console.log(5)

Promise.resolve().then(() => {
  console.log(3)
})

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

输出结果

text 复制代码
1 → 5 → 3 → 4 → 2

原因

JavaScript 的执行顺序可以简单理解为:

text 复制代码
同步任务
    ↓
微任务
    ↓
宏任务

执行过程:

  1. console.log(1),输出 1。
  2. 执行 fn(),遇到 await,后续代码进入微任务队列。
  3. 继续执行同步代码,输出 5。
  4. 同步代码执行完毕,执行微任务:
    • Promise.then() 输出 3
    • await 后面的代码输出 4
  5. 最后执行宏任务 setTimeout,输出 2。

面试回答

JavaScript 会优先执行同步任务,同步任务执行完成后清空微任务队列,最后执行宏任务。await 后面的代码会被放入微任务队列,因此最终输出顺序是 1 → 5 → 3 → 4 → 2。


2. 循环绑定点击事件,为什么输出最后一个索引?

错误示例

js 复制代码
for (var i = 0; i < 3; i++) {
  buttons[i].onclick = function () {
    console.log(i)
  }
}

点击按钮时可能全部输出:

text 复制代码
3
3
3

原因

var 没有块级作用域,整个循环共享同一个 i。

循环结束后:

text 复制代码
i = 3

点击事件是之后才触发的,因此所有回调访问的都是同一个变量 i,最终拿到的都是 3。

解决方案一:使用 let

js 复制代码
for (let i = 0; i < 3; i++) {
  buttons[i].onclick = function () {
    console.log(i)
  }
}

let 具有块级作用域,每一轮循环都会创建独立的变量。

解决方案二:使用 IIFE

js 复制代码
for (var i = 0; i < 3; i++) {
  ;(function (index) {
    buttons[index].onclick = function () {
      console.log(index)
    }
  })(i)
}

面试回答

var 没有块级作用域,循环中的所有回调共享同一个 i。事件触发时循环已经结束,i 已经变成最终值。可以使用 let 的块级作用域,或者通过 IIFE 创建闭包保存每一轮的索引。


3. 不同场景下的 this 指向

调用方式 this 指向
普通函数直接调用 非严格模式:window;严格模式:undefined
箭头函数 没有自己的 this,继承外层作用域
对象方法调用 调用该方法的对象
new 调用 新创建的实例对象
call/apply/bind 手动传入的对象

普通函数

js 复制代码
function fn() {
  console.log(this)
}

fn()

对象方法

js 复制代码
const user = {
  name: 'Tom',
  say() {
    console.log(this.name)
  }
}

user.say()

此时:

text 复制代码
this === user

箭头函数

js 复制代码
const user = {
  name: 'Tom',
  say: () => {
    console.log(this.name)
  }
}

箭头函数没有自己的 this,会继承定义时外层作用域的 this。

面试重点

this 的指向主要取决于函数的调用方式,而箭头函数是特殊情况,它没有自己的 this,也不能通过 call/apply/bind 修改。


4. 防抖和节流的区别

防抖 Debounce

连续触发时不断重新计时,停止触发一段时间后只执行一次。

适用场景:

  • 输入框搜索联想
  • 搜索请求
  • 窗口 resize
js 复制代码
function debounce(fn, delay) {
  let timer

  return function (...args) {
    clearTimeout(timer)

    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

节流 Throttle

固定时间内最多执行一次。

适用场景:

  • 滚动事件
  • 拖拽
  • 按钮防重复提交

一句话记忆

text 复制代码
防抖:等你停止操作后执行
节流:固定时间执行一次

5. ES5 寄生组合继承

js 复制代码
function Parent(name) {
  this.name = name
}

Parent.prototype.say = function () {
  console.log('hello')
}

function Child(age) {
  Parent.call(this, 'parentName')
  this.age = age
}

Child.prototype = Object.create(Parent.prototype)

Child.prototype.constructor = Child

核心逻辑

text 复制代码
Parent.call(this)
        ↓
继承父类实例属性

Object.create(Parent.prototype)
        ↓
继承父类原型方法

优点

  • 避免重复调用父类构造函数
  • 避免子类原型上出现多余的父类实例属性
  • 解决普通组合继承的问题

6. 如何判断一个变量是否为数组?

instanceof

js 复制代码
arr instanceof Array

缺点:跨 iframe 场景可能失效。

Array.isArray

js 复制代码
Array.isArray(arr)

最推荐。

Object.prototype.toString

js 复制代码
Object.prototype.toString.call(arr) === '[object Array]'

兼容性好,可以准确判断多种内置类型。

constructor

js 复制代码
arr.constructor === Array

缺点是 constructor 可以被手动修改。


7. 浅拷贝和深拷贝

浅拷贝

只复制第一层属性。

js 复制代码
const obj = {
  name: 'Tom',
  info: {
    age: 18
  }
}

const copy = { ...obj }

copy.info.age = 20

console.log(obj.info.age) // 20

因为:

text 复制代码
obj.info
    ↓
同一个引用地址
    ↑
copy.info

深拷贝

递归复制所有层级,两个对象完全独立。

实现深拷贝时需要考虑:

  • 循环引用
  • Date
  • RegExp
  • Function
  • Set
  • Map

循环引用处理

可以使用:

js 复制代码
WeakMap

记录已经拷贝过的对象,避免无限递归。

面试回答

浅拷贝只复制第一层,引用类型仍然共享内存地址;深拷贝会递归复制所有层级,使两个对象完全独立。实现完整深拷贝时需要使用 WeakMap 处理循环引用,并单独处理 Date、RegExp、Map、Set 等特殊类型。


8. let、var、const 的区别

特性 var let const
变量提升 有 有暂时性死区 有暂时性死区
块级作用域 ❌ ✅ ✅
重复声明 ✅ ❌ ❌
重新赋值 ✅ ✅ ❌
必须初始化 ❌ ❌ ✅

const 的注意事项

js 复制代码
const user = {
  name: 'Tom'
}

user.name = 'Jack'

这是允许的。

因为:

text 复制代码
const 锁定的是引用地址
不是对象内部属性

9. 什么是闭包?

闭包指:

一个函数可以访问并保留自己词法作用域中的变量,即使外层函数已经执行完毕。

js 复制代码
function outer() {
  let count = 0

  return function () {
    count++
    console.log(count)
  }
}

const fn = outer()

fn() // 1
fn() // 2

应用场景

  • 函数柯里化
  • 封装私有变量
  • 防抖
  • 节流
  • 循环保存变量

潜在问题

如果闭包长期持有不需要的变量:

text 复制代码
变量无法被垃圾回收
        ↓
可能造成内存泄漏

10. 数组高阶方法

map

返回一个新数组,长度通常不变。

js 复制代码
const result = [1, 2, 3].map(item => item * 2)

// [2, 4, 6]

适合数据转换。

filter

过滤数据。

js 复制代码
const result = [1, 2, 3].filter(item => item > 1)

// [2, 3]

reduce

累计计算。

js 复制代码
const total = [1, 2, 3].reduce((sum, item) => {
  return sum + item
}, 0)

适合:

  • 求和
  • 分组
  • 扁平化
  • 复杂聚合

forEach

只遍历,不返回新数组。


ES6+ 高频场景题

11. 嵌套解构赋值默认值为什么失效?

错误写法

js 复制代码
const { user: { name = '匿名' } } = res

如果 res.user 是 undefined,会直接报错。

正确写法

js 复制代码
const {
  user: {
    name = '匿名'
  } = {}
} = res.data || {}

原因

解构默认值只有在对应属性值为 undefined 时才生效。

如果父级对象本身是:

js 复制代码
null

或:

js 复制代码
undefined

就无法继续解构。


12. 手写 Promise 的核心原理

Promise 的核心包括:

text 复制代码
pending
   ↓
fulfilled

pending
   ↓
rejected

状态只能改变一次。

核心代码

js 复制代码
class MyPromise {
  constructor(executor) {
    this.state = 'pending'
    this.result = undefined

    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = value => {
      if (this.state !== 'pending') return

      this.state = 'fulfilled'
      this.result = value

      this.onFulfilledCallbacks.forEach(fn => fn())
    }

    const reject = reason => {
      if (this.state !== 'pending') return

      this.state = 'rejected'
      this.result = reason

      this.onRejectedCallbacks.forEach(fn => fn())
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === 'function'
        ? onFulfilled
        : value => value

    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : error => {
            throw error
          }

    return new MyPromise((resolve, reject) => {
      const runMicroTask = callback => {
        queueMicrotask(() => {
          try {
            const result = callback(this.result)
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }

      if (this.state === 'fulfilled') {
        runMicroTask(onFulfilled)
      }

      if (this.state === 'rejected') {
        runMicroTask(onRejected)
      }

      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          runMicroTask(onFulfilled)
        })

        this.onRejectedCallbacks.push(() => {
          runMicroTask(onRejected)
        })
      }
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
}

面试重点

手写 Promise 主要考察:

  • 状态不可逆
  • 异步执行
  • 微任务调度
  • 链式调用
  • 错误捕获
  • then 返回新的 Promise

13. Promise.all、race、allSettled、any

方法 核心规则 适用场景
Promise.all 全部成功才成功,一个失败则失败 多个接口并行请求
Promise.race 谁先改变状态就返回谁 请求超时控制
Promise.allSettled 等待所有任务完成 批量上传统计成功失败
Promise.any 任意一个成功即可 多 CDN 资源兜底

Promise.all

js 复制代码
const result = await Promise.all([
  getUser(),
  getOrder(),
  getPermission()
])

14. CommonJS 和 ES Module 的区别

CommonJS

js 复制代码
const module = require('./module')

module.exports = {}

特点:

  • 运行时加载
  • 主要用于 Node.js
  • 导出通常是值的拷贝
  • 支持动态 require

ES Module

js 复制代码
import { name } from './module.js'

export const name = 'Tom'

特点:

  • 静态编译时解析
  • 支持 Tree-Shaking
  • 导出的是实时绑定
  • 浏览器原生支持

浏览器

html 复制代码
<script type="module" src="./main.js"></script>

Node.js

可以通过:

text 复制代码
.mjs

或者:

json 复制代码
{
  "type": "module"
}

开启 ES Module。


15. ES Module 和 CommonJS 循环依赖

CommonJS

执行 require 时会缓存当前模块已经导出的内容。

循环依赖时,另一方可能拿到:

尚未完全初始化的导出对象。

ES Module

模块会先进行静态分析,导入的是实时绑定。

只要变量在真正执行到使用位置之前已经初始化,就可以正常使用。


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

箭头函数特点

  1. 没有自己的 this
  2. 没有自己的 arguments
  3. 没有 prototype
  4. 不能作为构造函数
  5. 不能使用 yield
js 复制代码
const fn = () => {}

不能:

js 复制代码
new fn()

为什么不能作为构造函数?

new 的过程需要:

text 复制代码
创建新对象
    ↓
绑定 prototype
    ↓
绑定 this
    ↓
执行构造函数

箭头函数没有自己的 this 和 prototype,因此不能完成构造函数流程。


17. async/await 的执行机制

async 函数本质上可以理解为 Promise 的语法糖。

执行到:

js 复制代码
await promise

时:

text 复制代码
暂停 async 函数后续代码
        ↓
让出主线程
        ↓
执行同步代码
        ↓
Promise 状态完成
        ↓
后续代码进入微任务队列
        ↓
继续执行

所以:

await 后面的代码会被放入微任务队列。


18. 模板字符串

使用反引号:

js 复制代码
const name = '前端开发者'

const str = `你好,我是${name}
今天学习 ES6 面试题`

优势:

  • 支持变量插值
  • 支持多行字符串
  • 不需要大量 + 拼接

19. Set 和 Map 的区别

Set

特点:

  • 元素唯一
  • 没有键值对
  • 适合去重
js 复制代码
const arr = [1, 2, 2, 3]

const result = [...new Set(arr)]

Map

特点:

  • 键值对集合
  • key 可以是任意类型
  • 适合缓存和映射
js 复制代码
const map = new Map()

map.set('name', 'Tom')
map.set({ id: 1 }, 'user')

20. class 和 ES5 原型继承

ES5

需要手动处理:

text 复制代码
构造函数
原型链
constructor

class

js 复制代码
class Child extends Parent {
  constructor() {
    super()
  }
}

class 本质上仍然基于原型,但语法更加清晰。

多重继承

JavaScript 不支持原生多继承,可以使用 Mixin:

js 复制代码
const Flyable = Base => class extends Base {
  fly() {}
}

const Swimable = Base => class extends Base {
  swim() {}
}

class Animal {}

class Duck extends Swimable(Flyable(Animal)) {}

二、Vue3

1. Vue3 为什么使用 Proxy 替代 Object.defineProperty?

Object.defineProperty 的问题

只能监听已有属性:

js 复制代码
const obj = {
  name: 'Tom'
}

新增:

js 复制代码
obj.age = 18

需要额外处理。

数组下标修改:

js 复制代码
arr[0] = 100

也存在监听问题。

Proxy 的优势

js 复制代码
const proxy = new Proxy(obj, {
  get(target, key) {},
  set(target, key, value) {}
})

可以监听:

  • 属性读取
  • 属性修改
  • 属性新增
  • 属性删除
  • 数组操作
  • 深层对象

面试回答

Object.defineProperty 只能劫持单个已有属性,Vue2 需要通过递归遍历和重写数组方法实现响应式。Vue3 使用 Proxy 代理整个对象,可以直接监听新增、删除、数组下标等操作,并且是惰性代理,性能更好。


2. Vue3 为什么能监听数组下标和新增属性?

数组下标本质上也是对象属性:

js 复制代码
arr[0] = 100

Proxy 可以直接拦截:

text 复制代码
set

因此能够监听。

深层对象

Vue3 的响应式是递归惰性代理:

text 复制代码
访问对象
   ↓
get 拦截
   ↓
发现子对象
   ↓
再次 reactive
   ↓
生成新的 Proxy

3. ref 和 reactive 的区别

对比 ref reactive
基本类型 支持 不支持
对象 支持 支持
访问方式 .value 直接访问
整体替换 支持 容易丢失响应式

ref

js 复制代码
const count = ref(0)

count.value++

reactive

js 复制代码
const user = reactive({
  name: 'Tom'
})

user.name = 'Jack'

为什么 reactive 不能完全替代 ref?

因为:

js 复制代码
reactive(1)

无法代理基本类型。

而:

js 复制代码
ref(1)

通过 value 属性包裹基本类型,实现响应式。


4. Composition API 的优势

可以把同一业务逻辑封装到:

text 复制代码
useUserLogin()
useTableList()
usePermission()

例如:

js 复制代码
function useCounter() {
  const count = ref(0)

  const increment = () => {
    count.value++
  }

  return {
    count,
    increment
  }
}

优势:

  • 按业务逻辑聚合代码
  • 逻辑复用方便
  • 避免 Mixins 命名冲突
  • TypeScript 支持更好

5. Vue3 生命周期

onMounted

适合:

  • DOM 操作
  • 初始化第三方库
  • 首次请求数据

onUpdated

组件 DOM 更新完成后执行。

onUnmounted

适合清理:

js 复制代码
onUnmounted(() => {
  clearInterval(timer)

  window.removeEventListener('resize', handleResize)
})

如果不清理:

text 复制代码
组件销毁
    ↓
定时器仍然运行
    ↓
事件监听仍然存在
    ↓
内存泄漏

6. Pinia 相比 Vuex 的优势

  • 移除了 Mutation
  • 直接通过 Action 修改状态
  • TypeScript 支持更好
  • 支持多个 Store
  • 更适合按业务拆分

推荐:

text 复制代码
userStore
orderStore
appStore
permissionStore

7. Vue3 组件通信方式

父子通信

text 复制代码
父 → 子:props
子 → 父:emit

双向绑定

text 复制代码
v-model

跨层级

text 复制代码
provide / inject

全局状态

text 复制代码
Pinia

事件通知

text 复制代码
mitt

直接调用子组件

text 复制代码
ref

8. Vue3 v-model 原理

默认是:

text 复制代码
modelValue
+
update:modelValue

子组件:

vue 复制代码
<script setup>
const props = defineProps({
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])

function changeValue(value) {
  emit('update:modelValue', value)
}
</script>

父组件:

vue 复制代码
<Child v-model="value" />

本质:

vue 复制代码
<Child
  :modelValue="value"
  @update:modelValue="value = $event"
/>

9. Teleport

Teleport 可以把组件内部的 DOM 渲染到指定位置。

vue 复制代码
<Teleport to="body">
  <div class="modal">
    弹窗
  </div>
</Teleport>

适合:

  • 弹窗
  • 抽屉
  • Tooltip
  • 全局浮层

可以避免:

css 复制代码
overflow: hidden;
z-index;

造成的显示问题。


10. watch 和 watchEffect

watch

js 复制代码
watch(
  () => user.name,
  (newValue, oldValue) => {
    console.log(newValue, oldValue)
  }
)

特点:

  • 手动指定监听源
  • 默认不立即执行
  • 可以获取新旧值

watchEffect

js 复制代码
watchEffect(() => {
  console.log(user.name)
})

特点:

  • 自动收集依赖
  • 初始化立即执行
  • 依赖变化时重新执行

选择

text 复制代码
精准监听某个值 → watch

自动追踪多个依赖 → watchEffect

11. defineProps、defineEmits 和 Props 校验

vue 复制代码
<script setup>
const props = defineProps({
  name: {
    type: String,
    required: true
  }
})

const emit = defineEmits(['change'])
</script>

TypeScript 写法:

ts 复制代码
interface Props {
  title: string
  count?: number
}

const props = defineProps<Props>()

12. keep-alive

作用:

缓存组件实例,切换组件时不销毁重建。

vue 复制代码
<KeepAlive>
  <RouterView />
</KeepAlive>

include

缓存指定组件。

exclude

排除指定组件。

适合:

  • 列表页
  • 表单页
  • Tab 页面

三、React

1. React 虚拟 DOM

虚拟 DOM 是使用 JavaScript 对象描述真实 DOM 结构。

流程:

text 复制代码
状态变化
    ↓
生成新的虚拟 DOM
    ↓
Diff 对比
    ↓
计算最小更新
    ↓
更新真实 DOM

优势:

  • 减少直接操作 DOM
  • 声明式开发
  • 跨平台能力强

2. React 生命周期

类组件

挂载:

text 复制代码
constructor
    ↓
render
    ↓
componentDidMount

更新:

text 复制代码
shouldComponentUpdate
    ↓
render
    ↓
componentDidUpdate

卸载:

text 复制代码
componentWillUnmount

函数组件

使用:

js 复制代码
useEffect()

模拟生命周期。

js 复制代码
useEffect(() => {
  // 挂载

  return () => {
    // 卸载
  }
}, [])

3. useState 注意事项

js 复制代码
const [count, setCount] = useState(0)

不要直接修改:

js 复制代码
count = count + 1

应该:

js 复制代码
setCount(count + 1)

连续更新推荐:

js 复制代码
setCount(prev => prev + 1)

注意

Hooks 不能放在:

  • 条件语句
  • 循环
  • 嵌套函数

中调用。


4. useEffect 依赖项

无依赖

js 复制代码
useEffect(() => {})

每次渲染后执行。

空数组

js 复制代码
useEffect(() => {}, [])

首次挂载执行。

指定依赖

js 复制代码
useEffect(() => {}, [count])

count 变化时执行。


5. React 组件通信

父传子

jsx 复制代码
<Child name="Tom" />

子传父

父组件传递回调:

jsx 复制代码
<Child onChange={handleChange} />

兄弟组件

通过共同父组件状态提升。

跨层级

使用:

text 复制代码
Context API
Redux
Zustand

6. Props 和 State 的区别

Props State
父组件传入 组件内部维护
外部数据 内部状态
子组件只读 可以通过 setter 修改

Props 设计为只读,是为了保证:

text 复制代码
单向数据流
    ↓
状态来源清晰
    ↓
组件行为可预测

7. Context API 的性能问题

Context 更新时,消费该 Context 的组件可能重新渲染。

优化方式:

text 复制代码
拆分 Context
    ↓
useMemo 缓存 value
    ↓
React.memo

示例:

jsx 复制代码
const value = useMemo(() => ({
  user,
  setUser
}), [user])

8. useMemo 和 useCallback

useMemo

缓存计算结果:

js 复制代码
const result = useMemo(() => {
  return expensiveCalculation(data)
}, [data])

useCallback

缓存函数引用:

js 复制代码
const handleClick = useCallback(() => {
  console.log('click')
}, [])

一句话:

text 复制代码
useMemo     → 缓存结果
useCallback → 缓存函数

不要滥用,因为缓存本身也有开销。


9. React Router v6

路由跳转

js 复制代码
const navigate = useNavigate()

navigate('/user')

动态参数

js 复制代码
const { id } = useParams()

查询参数

js 复制代码
const [searchParams] = useSearchParams()

const keyword = searchParams.get('keyword')

路由守卫

可以封装权限组件:

jsx 复制代码
function AuthRoute({ children }) {
  const token = getToken()

  return token ? children : <Navigate to="/login" />
}

10. Redux / Redux Toolkit

核心流程:

text 复制代码
组件
  ↓ dispatch(action)
Action
  ↓
Reducer
  ↓
新的 State
  ↓
组件重新渲染

Redux Toolkit 的优势:

  • 减少样板代码
  • 内置 Immer
  • 支持直接修改 state 的写法

11. React Diff 算法

核心原则:

同层比较

React 主要进行同层级节点比较。

key

jsx 复制代码
list.map(item => (
  <div key={item.id}>
    {item.name}
  </div>
))

key 应该:

  • 唯一
  • 稳定

不推荐:

jsx 复制代码
key={index}

特别是列表存在新增、删除、排序时。


12. 函数组件和类组件

函数组件优势:

  • 代码更简洁
  • 没有 this 指向问题
  • Hooks 复用逻辑方便
  • 更贴近函数式编程
  • 逻辑组织更灵活

现在 React 项目通常优先使用函数组件。


13. React 错误边界

错误边界用于捕获子组件渲染过程中出现的错误,避免整个页面白屏。

核心方法:

js 复制代码
static getDerivedStateFromError(error) {}

以及:

js 复制代码
componentDidCatch(error, info) {}

注意:

错误边界不能捕获所有错误,例如事件回调和异步代码中的错误需要单独处理。


四、TypeScript

1. TypeScript 基本类型

常见类型:

text 复制代码
string
number
boolean
null
undefined
symbol
bigint
void
never
any
unknown

void

表示没有有效返回值:

ts 复制代码
function log(): void {
  console.log('hello')
}

never

表示永远不会正常结束:

ts 复制代码
function error(): never {
  throw new Error('error')
}

2. interface 和 type

interface

ts 复制代码
interface User {
  name: string
  age: number
}

支持声明合并:

ts 复制代码
interface User {
  id: number
}

type

ts 复制代码
type User = {
  name: string
  age: number
}

可以表示:

ts 复制代码
type Status = 'success' | 'error'

总结

text 复制代码
interface → 对象结构、面向对象契约

type → 联合类型、交叉类型、元组等复杂类型

3. 泛型

泛型就是:

把类型作为参数传入。

ts 复制代码
function identity<T>(value: T): T {
  return value
}

identity<number>(100)
identity<string>('hello')

泛型组件

tsx 复制代码
function GenericList<T>({
  list,
  renderItem
}: {
  list: T[]
  renderItem: (item: T) => React.ReactNode
}) {
  return (
    <div>
      {list.map(renderItem)}
    </div>
  )
}

4. 可选链和空值合并

可选链

ts 复制代码
const name = user?.info?.profile?.name

如果中间为:

text 复制代码
null
undefined

则返回 undefined。

空值合并

ts 复制代码
const pageSize = inputSize ?? 10

只有 null 或 undefined 才使用默认值。

以下值不会被替换:

text 复制代码
0
''
false

5. 联合类型和交叉类型

联合类型

ts 复制代码
type Status = string | number

表示:

可以是 string,也可以是 number。

交叉类型

ts 复制代码
type User = BasicUser & Permission

表示:

同时拥有两个类型的属性。


6. 定义数组、对象、函数类型

数组

ts 复制代码
const arr1: number[] = [1, 2, 3]

const arr2: Array<string> = ['a', 'b']

对象

ts 复制代码
interface Person {
  name: string
  age?: number
  readonly id: number
}

函数

ts 复制代码
type AddFunc = (a: number, b: number) => number

const add: AddFunc = (x, y) => x + y

7. 类型断言

类型断言是告诉 TypeScript:

我比编译器更清楚这个值的类型。

as

ts 复制代码
const str = value as string

尖括号

ts 复制代码
const str = <string>value

React TSX 项目推荐使用:

ts 复制代码
as

8. 如何解决属性不存在报错?

例如:

text 复制代码
类型 xxx 上不存在属性 xxx

解决方式:

  1. 补充完整类型定义。
  2. 使用类型断言。
  3. 扩展第三方模块声明。
  4. 使用索引签名。
ts 复制代码
interface Obj {
  [key: string]: any
}

9. TypeScript 枚举

ts 复制代码
enum Status {
  Success = 200,
  NotFound = 404,
  Error = 500
}

const status = Status.Success

注意:

  • 数字枚举支持反向映射
  • 字符串枚举不支持反向映射
  • 需要时可以使用 const enum

10. Vue3 + TS、React + TS 类型定义

Vue3 + TS

ts 复制代码
interface Props {
  title: string
  count?: number
}

const props = defineProps<Props>()

React + TS

tsx 复制代码
interface Props {
  title: string
}

function DemoComponent({ title }: Props) {
  const [count, setCount] = useState<number>(0)

  return (
    <div>
      {title} {count}
    </div>
  )
}

五、Webpack / Vite / 前端工程化

1. Webpack 四大核心

Entry

入口。

text 复制代码
从入口文件开始分析依赖

Output

输出。

text 复制代码
定义构建产物路径和文件名

Loader

处理非 JavaScript 文件:

text 复制代码
CSS
图片
Vue
TypeScript

Plugin

扩展 Webpack 能力:

text 复制代码
压缩代码
生成 HTML
注入环境变量

2. Webpack 如何优化打包速度?

持久化缓存

js 复制代码
cache: {
  type: 'filesystem'
}

多进程

使用:

text 复制代码
thread-loader

缩小处理范围

text 复制代码
include
exclude

第三方依赖预打包

使用:

text 复制代码
DllPlugin

3. Webpack 如何优化打包体积?

  • 开启生产模式
  • Tree-Shaking
  • splitChunks
  • 图片压缩
  • 图片懒加载
  • 第三方库按需引入

4. Vite 和 Webpack 的区别

Webpack

text 复制代码
启动
  ↓
全量打包
  ↓
启动开发服务器

Vite

text 复制代码
启动开发服务器
      ↓
浏览器通过 ESM 按需请求
      ↓
按需转换模块

Vite 优势

  • 冷启动快
  • HMR 快
  • 大型项目开发体验更好

生产构建

Vite 生产环境会进行完整构建和优化,常见构建流程基于 Rollup 生态。


5. 模块化打包

模块化打包就是:

将项目中分散的模块统一分析、转换和打包成浏览器可以运行的静态资源。

解决:

  • 全局变量污染
  • 依赖混乱
  • 非 JS 资源无法直接运行
  • 代码压缩和兼容问题

6. ESLint 和 Prettier

ESLint

负责:

text 复制代码
代码质量
语法错误
潜在问题

Prettier

负责:

text 复制代码
缩进
换行
引号
代码格式

一句话:

text 复制代码
ESLint → 代码有没有问题

Prettier → 代码长什么样

7. Git 常用命令

命令 作用
clone 克隆仓库
add 添加到暂存区
commit 提交本地
push 推送远程
pull 拉取远程
branch 分支管理
merge 合并分支
stash 暂存未提交代码

标准工作流

text 复制代码
主分支
  ↓
创建功能分支
  ↓
开发
  ↓
提交
  ↓
代码评审
  ↓
合并
  ↓
发布

8. CI/CD

CI/CD:

text 复制代码
持续集成
+
持续部署

典型流程:

text 复制代码
提交代码
    ↓
自动拉取
    ↓
安装依赖
    ↓
执行测试
    ↓
构建
    ↓
部署

9. npm 和 yarn

早期 Yarn 主要解决:

  • npm 安装速度慢
  • 依赖版本不确定

现代 npm 也已经支持:

  • lock 文件
  • 并行安装
  • 依赖管理优化

依赖冲突解决:

text 复制代码
升级冲突依赖
resolutions
npm dedupe

10. 前端工程化

核心包括:

text 复制代码
模块化
组件化
规范化
自动化

模块化

管理:

text 复制代码
JS
CSS
资源

组件化

抽取:

text 复制代码
Button
Table
Dialog
Form

规范化

text 复制代码
ESLint
Prettier
Git Commit

自动化

text 复制代码
自动构建
自动测试
自动部署

11. Vue3 + TypeScript + Vite 项目搭建

常见步骤:

bash 复制代码
npm create vite@latest

选择:

text 复制代码
Vue
TypeScript

核心配置:

text 复制代码
vite.config.ts
tsconfig.json
ESLint
Prettier
Vue Router
Pinia
环境变量

常见 Vite 配置:

ts 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],

  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },

  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  }
})

六、前端性能优化

1. 页面加载性能优化

网络层

  • HTTP/2
  • HTTP/3
  • CDN
  • Gzip
  • Brotli
  • 浏览器缓存

渲染层

  • 首屏优先加载核心资源
  • 非关键 JS / CSS 懒加载
  • 避免长任务阻塞主线程
  • 使用 SSR / SSG

代码层

  • Tree-Shaking
  • 路由懒加载
  • 代码分割
  • 减少第三方依赖
  • 图片使用 WebP / AVIF

2. 如何减少重排和重绘?

推荐使用:

css 复制代码
transform
opacity

进行动画。

避免频繁读取:

js 复制代码
offsetWidth
offsetHeight
getBoundingClientRect()

因为可能导致:

text 复制代码
强制同步布局

批量修改 DOM,避免:

text 复制代码
修改一次
触发布局

修改一次
触发布局

3. 万级数据列表如何优化?

使用:

text 复制代码
虚拟滚动

核心思想:

text 复制代码
10000 条数据
      ↓
只渲染可视区域几十条
      ↓
动态复用 DOM
      ↓
减少 DOM 数量
      ↓
提升性能

还可以使用:

css 复制代码
contain: layout paint size;

隔离渲染影响。


4. 图片性能优化

图片格式

优先:

text 复制代码
AVIF
WebP

懒加载

html 复制代码
<img loading="lazy" src="image.jpg" />

预加载

html 复制代码
<link
  rel="preload"
  as="image"
  href="banner.webp"
/>

5. 浏览器缓存

强缓存

http 复制代码
Cache-Control: max-age=31536000

适合:

text 复制代码
带 hash 的 JS
CSS
图片

协商缓存

text 复制代码
ETag
Last-Modified

服务器返回:

text 复制代码
304 Not Modified

表示资源没有变化。


6. 首屏加载优化

  • 内联首屏关键 CSS
  • 非首屏 JS 使用 defer
  • 非关键资源延迟加载
  • 使用骨架屏
  • 减少关键资源数量
  • 减少关键资源体积

7. JavaScript 性能优化

Tree-Shaking

删除没有使用的代码。

避免内存泄漏

注意清理:

js 复制代码
clearInterval(timer)

window.removeEventListener(
  'resize',
  handleResize
)

Web Worker

复杂计算可以放到子线程:

text 复制代码
主线程
  ↓
负责页面交互

Worker
  ↓
负责复杂计算

避免长任务阻塞页面。


8. CSS 性能优化

避免深层选择器

不推荐:

css 复制代码
.page .content .main .list .item span {}

推荐:

css 复制代码
.list-item {}

减少重复样式

提取公共类。

避免大量 DOM 使用昂贵属性

例如:

text 复制代码
box-shadow
filter

大量使用可能增加渲染成本。


9. 路由切换优化

路由懒加载

js 复制代码
const User = () => import('./views/User.vue')

组件缓存

Vue:

vue 复制代码
<KeepAlive>
  <RouterView />
</KeepAlive>

预加载

提前加载即将访问的路由资源。


10. 前端性能检测工具

Chrome DevTools

使用:

text 复制代码
Performance

分析:

  • 长任务
  • 重排
  • 重绘
  • JS 执行耗时

Lighthouse

可以分析:

  • Performance
  • Accessibility
  • Best Practices
  • SEO

Performance API

可以监控:

text 复制代码
FP
FCP
LCP
FID
CLS

七、CSS / HTML 高频基础

1. CSS 优先级

常见优先级:

text 复制代码
!important
    ↓
行内样式
    ↓
ID 选择器
    ↓
类 / 属性 / 伪类选择器
    ↓
标签 / 伪元素选择器

例如:

html 复制代码
<div id="app" class="box" style="color: red">
  内容
</div>
css 复制代码
div {
  color: blue;
}

.box {
  color: green;
}

#app {
  color: yellow;
}

最终颜色:

text 复制代码
red

因为:

text 复制代码
行内样式 > ID > class > 标签

注意

行内样式不是 CSS 选择器,但在完整的 CSS 优先级比较中需要考虑。


2. src 和 href 的区别

src

表示:

加载资源。

常见:

html 复制代码
<img src="image.png" />

<script src="main.js"></script>

<iframe src="page.html"></iframe>

href

表示:

引用资源或指向地址。

常见:

html 复制代码
<a href="page.html">页面</a>

<link rel="stylesheet" href="style.css" />

一句话:

text 复制代码
src  → 加载资源

href → 引用资源 / 指向地址

3. AJAX、Fetch、Axios 的区别

AJAX

AJAX 不是一个具体的库,而是一种:

页面不刷新,通过异步请求与服务器交互的技术思想。

Fetch

浏览器原生 API:

js 复制代码
fetch('/api/user')
  .then(res => res.json())
  .then(data => {
    console.log(data)
  })

Axios

第三方 HTTP 请求库:

js 复制代码
import axios from 'axios'

const res = await axios.get('/api/user')

console.log(res.data)

对比

对比 AJAX Fetch Axios
本质 技术思想 原生 API 第三方库
是否安装 否 否 是
Promise 可以 支持 支持
请求拦截器 需要自己实现 需要自己封装 原生支持
JSON 处理 手动 通常手动 自动
项目常用 概念 常用 非常常用

八、面试快速背诵总结

JavaScript

text 复制代码
事件循环:
同步任务 → 微任务 → 宏任务

防抖:
停止操作后执行

节流:
固定时间执行一次

闭包:
函数能够访问并保留外部作用域变量

浅拷贝:
复制第一层

深拷贝:
递归复制所有层级

箭头函数:
没有自己的 this、arguments、prototype

Vue3

text 复制代码
Proxy:
代理整个对象,支持新增、删除、数组下标

ref:
支持基本类型和引用类型

reactive:
主要用于对象和数组

watch:
精准监听

watchEffect:
自动收集依赖

Pinia:
轻量、TS 友好、按业务拆 Store

Teleport:
把 DOM 挂载到指定位置

KeepAlive:
缓存组件实例

React

text 复制代码
Props:
父组件传入,只读

State:
组件内部状态

useState:
管理状态

useEffect:
处理副作用

useMemo:
缓存计算结果

useCallback:
缓存函数引用

Context:
跨层级传递数据

React.memo:
避免不必要的组件重新渲染

TypeScript

text 复制代码
interface:
对象结构和面向对象契约

type:
联合、交叉、元组等复杂类型

泛型:
类型参数化

never:
永远不会正常结束

void:
没有有效返回值

unknown:
安全的未知类型

any:
关闭类型检查

工程化

text 复制代码
Webpack:
Entry
Output
Loader
Plugin

Vite:
开发环境基于 ESM 按需加载
生产环境进行构建优化

ESLint:
代码质量

Prettier:
代码格式

Git:
版本控制

CI/CD:
自动构建、测试、部署

性能优化

text 复制代码
网络:
CDN、HTTP2、压缩、缓存

代码:
Tree-Shaking、代码分割、懒加载

渲染:
减少重排重绘、避免长任务

列表:
虚拟滚动

图片:
WebP、AVIF、懒加载、预加载

监控:
Performance API、Lighthouse、DevTools

📌 最后总结

前端面试不要只背概念,建议按照下面的方式回答:

text 复制代码
是什么
  ↓
为什么
  ↓
怎么实现
  ↓
应用场景
  ↓
项目中怎么用

例如面试官问:

Vue3 为什么使用 Proxy?

可以回答:

Vue3 使用 Proxy 替代 Object.defineProperty,主要是因为 Proxy 可以代理整个对象,能够监听新增、删除属性以及数组下标操作。Object.defineProperty 只能劫持已有属性,Vue2 需要通过递归遍历和重写数组方法来实现响应式。Proxy 采用惰性代理,在访问深层对象时再进行代理,因此在大对象场景下性能和实现复杂度更好。

这样回答就比单纯说:

Proxy 比 Object.defineProperty 好。

更加完整。


前端面试的核心不是背答案,而是理解浏览器、JavaScript、框架和工程化背后的运行机制。

相关推荐
子兮曰6 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰6 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万6 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝6 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋6 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁6 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95276 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大6 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师6 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学6 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端