前言
最近在准备前端面试的过程中,我整理了一系列高频面试题,发现很多问题都围绕着现代前端框架的底层原理展开。今天就来深度解析这些"既常见又致命"的面试题,帮你打通前端框架的任督二脉!
一、Vue2到Vue3的变革:不只是语法糖
1.1 响应式系统的重写
Vue2使用Object.defineProperty实现响应式,而Vue3改用Proxy:
// Vue2 响应式原理
const data = { count: 0 }
Object.defineProperty(data, 'count', {
get() {
console.log('获取值')
return value
},
set(newVal) {
console.log('设置值')
value = newVal
}
})
// Vue3 响应式原理
const data = { count: 0 }
const proxy = new Proxy(data, {
get(target, key) {
console.log('获取值', key)
return target[key]
},
set(target, key, value) {
console.log('设置值', key, value)
target[key] = value
return true
}
})
优势对比:
- Proxy可以直接监听对象而非属性
- 可以监听数组变化而不需要重写方法
- 支持Map、Set等数据结构
1.2 Composition API 的引入
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ double }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件挂载')
})
return {
count,
double,
increment
}
}
}
</script>
二、JavaScript基础深度拷问
2.1 变量声明与提升
var a = 1
var a = () => {}
console.log(a) // 打印什么?
答案: 打印的是函数 () => {}
原理分析:
- 变量提升:
var a被提升到作用域顶部 - 重复声明:
var允许重复声明,后面的覆盖前面的 - 执行顺序:先赋值
a = 1,然后被重新赋值为函数
扩展思考:
// 如果用 let 会怎样?
let a = 1
let a = () => {} // SyntaxError: Identifier 'a' has already been declared
2.2 事件循环与异步编程
console.log('1')
setTimeout(() => {
console.log('2')
Promise.resolve().then(() => {
console.log('3')
})
}, 0)
Promise.resolve().then(() => {
console.log('4')
setTimeout(() => {
console.log('5')
}, 0)
})
console.log('6')
// 输出顺序:1 -> 6 -> 4 -> 2 -> 3 -> 5
三、构建工具的革命:Webpack vs Vite
3.1 Webpack的打包机制
// webpack 需要先打包再服务
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
contentBase: './dist'
}
}
3.2 Vite的ESM原生加载
// vite.config.js
export default {
server: {
port: 3000
},
build: {
rollupOptions: {
// 构建配置
}
}
}
Vite快的根本原因:
- 基于ES Modules:浏览器直接解析import,无需打包
- 按需编译:只编译当前页面需要的文件
- Esbuild预构建:使用Go编写的Esbuild,比JS打包器快10-100倍
四、React核心机制解析
4.1 setState的同步与异步
class Example extends React.Component {
state = { count: 0 }
handleClick = () => {
// 异步情况
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 0
// 同步情况
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 2
}, 0)
}
}
setState工作流程:
- 将setState调用的更新放入队列
- 合并多次setState调用
- 在合适的时机批量执行更新
4.2 Hooks的设计哲学
// 自定义Hook
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 } = useCounter(0)
return <button onClick={increment}>{count}</button>
}
4.3 虚拟DOM与Diff算法优化
// 虚拟DOM结构
const vnode = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'span',
props: { children: 'Hello' }
}
]
}
}
// Diff算法优化策略
1. 同层比较:只比较同一层次的节点
2. Key优化:通过key复用节点
3. 组件类型判断:类型不同直接替换
4.4 Fiber架构与优先级调度
// Fiber节点结构
const fiberNode = {
type: Function | Class | String,
key: null | String,
stateNode: HTMLElement | Component,
child: Fiber | null,
sibling: Fiber | null,
return: Fiber | null,
pendingProps: Object,
memoizedProps: Object,
updateQueue: UpdateQueue,
// ... 其他属性
}
// 优先级定义
const PriorityLevels = {
ImmediatePriority: 1, // 最高优先级
UserBlockingPriority: 2,
NormalPriority: 3,
LowPriority: 4,
IdlePriority: 5, // 最低优先级
}
Fiber调度流程:
- 将任务分解为多个工作单元
- 为每个工作单元分配优先级
- 使用requestIdleCallback在浏览器空闲时执行低优先级任务
- 高优先级任务可以打断低优先级任务
五、现代Web技术:流式输出与SSE
5.1 Server-Sent Events (SSE)
// 服务端
app.get('/stream', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
})
let count = 0
const timer = setInterval(() => {
res.write(`data: ${JSON.stringify({ count: count++ })}\n\n`)
if (count >= 10) {
clearInterval(timer)
res.write('data: [DONE]\n\n')
res.end()
}
}, 1000)
req.on('close', () => {
clearInterval(timer)
})
})
// 客户端
const eventSource = new EventSource('/stream')
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data)
if (data === '[DONE]') {
eventSource.close()
} else {
console.log('Received:', data)
}
}
六、原型与原型链的终极理解
function Person(name) {
this.name = name
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`)
}
const john = new Person('John')
// 原型链
john.__proto__ === Person.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true
// 继承
function Student(name, grade) {
Person.call(this, name)
this.grade = grade
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student