前端面试题大全: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 处理循环引用,并单独处理 DateRegExpMapSet 等特殊类型。


8. letvarconst 的区别

特性 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.userundefined,会直接报错。

正确写法

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.allraceallSettledany

方法 核心规则 适用场景
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
    ↓
执行构造函数

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


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. refreactive 的区别

对比 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. watchwatchEffect

watch

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

特点:

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

watchEffect

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

特点:

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

选择

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

自动追踪多个依赖 → watchEffect

11. definePropsdefineEmits 和 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. useMemouseCallback

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. interfacetype

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

只有 nullundefined 才使用默认值。

以下值不会被替换:

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. srchref 的区别

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、框架和工程化背后的运行机制。

相关推荐
我不叫武2 小时前
一个用 Rust 写的离线编码查询桌面工具
前端
Web4Browser3 小时前
指纹浏览器 API 自动化怎么接:启动 Profile、获取 CDP 端点并连接自动化框架
前端·网络·typescript·自动化
Patrick_Wilson3 小时前
从 React 到 Flutter:写给前端的一张跨端知识地图
前端·flutter·react.js
颜酱3 小时前
06 | 把 meta_config 同步进 MySQL(生成阶段)
前端·人工智能·后端
OpenTiny社区4 小时前
Loop Engineering:让 AI Agent 自己跑起来的工程方法
前端·github
IT_陈寒4 小时前
SpringBoot自动配置坑了我一周,原来问题这么蠢!
前端·人工智能·后端
kyriewen4 小时前
我review了一份Vibe Coding写的前端代码——能跑,但5个地方迟早要命
前端·javascript·ai编程
REDcker5 小时前
Cesium三维WebGIS入门详解
前端·gis·web·cesium·webgis
0暗影流光05 小时前
十倍效能提升——Web 基础研发体系的建立
前端