2025年前端高频面试题全集
目录
- [JavaScript基础]
- [ES6+新特性]
- [Vue框架]
- [React框架]
- [前端工程化]
- [性能优化]
- [浏览器原理]
- [网络与安全]
- [算法与数据结构]
- [项目实战]
JavaScript基础
1. 说说JavaScript的数据类型及其判断方法
答案:
基本数据类型(7种) :
- Number
- String
- Boolean
- Undefined
- Null
- Symbol(ES6)
- BigInt(ES2020)
引用数据类型:
- Object(包括Array、Function、Date、RegExp等)
判断方法:
// 1. typeof - 适用于基本类型(除null)
typeof 123 // 'number'
typeof 'abc' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof 123n // 'bigint'
typeof null // 'object' (历史遗留bug)
typeof {} // 'object'
typeof [] // 'object'
typeof function(){} // 'function'
// 2. instanceof - 判断引用类型
[] instanceof Array // true
{} instanceof Object // true
function(){} instanceof Function // true
// 3. Object.prototype.toString.call() - 最准确
Object.prototype.toString.call(123) // '[object Number]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
// 4. Array.isArray() - 专门判断数组
Array.isArray([]) // true
Array.isArray({}) // false
2. 闭包是什么?有什么作用和应用场景?
答案:
定义:闭包是指有权访问另一个函数作用域中变量的函数。
形成条件:
- 函数嵌套
- 内部函数引用外部函数的变量
- 内部函数被返回或以某种方式被外部引用
示例:
function outer() {
let count = 0
return function inner() {
count++
console.log(count)
}
}
const counter = outer()
counter() // 1
counter() // 2
counter() // 3
作用:
- 数据私有化/封装
- 保持变量不被回收
- 实现模块化
应用场景:
// 1. 防抖函数
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 2. 节流函数
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
// 3. 单例模式
const Singleton = (function() {
let instance = null
return function(name) {
if (instance) return instance
this.name = name
instance = this
}
})()
// 4. 缓存函数
function memoize(fn) {
const cache = {}
return function(...args) {
const key = JSON.stringify(args)
if (cache[key]) return cache[key]
const result = fn.apply(this, args)
cache[key] = result
return result
}
}
注意事项:
- 可能导致内存泄漏
- 及时清理不需要的闭包
3. 说说原型和原型链
答案:
原型(prototype) :
- 每个函数都有一个prototype属性,指向原型对象
- 原型对象包含所有实例共享的属性和方法
原型链:
- 每个对象都有
__proto__属性,指向其构造函数的prototype - 通过
__proto__形成的链式结构就是原型链 - 原型链的顶端是Object.prototype,再往上是null
function Person(name) {
this.name = name
}
Person.prototype.sayName = function() {
console.log(this.name)
}
const person = new Person('张三')
// 原型关系
person.__proto__ === Person.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true
// 原型链查找
person.sayName() // 先找person自身,没有就找Person.prototype
person.toString() // 继续往上找Object.prototype
继承实现:
// ES5 寄生组合式继承
function Parent(name) {
this.name = name
this.colors = ['red', 'blue']
}
Parent.prototype.sayName = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name) // 继承属性
this.age = age
}
Child.prototype = Object.create(Parent.prototype) // 继承方法
Child.prototype.constructor = Child
// ES6 Class继承
class Parent {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
4. this的指向问题
答案:
this的四种绑定规则:
// 1. 默认绑定 - 独立函数调用
function foo() {
console.log(this) // 非严格模式: window,严格模式: undefined
}
foo()
// 2. 隐式绑定 - 对象方法调用
const obj = {
name: '张三',
sayName() {
console.log(this.name) // this指向obj
}
}
obj.sayName()
// 隐式绑定丢失
const fn = obj.sayName
fn() // this指向window
// 3. 显式绑定 - call/apply/bind
function greet() {
console.log(this.name)
}
const person = { name: '李四' }
greet.call(person) // this指向person
greet.apply(person)
const boundGreet = greet.bind(person)
boundGreet()
// 4. new绑定 - 构造函数
function Person(name) {
this.name = name
}
const p = new Person('王五') // this指向新创建的对象
// 5. 箭头函数 - 继承外层作用域的this
const obj2 = {
name: '赵六',
sayName: () => {
console.log(this.name) // this指向外层作用域
},
sayName2() {
setTimeout(() => {
console.log(this.name) // this指向obj2
}, 100)
}
}
优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
5. 事件循环(Event Loop)机制
答案:
概念:
- JavaScript是单线程语言,通过事件循环实现异步
- 事件循环包括:主线程、宏任务队列、微任务队列
执行顺序:
- 执行同步代码(宏任务)
- 执行所有微任务
- 渲染页面(如需要)
- 取出下一个宏任务,重复步骤1-3
console.log('1') // 同步代码
setTimeout(() => {
console.log('2') // 宏任务
}, 0)
Promise.resolve().then(() => {
console.log('3') // 微任务
}).then(() => {
console.log('4') // 微任务
})
console.log('5') // 同步代码
// 输出顺序: 1 5 3 4 2
宏任务(MacroTask) :
- setTimeout
- setInterval
- setImmediate(Node.js)
- I/O操作
- UI渲染
微任务(MicroTask) :
- Promise.then/catch/finally
- MutationObserver
- process.nextTick(Node.js)
复杂示例:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
async1()
new Promise(resolve => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
ES6+新特性
1. let、const和var的区别
答案:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 有(undefined) | 有(暂时性死区) | 有(暂时性死区) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 全局对象属性 | 是 | 否 | 否 |
// 1. 作用域
{
var a = 1
let b = 2
const c = 3
}
console.log(a) // 1
console.log(b) // ReferenceError
console.log(c) // ReferenceError
// 2. 变量提升
console.log(x) // undefined
var x = 1
console.log(y) // ReferenceError(暂时性死区)
let y = 2
// 3. 重复声明
var m = 1
var m = 2 // 允许
let n = 1
let n = 2 // SyntaxError
// 4. const常量
const obj = { a: 1 }
obj.a = 2 // 允许(修改对象属性)
obj = {} // TypeError(重新赋值)
2. 箭头函数与普通函数的区别
答案:
// 1. this指向
const obj = {
name: '张三',
// 普通函数:this指向调用者
sayName1: function() {
console.log(this.name)
},
// 箭头函数:this继承外层作用域
sayName2: () => {
console.log(this.name) // undefined
}
}
// 2. 没有arguments
function fn1() {
console.log(arguments) // [1, 2, 3]
}
fn1(1, 2, 3)
const fn2 = () => {
console.log(arguments) // ReferenceError
}
// 可以用剩余参数替代
const fn3 = (...args) => {
console.log(args) // [1, 2, 3]
}
// 3. 不能作为构造函数
function Person1(name) {
this.name = name
}
const p1 = new Person1('张三') // 正常
const Person2 = (name) => {
this.name = name
}
const p2 = new Person2('李四') // TypeError
// 4. 没有prototype
console.log(Person1.prototype) // {constructor: ƒ}
console.log(Person2.prototype) // undefined
// 5. 不能用作Generator函数
function* generator() {
yield 1
}
// 箭头函数不能使用yield
3. Promise原理及手写实现
答案:
基本用法:
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
resolve('成功')
// reject('失败')
}, 1000)
})
promise
.then(result => console.log(result))
.catch(error => console.log(error))
.finally(() => console.log('完成'))
手写Promise:
class MyPromise {
constructor(executor) {
this.state = 'pending' // pending, fulfilled, rejected
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = 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 : reason => { throw reason }
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (error) {
reject(error)
}
})
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
})
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (error) {
reject(error)
}
})
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
})
})
}
})
return promise2
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
)
}
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value
count++
if (count === promises.length) {
resolve(results)
}
},
reason => reject(reason)
)
})
})
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject)
})
})
}
}
4. async/await原理
答案:
基本用法:
async function fetchData() {
try {
const data1 = await fetch('/api/data1')
const data2 = await fetch('/api/data2')
return { data1, data2 }
} catch (error) {
console.error(error)
}
}
实现原理(基于Generator + 自动执行器):
// async/await是Generator的语法糖
function* generatorFunc() {
const data1 = yield fetch('/api/data1')
const data2 = yield fetch('/api/data2')
return { data1, data2 }
}
// 自动执行器
function asyncToGenerator(generatorFunc) {
return function() {
const gen = generatorFunc.apply(this, arguments)
return new Promise((resolve, reject) => {
function step(key, arg) {
let result
try {
result = gen[key](arg)
} catch (error) {
return reject(error)
}
const { value, done } = result
if (done) {
return resolve(value)
} else {
return Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
)
}
}
step('next')
})
}
}
// 使用
const asyncFunc = asyncToGenerator(generatorFunc)
asyncFunc().then(result => console.log(result))
错误处理:
// 1. try-catch
async function getData() {
try {
const data = await fetch('/api/data')
return data
} catch (error) {
console.error(error)
}
}
// 2. Promise.catch
async function getData2() {
const data = await fetch('/api/data').catch(error => {
console.error(error)
return null
})
return data
}
// 3. 统一错误处理
function to(promise) {
return promise
.then(data => [null, data])
.catch(err => [err, null])
}
async function getData3() {
const [err, data] = await to(fetch('/api/data'))
if (err) {
console.error(err)
return
}
return data
}
5. 解构赋值和扩展运算符
答案:
// 1. 数组解构
const arr = [1, 2, 3, 4, 5]
const [a, b, ...rest] = arr
console.log(a, b, rest) // 1 2 [3, 4, 5]
// 默认值
const [x = 0, y = 0] = [1]
console.log(x, y) // 1 0
// 交换变量
let m = 1, n = 2;
[m, n] = [n, m]
// 2. 对象解构
const obj = { name: '张三', age: 20, city: '北京' }
const { name, age, ...others } = obj
console.log(name, age, others) // 张三 20 {city: '北京'}
// 重命名
const { name: username } = obj
console.log(username) // 张三
// 默认值
const { gender = '男' } = obj
console.log(gender) // 男
// 嵌套解构
const user = {
info: {
name: '李四',
address: {
city: '上海'
}
}
}
const { info: { name, address: { city } } } = user
// 3. 扩展运算符
// 数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const merged = [...arr1, ...arr2]
// 对象
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 3, c: 4 }
const merged2 = { ...obj1, ...obj2 } // {a: 1, b: 3, c: 4}
// 函数参数
function sum(...numbers) {
return numbers.reduce((acc, cur) => acc + cur, 0)
}
sum(1, 2, 3, 4) // 10
// 浅拷贝
const original = { a: 1, b: { c: 2 } }
const copy = { ...original }
copy.b.c = 3 // 会影响original
Vue框架
1. Vue2和Vue3的区别
答案:
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 响应式原理 | Object.defineProperty | Proxy |
| API风格 | Options API | Composition API |
| 性能 | 较慢 | 更快(重写虚拟DOM) |
| TypeScript | 支持较弱 | 完全支持 |
| 包大小 | 较大 | 更小(Tree-shaking) |
| 生命周期 | beforeCreate/created等 | setup |
| 多根节点 | 不支持 | 支持 |
| Teleport | 不支持 | 支持 |
| Suspense | 不支持 | 支持 |
核心区别示例:
// Vue2 Options API
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('mounted')
}
}
// Vue3 Composition API
import { ref, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('mounted')
})
return {
count,
increment
}
}
}
// Vue3 script setup(更简洁)
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(0)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('mounted')
})
</script>
2. Vue响应式原理
答案:
Vue2响应式原理:
// 基于Object.defineProperty实现
function defineReactive(obj, key, val) {
const dep = new Dep() // 依赖收集器
Object.defineProperty(obj, key, {
get() {
// 依赖收集
if (Dep.target) {
dep.depend()
}
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
// 通知更新
dep.notify()
}
})
}
class Dep {
constructor() {
this.subs = []
}
depend() {
if (Dep.target) {
this.subs.push(Dep.target)
}
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
this.value = this.get()
}
get() {
Dep.target = this
const value = this.vm[this.exp]
Dep.target = null
return value
}
update() {
const newValue = this.vm[this.exp]
this.cb.call(this.vm, newValue, this.value)
this.value = newValue
}
}
Vue3响应式原理:
// 基于Proxy实现
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
// 依赖收集
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
// 触发更新
if (oldValue !== value) {
trigger(target, key)
}
return result
}
})
}
const targetMap = new WeakMap()
let activeEffect = null
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, depsMap = new Map())
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, dep = new Set())
}
dep.add(activeEffect)
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
function effect(fn) {
activeEffect = fn
fn()
activeEffect = null
}
3. computed和watch的区别
答案:
// computed - 计算属性
// 特点:缓存、依赖追踪、同步
export default {
data() {
return {
firstName: '张',
lastName: '三'
}
},
computed: {
// 有缓存,只有依赖变化才重新计算
fullName() {
return this.firstName + this.lastName
},
// 可设置getter和setter
fullName2: {
get() {
return this.firstName + this.lastName
},
set(val) {
const names = val.split(' ')
this.firstName = names[0]
this.lastName = names[1]
}
}
}
}
// watch - 侦听器
// 特点:无缓存、可异步、可配置
export default {
data() {
return {
question: '',
answer: ''
}
},
watch: {
// 简单监听
question(newVal, oldVal) {
this.getAnswer()
},
// 深度监听
obj: {
handler(newVal, oldVal) {
console.log('obj changed')
},
deep: true,
immediate: true // 立即执行
},
// 监听对象属性
'obj.name'(newVal) {
console.log(newVal)
}
},
methods: {
async getAnswer() {
this.answer = await fetch('/api/answer')
}
}
}
// Vue3 Composition API
import { ref, computed, watch, watchEffect } from 'vue'
const count = ref(0)
// computed
const double = computed(() => count.value * 2)
// watch
watch(count, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// watchEffect - 自动追踪依赖
watchEffect(() => {
console.log(count.value)
})
区别总结:
- computed:有缓存,依赖不变不重新计算,适合复杂计算
- watch:无缓存,每次都执行,适合异步操作或开销较大的操作
4. Vue组件通信方式
答案:
// 1. Props / Emit(父子组件)
// 父组件
<Child :msg="message" @update="handleUpdate" />
// 子组件
export default {
props: ['msg'],
methods: {
handleClick() {
this.$emit('update', 'new value')
}
}
}
// 2. v-model(双向绑定)
// 父组件
<Child v-model="value" />
// 子组件
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
handleInput(e) {
this.$emit('update:modelValue', e.target.value)
}
}
}
// 3. $refs(父访问子)
<Child ref="childRef" />
this.$refs.childRef.childMethod()
// 4. $parent / $children(不推荐)
this.$parent.parentMethod()
this.$children[0].childMethod()
// 5. provide / inject(祖孙组件)
// 祖先组件
export default {
provide() {
return {
theme: 'dark',
user: this.user
}
}
}
// 后代组件
export default {
inject: ['theme', 'user']
}
// Vue3 响应式provide/inject
import { provide, inject, ref } from 'vue'
// 祖先组件
const theme = ref('dark')
provide('theme', theme)
// 后代组件
const theme = inject('theme')
// 6. EventBus(兄弟组件)
// Vue2
const EventBus = new Vue()
// 组件A
EventBus.$emit('event-name', data)
// 组件B
EventBus.$on('event-name', data => {
console.log(data)
})
// Vue3使用mitt
import mitt from 'mitt'
const emitter = mitt()
emitter.on('event-name', data => {})
emitter.emit('event-name', data)
// 7. Vuex / Pinia(全局状态)
// Vuex
this.$store.commit('increment')
this.$store.dispatch('asyncIncrement')
// Pinia
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
store.increment()
// 8. $attrs / $listeners(Vue2)
// 父组件
<Child :name="name" :age="age" @click="handleClick" />
// 子组件(传递给孙组件)
<GrandChild v-bind="$attrs" v-on="$listeners" />
5. Vue生命周期
答案:
Vue2生命周期:
export default {
// 1. 创建阶段
beforeCreate() {
// 实例初始化后,data、methods未初始化
console.log(this.data) // undefined
},
created() {
// data、methods已初始化,DOM未挂载
// 适合:数据请求、初始化数据
this.fetchData()
},
// 2. 挂载阶段
beforeMount() {
// 模板编译完成,DOM未渲染
},
mounted() {
// DOM已挂载
// 适合:DOM操作、第三方库初始化
this.$refs.chart.init()
},
// 3. 更新阶段
beforeUpdate() {
// 数据更新,DOM未重新渲染
},
updated() {
// DOM已重新渲染
// 注意:避免在此修改数据,可能导致无限循环
},
// 4. 销毁阶段
beforeDestroy() {
// 组件销毁前
// 适合:清除定时器、解绑事件
clearInterval(this.timer)
},
destroyed() {
// 组件已销毁
}
}
Vue3生命周期:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
// beforeCreate和created的逻辑放在setup中
console.log('setup')
onBeforeMount(() => {
console.log('beforeMount')
})
onMounted(() => {
console.log('mounted')
})
onBeforeUpdate(() => {
console.log('beforeUpdate')
})
onUpdated(() => {
console.log('updated')
})
onBeforeUnmount(() => {
console.log('beforeUnmount')
})
onUnmounted(() => {
console.log('unmounted')
})
}
}
父子组件生命周期执行顺序:
加载渲染:
父beforeCreate → 父created → 父beforeMount
→ 子beforeCreate → 子created → 子beforeMount
→ 子mounted → 父mounted
更新:
父beforeUpdate → 子beforeUpdate
→ 子updated → 父updated
销毁:
父beforeDestroy → 子beforeDestroy
→ 子destroyed → 父destroyed
React框架
1. React Hooks原理及使用
答案:
常用Hooks:
import {
useState,
useEffect,
useContext,
useReducer,
useCallback,
useMemo,
useRef,
useLayoutEffect
} from 'react'
function Component() {
// 1. useState - 状态管理
const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: '张三', age: 20 })
// 函数式更新
setCount(prev => prev + 1)
// 2. useEffect - 副作用
useEffect(() => {
// 副作用逻辑
document.title = `Count: ${count}`
// 清理函数
return () => {
document.title = 'React App'
}
}, [count]) // 依赖数组
// 3. useContext - 上下文
const theme = useContext(ThemeContext)
// 4. useReducer - 复杂状态管理
const [state, dispatch] = useReducer(reducer, initialState)
// 5. useCallback - 缓存函数
const handleClick = useCallback(() => {
console.log(count)
}, [count])
// 6. useMemo - 缓存值
const expensiveValue = useMemo(() => {
return computeExpensiveValue(count)
}, [count])
// 7. useRef - 引用DOM或持久化值
const inputRef = useRef(null)
const countRef = useRef(count) // 不会触发重渲染
useEffect(() => {
inputRef.current.focus()
}, [])
// 8. useLayoutEffect - 同步执行
useLayoutEffect(() => {
// DOM变更后同步执行
}, [])
return <div>...</div>
}
自定义Hook:
// 自定义Hook - 必须以use开头
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = useCallback(() => {
setCount(c => c + 1)
}, [])
const decrement = useCallback(() => {
setCount(c => c - 1)
}, [])
const reset = useCallback(() => {
setCount(initialValue)
}, [initialValue])
return { count, increment, decrement, reset }
}
// 使用
function Counter() {
const { count, increment, decrement, reset } = useCounter(0)
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
)
}
Hooks实现原理:
// 简化版实现
let currentFiber = null
let hookIndex = 0
function useState(initialState) {
const fiber = currentFiber
const hooks = fiber.hooks || (fiber.hooks = [])
const hook = hooks[hookIndex] || (hooks[hookIndex] = {
state: initialState,
queue: []
})
// 执行更新队列
hook.queue.forEach(action => {
hook.state = typeof action === 'function' ? action(hook.state) : action
})
hook.queue = []
const setState = (action) => {
hook.queue.push(action)
scheduleUpdate() // 调度更新
}
hookIndex++
return [hook.state, setState]
}
function useEffect(callback, deps) {
const fiber = currentFiber
const hooks = fiber.hooks || (fiber.hooks = [])
const hook = hooks[hookIndex] || (hooks[hookIndex] = {})
const hasChanged = !hook.deps || deps.some((dep, i) => dep !== hook.deps[i])
if (hasChanged) {
hook.cleanup && hook.cleanup()
hook.cleanup = callback()
hook.deps = deps
}
hookIndex++
}
2. React组件性能优化
答案:
// 1. React.memo - 浅比较props
const MemoComponent = React.memo(function Component({ name, age }) {
console.log('render')
return <div>{name} - {age}</div>
})
// 自定义比较函数
const MemoComponent2 = React.memo(Component, (prevProps, nextProps) => {
return prevProps.id === nextProps.id
})
// 2. useMemo - 缓存计算结果
function Component({ items }) {
const expensiveResult = useMemo(() => {
return items.reduce((acc, item) => acc + item.value, 0)
}, [items]) // 只在items变化时重新计算
return <div>{expensiveResult}</div>
}
// 3. useCallback - 缓存函数
function Parent() {
const [count, setCount] = useState(0)
const [name, setName] = useState('')
// 不使用useCallback,每次渲染都会创建新函数
const handleClick1 = () => {
console.log(count)
}
// 使用useCallback,只在count变化时创建新函数
const handleClick2 = useCallback(() => {
console.log(count)
}, [count])
return <Child onClick={handleClick2} />
}
// 4. PureComponent - 类组件优化
class PureComp extends React.PureComponent {
render() {
return <div>{this.props.name}</div>
}
}
// 5. shouldComponentUpdate - 手动控制更新
class Component extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id
}
render() {
return <div>{this.props.name}</div>
}
}
// 6. 虚拟列表 - 只渲染可见区域
import { FixedSizeList } from 'react-window'
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index]}</div>
)
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
)
}
// 7. 懒加载 - 按需加载组件
const LazyComponent = React.lazy(() => import('./Component'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
)
}
// 8. 代码分割 - 路由懒加载
const Home = React.lazy(() => import('./Home'))
const About = React.lazy(() => import('./About'))
// 9. 避免内联对象和函数
// ❌ 不好的做法
<Component style={{ color: 'red' }} onClick={() => {}} />
// ✅ 好的做法
const style = { color: 'red' }
const handleClick = () => {}
<Component style={style} onClick={handleClick} />
// 10. 使用Key优化列表渲染
{items.map(item => (
<Item key={item.id} {...item} />
))}
3. React状态管理(Redux、Zustand)
答案:
Redux使用:
// 1. 创建store
import { createStore } from 'redux'
const initialState = { count: 0 }
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 }
case 'DECREMENT':
return { count: state.count - 1 }
default:
return state
}
}
const store = createStore(reducer)
// 2. Redux Toolkit(推荐)
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: state => {
state.count += 1 // 可以直接修改(内部使用Immer)
},
decrement: state => {
state.count -= 1
},
incrementByAmount: (state, action) => {
state.count += action.payload
}
}
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
})
// 3. 在组件中使用
import { useSelector, useDispatch } from 'react-redux'
function Counter() {
const count = useSelector(state => state.counter.count)
const dispatch = useDispatch()
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
)
}
Zustand使用(更轻量):
import create from 'zustand'
// 创建store
const useStore = create((set, get) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
// 异步action
fetchUser: async () => {
const user = await fetch('/api/user').then(res => res.json())
set({ user })
}
}))
// 在组件中使用
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
// 选择性订阅(性能优化)
const count = useStore(state => state.count)
前端工程化
1. Webpack核心概念和配置
答案:
核心概念:
- Entry:入口文件
- Output:输出配置
- Loader:文件转换器
- Plugin:插件,扩展功能
- Mode:模式(development/production)
基础配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// 入口
entry: './src/index.js',
// 输出
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true
},
// 模式
mode: 'production',
// Loader
module: {
rules: [
// JS/JSX
{
test: /.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
// CSS
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
// SCSS
{
test: /.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
},
// 图片
{
test: /.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 10kb以下转base64
}
}
}
]
},
// 插件
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
],
// 优化
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
},
// 开发服务器
devServer: {
port: 3000,
hot: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
2. Vite与Webpack的区别
答案:
| 特性 | Webpack | Vite |
|---|---|---|
| 构建方式 | Bundle | ESM |
| 开发服务器启动 | 慢(需打包) | 快(按需编译) |
| 热更新 | 较慢 | 极快 |
| 生产构建 | Webpack | Rollup |
| 配置复杂度 | 复杂 | 简单 |
| 生态 | 成熟 | 快速发展 |
Vite配置示例:
// vite.config.js
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: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-router', 'pinia']
}
}
}
}
})
性能优化
1. 首屏加载优化方案
答案:
// 1. 路由懒加载
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/about',
component: () => import('./views/About.vue')
}
]
// 2. 组件懒加载
const AsyncComponent = defineAsyncComponent(() => import('./Component.vue'))
// 3. 图片懒加载
<img v-lazy="imageUrl" />
// 或使用Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})
// 4. 资源预加载
<link rel="preload" href="critical.css" as="style">
<link rel="prefetch" href="next-page.js">
// 5. CDN加速
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
// 6. Gzip压缩
// webpack配置
const CompressionPlugin = require('compression-webpack-plugin')
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
test: /.(js|css)$/,
threshold: 10240,
minRatio: 0.8
})
]
// 7. 首屏内联关键CSS
<style>
/* 关键CSS */
</style>
// 8. SSR服务端渲染
// Nuxt.js / Next.js
// 9. 骨架屏
<div class="skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-content"></div>
</div>
// 10. Tree Shaking
// package.json
{
"sideEffects": false
}
2. 长列表优化
答案:
// 1. 虚拟滚动
// Vue3 + vue-virtual-scroller
<template>
<RecycleScroller
:items="items"
:item-size="50"
key-field="id"
v-slot="{ item }"
>
<div class="item">{{ item.name }}</div>
</RecycleScroller>
</template>
// React + react-window
import { FixedSizeList } from 'react-window'
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
)
}
// 2. 分页加载
async function loadMore() {
const { data } = await fetch(`/api/list?page=${page}`)
items.value = [...items.value, ...data]
page++
}
// 3. 时间切片
function timeSlice(tasks, duration = 50) {
let index = 0
function run() {
const start = Date.now()
while (index < tasks.length && Date.now() - start < duration) {
tasks[index]()
index++
}
if (index < tasks.length) {
requestIdleCallback(run)
}
}
requestIdleCallback(run)
}
// 使用
const tasks = items.map(item => () => renderItem(item))
timeSlice(tasks)
// 4. 防抖滚动
const handleScroll = debounce(() => {
// 滚动处理逻辑
}, 200)
浏览器原理
1. 浏览器渲染流程
答案:
1. 解析HTML → 构建DOM树
2. 解析CSS → 构建CSSOM树
3. 合并DOM树和CSSOM树 → 构建渲染树(Render Tree)
4. 布局(Layout)→ 计算元素位置和大小
5. 绘制(Paint)→ 将渲染树绘制到屏幕
6. 合成(Composite)→ 合成图层
优化点:
// 1. 减少重排(Reflow)
// ❌ 触发多次重排
element.style.width = '100px'
element.style.height = '100px'
element.style.margin = '10px'
// ✅ 批量修改
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;'
// 或
element.classList.add('new-style')
// 2. 使用transform代替left/top
// ❌ 触发重排
element.style.left = '100px'
// ✅ 只触发合成
element.style.transform = 'translateX(100px)'
// 3. 读写分离
// ❌ 强制同步布局
const width = element.offsetWidth
element.style.width = width + 10 + 'px'
const height = element.offsetHeight
element.style.height = height + 10 + 'px'
// ✅ 批量读取,批量写入
const width = element.offsetWidth
const height = element.offsetHeight
element.style.width = width + 10 + 'px'
element.style.height = height + 10 + 'px'
// 4. 使用DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div')
fragment.appendChild(div)
}
document.body.appendChild(fragment)
// 5. 使用will-change提示浏览器
.element {
will-change: transform;
}
2. 浏览器缓存机制
答案:
// 1. 强缓存
// Cache-Control
Cache-Control: max-age=3600 // 1小时
Cache-Control: no-cache // 每次需验证
Cache-Control: no-store // 不缓存
// Expires(HTTP/1.0,已过时)
Expires: Wed, 21 Oct 2025 07:28:00 GMT
// 2. 协商缓存
// Last-Modified / If-Modified-Since
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT
// ETag / If-None-Match(更精确)
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// 3. 缓存策略
// HTML
Cache-Control: no-cache
// CSS/JS(带hash)
Cache-Control: max-age=31536000 // 1年
// 图片
Cache-Control: max-age=2592000 // 30天
// 4. Service Worker缓存
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/styles/main.css',
'/scripts/main.js'
])
})
)
})
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request)
})
)
})
网络与安全
1. 跨域解决方案
答案:
// 1. CORS(最常用)
// 服务端设置
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.header('Access-Control-Allow-Credentials', 'true')
next()
})
// 2. JSONP(仅支持GET)
function jsonp(url, callback) {
const script = document.createElement('script')
script.src = `${url}?callback=${callback}`
document.body.appendChild(script)
}
window.handleData = function(data) {
console.log(data)
}
jsonp('http://api.example.com/data', 'handleData')
// 3. 代理服务器
// webpack devServer
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
// Vite
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^/api/, '')
}
}
}
// 4. postMessage
// 页面A
window.addEventListener('message', event => {
if (event.origin === 'http://example.com') {
console.log(event.data)
}
})
// 页面B
window.parent.postMessage('数据', 'http://example.com')
// 5. nginx反向代理
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
2. 常见Web安全问题
答案:
// 1. XSS(跨站脚本攻击)
// 防御:转义输出
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
// Vue自动转义
<div>{{ userInput }}</div>
// 使用v-html需谨慎
<div v-html="sanitizedHTML"></div>
// 2. CSRF(跨站请求伪造)
// 防御:Token验证
// 后端生成token
const csrfToken = generateToken()
res.cookie('csrf-token', csrfToken)
// 前端发送请求时携带
axios.defaults.headers.common['X-CSRF-Token'] = getCookie('csrf-token')
// 3. SQL注入
// ❌ 危险的拼接
const query = `SELECT * FROM users WHERE username = '${username}'`
// ✅ 使用参数化查询
const query = 'SELECT * FROM users WHERE username = ?'
db.query(query, [username])
// 4. 点击劫持
// 防御:X-Frame-Options
X-Frame-Options: DENY
// 或
X-Frame-Options: SAMEORIGIN
// CSP
Content-Security-Policy: frame-ancestors 'self'
// 5. HTTPS
// 使用HTTPS传输敏感数据
// 设置Secure Cookie
Set-Cookie: sessionId=xxx; Secure; HttpOnly; SameSite=Strict
算法与数据结构
1. 常见算法题
答案:
// 1. 防抖
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 2. 节流
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
// 3. 深拷贝
function deepClone(obj, map = new WeakMap()) {
// 处理null和非对象
if (obj === null || typeof obj !== 'object') {
return obj
}
// 处理循环引用
if (map.has(obj)) {
return map.get(obj)
}
// 处理日期
if (obj instanceof Date) {
return new Date(obj)
}
// 处理正则
if (obj instanceof RegExp) {
return new RegExp(obj)
}
// 处理数组和对象
const clone = Array.isArray(obj) ? [] : {}
map.set(obj, clone)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map)
}
}
return clone
}
// 4. 数组扁平化
function flatten(arr, depth = 1) {
if (depth === 0) return arr
return arr.reduce((acc, cur) => {
return acc.concat(
Array.isArray(cur) ? flatten(cur, depth - 1) : cur
)
}, [])
}
// 或使用flat
const arr = [1, [2, [3, [4]]]].flat(Infinity)
// 5. 数组去重
// 方法1: Set
const unique = arr => [...new Set(arr)]
// 方法2: filter
const unique2 = arr => arr.filter((item, index) => arr.indexOf(item) === index)
// 方法3: reduce
const unique3 = arr => arr.reduce((acc, cur) => {
return acc.includes(cur) ? acc : [...acc, cur]
}, [])
// 6. 实现Promise.all
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
results[index] = value
count++
if (count === promises.length) {
resolve(results)
}
},
reason => reject(reason)
)
})
})
}
// 7. 实现发布订阅
class EventEmitter {
constructor() {
this.events = {}
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => {
callback(...args)
})
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args)
this.off(event, wrapper)
}
this.on(event, wrapper)
}
}
// 8. 柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
// 使用
const add = (a, b, c) => a + b + c
const curriedAdd = curry(add)
curriedAdd(1)(2)(3) // 6
curriedAdd(1, 2)(3) // 6
项目实战
1. 如何设计一个组件库
答案:
// 1. 目录结构
components/
├── Button/
│ ├── index.vue
│ ├── index.ts
│ └── style.css
├── Input/
├── ...
├── index.ts // 导出所有组件
// 2. 组件设计原则
// - 单一职责
// - 可组合
// - 可配置
// - 可扩展
// 3. Button组件示例
<template>
<button
:class="['btn', `btn-${type}`, `btn-${size}`, { 'btn-disabled': disabled }]"
:disabled="disabled"
@click="handleClick"
>
<slot></slot>
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'default',
validator: value => ['default', 'primary', 'danger'].includes(value)
},
size: {
type: String,
default: 'medium'
},
disabled: Boolean
})
const emit = defineEmits(['click'])
const handleClick = (e) => {
if (!props.disabled) {
emit('click', e)
}
}
</script>
// 4. 导出组件
// index.ts
import Button from './Button'
import Input from './Input'
const components = [Button, Input]
const install = (app) => {
components.forEach(component => {
app.component(component.name, component)
})
}
export { Button, Input }
export default { install }
// 5. 使用
import MyUI from 'my-ui'
app.use(MyUI)
// 或按需引入
import { Button } from 'my-ui'
2. 微前端架构设计
答案:
// 使用qiankun框架
// 主应用
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
{
name: 'app1',
entry: '//localhost:8081',
container: '#container',
activeRule: '/app1',
props: {
data: { user: 'admin' }
}
},
{
name: 'app2',
entry: '//localhost:8082',
container: '#container',
activeRule: '/app2'
}
])
start()
// 子应用
// main.js
let instance = null
function render(props = {}) {
const { container } = props
instance = createApp(App)
instance.mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
// 作为微应用
export async function bootstrap() {
console.log('app bootstraped')
}
export async function mount(props) {
console.log('app mounted')
render(props)
}
export async function unmount() {
console.log('app unmounted')
instance.unmount()
instance = null
}
// 应用间通信
import { initGlobalState } from 'qiankun'
const actions = initGlobalState({
user: null,
theme: 'light'
})
// 监听变化
actions.onGlobalStateChange((state, prev) => {
console.log(state, prev)
})
// 修改状态
actions.setGlobalState({ user: { name: 'admin' } })
这份面试题集涵盖了2025年前端开发的核心知识点,建议重点掌握Vue3、React18、性能优化和工程化相关内容。