1. Vue 的双向数据绑定原理(v-model 的实现原理)?
答案:
Vue 2.x:
- 使用
Object.defineProperty劫持对象属性 - 通过
getter收集依赖(Watcher) - 通过
setter触发更新 v-model本质是:value+@input的语法糖
// v-model 等价于
<input :value="message" @input="message = $event.target.value" />
Vue 3:
- 使用
Proxy代理整个对象 - 支持数组和对象深层响应式
- 性能更好,无需递归遍历属性
2. Vue 组件间通信的方式有哪些?
答案:
-
父子组件通信
props:父传子$emit:子传父$parent/$children:访问父/子实例
-
跨级组件通信
provide/inject:祖先提供,后代注入$attrs/$listeners:透传属性和事件
-
兄弟组件通信
- 通过共同的父组件
- 事件总线(EventBus)
- Vuex / Pinia
-
全局状态管理
- Vuex(Vue 2)
- Pinia(Vue 3 推荐)
3. Vue 的响应式原理(Object.defineProperty vs Proxy)?
答案:
Vue 2.x(Object.defineProperty):
- 只能劫持对象属性,需要递归遍历
- 无法监听数组索引和长度变化
- 需要
Vue.set/this.$set添加新属性
Vue 3(Proxy):
- 代理整个对象,无需递归
- 可监听数组变化
- 支持动态添加属性
- 性能更好
// Vue 2
Object.defineProperty(obj, 'key', {
get() { return value },
set(newVal) {
value = newVal
// 触发更新
}
})
// Vue 3
new Proxy(obj, {
get(target, key) { return target[key] },
set(target, key, value) {
target[key] = value
// 触发更新
return true
}
})
4. v-if 和 v-show 的区别?
答案:
| 特性 | v-if | v-show |
|---|---|---|
| 渲染方式 | 条件为 false 时不渲染 DOM | 始终渲染,切换 display: none |
| 切换开销 | 高(创建/销毁组件) | 低(仅切换 CSS) |
| 初始渲染 | 条件为 false 时开销低 | 初始渲染开销高 |
| 使用场景 | 条件很少改变 | 频繁切换显示/隐藏 |
选择建议:
- 需要频繁切换用
v-show - 条件很少改变用
v-if
5. Vue 中 key 的作用?为什么不能用 index?
答案:
key 的作用:
- 唯一标识元素,帮助 Vue 识别节点
- 优化列表渲染性能
- 避免状态错乱
为什么不能用 index:
// 错误示例
<div v-for="(item, index) in list" :key="index">
<input v-model="item.name" />
</div>
// 问题:删除中间项时,index 会变化,导致:
// - 输入框内容错乱
// - 组件状态错乱
// - 性能问题
// 正确做法
<div v-for="item in list" :key="item.id">
<input v-model="item.name" />
</div>
使用唯一 ID 可确保:
- 元素正确复用
- 状态不混乱
- 性能更好
6. Webpack 的打包原理是什么?工作流程是怎样的?
答案:
核心原理:
- 将项目视为依赖图(Dependency Graph)
- 从入口文件开始,递归解析依赖
- 将模块转换为浏览器可执行的代码
工作流程:
1. 初始化阶段
- 读取配置文件和命令行参数
- 创建 Compiler 实例
2. 编译阶段
- 从入口(entry)开始
- 使用 Loader 转换模块(如 .js, .css, .vue)
- 解析模块依赖(AST 分析)
- 构建依赖图(Dependency Graph)
3. 优化阶段
- 执行插件(Plugins)
- 代码分割(Code Splitting)
- Tree Shaking
- 压缩优化
4. 输出阶段
- 生成 Chunk
- 输出到 output 目录
关键概念:
- Entry:入口文件
- Output:输出配置
- Loader:文件转换器(如 babel-loader)
- Plugin:插件,在打包过程中执行任务
- Chunk:代码块,一个或多个模块的集合
7. Webpack 的 Loader 和 Plugin 的区别?
答案:
| 特性 | Loader | Plugin |
|---|---|---|
| 作用时机 | 模块加载时执行 | 整个打包过程 |
| 功能 | 转换文件(如 .ts → .js) | 执行更广泛的任务 |
| 输入 | 接收源文件内容 | 访问整个编译过程 |
| 输出 | 返回转换后的内容 | 可修改输出文件 |
| 配置 | module.rules | plugins 数组 |
Loader 示例:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader' // 转换 ES6+ 代码
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 处理 CSS
}
]
}
}
Plugin 示例:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
}),
new webpack.optimize.UglifyJsPlugin() // 压缩代码
]
}
8. Webpack 的代码分割(Code Splitting)原理和实现方式?
答案:
原理:
- 将代码拆分为多个 chunk
- 按需加载,减少初始加载体积
- 利用浏览器缓存
实现方式:
1. 入口分割:
module.exports = {
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
}
}
2. 动态导入(推荐):
// 使用 import()
const loadModule = () => {
return import('./heavy-module.js').then(module => {
// 使用模块
})
}
// React 懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'))
3. SplitChunksPlugin 配置:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
}
优化效果:
- 减少初始包体积
- 提高加载速度
- 更好的缓存策略
9. Tree Shaking 的原理是什么?如何实现?
答案:
原理:
- 静态分析代码,移除未使用的代码
- 基于 ES6 模块的静态结构(import/export)
- 在打包时删除 dead code
实现条件:
- 使用 ES6 模块(
import/export) - 启用生产模式(
mode: 'production') - 配置
sideEffects: false
配置示例:
// package.json
{
"sideEffects": false, // 标记无副作用
// 或指定有副作用的文件
"sideEffects": ["./src/polyfill.js", "*.css"]
}
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true, // 标记未使用的导出
minimize: true // 压缩时删除
}
}
注意事项:
- CommonJS 不支持(动态导入)
- 有副作用的代码不会被删除
- CSS 的 Tree Shaking 需要额外配置
10. Webpack 打包优化策略有哪些?
答案:
1. 减少打包体积:
// 使用 Tree Shaking
optimization: {
usedExports: true,
minimize: true
}
// 排除不需要的依赖
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
2. 加快构建速度:
// 使用缓存
module.exports = {
cache: {
type: 'filesystem'
},
// 或
cache: true
}
// 多线程打包
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true // 启用多进程
})
]
}
}
3. 代码分割:
// 路由懒加载
const routes = [
{
path: '/home',
component: () => import('./Home.vue')
}
]
4. 资源优化:
// 图片压缩
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { quality: 80 }
}
}
]
}
5. 使用 CDN:
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter'
}
11. Vite 的打包原理是什么?为什么比 Webpack 快?
答案:
核心原理:
开发环境:
- 使用原生 ES 模块(Native ESM)
- 不打包,直接启动开发服务器
- 按需编译,只编译请求的模块
- 使用 esbuild 预构建依赖(比 Webpack 快 10-100 倍)
生产环境:
- 使用 Rollup 打包(更小的包体积)
- 代码分割和优化
为什么快:
-
开发服务器启动快:
Webpack: 打包所有模块 → 启动服务器(慢) Vite: 直接启动服务器 → 按需编译(快) -
HMR(热更新)快:
- Webpack:重新打包相关模块
- Vite:只更新变化的模块,利用 ESM 的浏览器缓存
-
预构建优化:
- 使用 esbuild(Go 编写)预构建 node_modules
- 比 Babel/TypeScript 快 10-100 倍
工作流程:
开发环境:
浏览器请求 → Vite 服务器 → 按需编译 → 返回 ESM → 浏览器执行
生产环境:
源码 → Rollup 打包 → 优化 → 输出文件
12. Vite 和 Webpack 的主要区别?
答案:
| 特性 | Webpack | Vite |
|---|---|---|
| 开发启动 | 慢(需要打包) | 快(无需打包) |
| HMR 速度 | 较慢(重新打包) | 快(只更新变化模块) |
| 打包工具 | Webpack | Rollup(生产环境) |
| 配置复杂度 | 高 | 低(开箱即用) |
| 生态 | 成熟 | 快速发展 |
| 适用场景 | 大型项目,复杂配置 | 现代项目,快速开发 |
选择建议:
- Webpack:需要复杂配置、大量插件、兼容性要求高
- Vite:现代项目、快速开发、Vue 3/React 新项目
13. Rollup 的打包原理和特点?
答案:
核心原理:
- 基于 ES6 模块的静态分析
- Tree Shaking 友好
- 生成更小的包体积
特点:
-
Tree Shaking 优秀:
- 静态分析,精确删除未使用代码
- 比 Webpack 的 Tree Shaking 更彻底
-
输出格式多样:
// rollup.config.js export default { output: { format: 'es', // ES 模块 // format: 'cjs', // CommonJS // format: 'umd', // UMD // format: 'iife' // 立即执行函数 } } -
适合库开发:
- 生成更小的包
- 更好的 Tree Shaking
- 适合发布 npm 包
配置示例:
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'es',
sourcemap: true
},
plugins: [
resolve(),
commonjs(),
terser() // 压缩
],
external: ['react', 'react-dom'] // 外部依赖
}
使用场景:
- 库/组件库开发
- 需要小体积的包
- Vite 生产环境打包
13. 如何优化打包后的代码体积?
答案:
1. 代码分割:
// Webpack
optimization: {
splitChunks: {
chunks: 'all'
}
}
// Vite
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router']
}
}
}
}
2. Tree Shaking:
// 使用 ES 模块
import { debounce } from 'lodash-es' // ✅
// 而不是
import _ from 'lodash' // ❌
3. 压缩代码:
// Webpack
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
}
// Vite(自动启用)
build: {
minify: 'terser'
}
4. 图片优化:
// 使用 WebP 格式
// 图片压缩
// 懒加载图片
5. Gzip/Brotli 压缩:
// 服务器配置
// Nginx
gzip on;
gzip_types text/javascript application/javascript;
6. 分析打包体积:
// webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
7. 按需引入:
// ❌ 全量引入
import _ from 'lodash'
// ✅ 按需引入
import debounce from 'lodash/debounce'
14. 前端性能优化有哪些方法?
答案:
1. 资源加载优化:
// 图片懒加载
<img src="placeholder.jpg" data-src="real.jpg" loading="lazy" />
// 预加载关键资源
<link rel="preload" href="critical.css" as="style" />
<link rel="prefetch" href="next-page.html" />
// CDN 加速
// 使用 CDN 加载静态资源
2. 代码优化:
// 代码分割
const LazyComponent = React.lazy(() => import('./LazyComponent'))
// Tree Shaking
import { debounce } from 'lodash-es' // ✅
// import _ from 'lodash' // ❌
// 防抖节流
const debouncedSearch = debounce(search, 300)
3. 渲染优化:
// 虚拟滚动
// 只渲染可见区域的列表项
// 使用 requestAnimationFrame
function animate() {
// 动画逻辑
requestAnimationFrame(animate)
}
// 避免强制同步布局
// ❌ 强制同步
const width = element.offsetWidth
element.style.width = width + 1 + 'px'
// ✅ 批量读取,批量写入
const width = element.offsetWidth
requestAnimationFrame(() => {
element.style.width = width + 1 + 'px'
})
4. 缓存策略:
// 浏览器缓存
// Cache-Control: max-age=31536000
// Service Worker 缓存
// 离线缓存策略
// HTTP 缓存
// 强缓存 + 协商缓存
5. 网络优化:
// HTTP/2 多路复用
// Gzip/Brotli 压缩
// 减少 HTTP 请求
// 使用 WebSocket 替代轮询
15. 如何优化首屏加载时间?
答案:
1. 减少资源体积:
// 代码分割
const routes = [
{
path: '/home',
component: () => import('./Home.vue') // 懒加载
}
]
// 压缩代码
// webpack: optimization.minimize = true
// Tree Shaking
// 移除未使用的代码
2. 关键资源优化:
<!-- 内联关键 CSS -->
<style>
/* 首屏关键样式 */
</style>
<!-- 延迟非关键 CSS -->
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- 预加载关键资源 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
3. 图片优化:
<!-- 使用 WebP 格式 -->
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="description">
</picture>
<!-- 响应式图片 -->
<img srcset="small.jpg 480w, large.jpg 800w" sizes="(max-width: 600px) 480px, 800px">
<!-- 懒加载 -->
<img src="placeholder.jpg" data-src="real.jpg" loading="lazy">
4. 服务端优化:
// SSR(服务端渲染)
// Next.js, Nuxt.js
// 预渲染
// 静态站点生成(SSG)
// 启用 Gzip/Brotli
// 服务器配置压缩
5. 监控指标:
// FCP (First Contentful Paint) - 首次内容绘制
// LCP (Largest Contentful Paint) - 最大内容绘制
// FID (First Input Delay) - 首次输入延迟
// CLS (Cumulative Layout Shift) - 累积布局偏移
// 使用 Performance API
performance.getEntriesByType('navigation')
16. 如何优化长列表性能?
答案:
1. 虚拟滚动:
// Vue 3 示例
import { VirtualList } from 'vue-virtual-scroll-list'
<VirtualList
:data-key="'id'"
:data-sources="items"
:data-component="Item"
:keeps="20"
/>
// React 示例
import { FixedSizeList } from 'react-window'
<FixedSizeList
height={600}
itemCount={1000}
itemSize={50}
width={300}
>
{Row}
</FixedSizeList>
2. 分页加载:
// 无限滚动
const loadMore = async () => {
const newItems = await fetchItems(page + 1)
items.value.push(...newItems)
page++
}
// 使用 Intersection Observer
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMore()
}
})
observer.observe(loadMoreTrigger.value)
3. 防抖节流:
// 滚动事件节流
const handleScroll = throttle(() => {
// 处理滚动
}, 100)
// 搜索防抖
const handleSearch = debounce((keyword) => {
// 搜索逻辑
}, 300)
4. 使用 key 优化:
<!-- ✅ 使用唯一 key -->
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
<!-- ❌ 不要用 index -->
<div v-for="(item, index) in list" :key="index">
17. 浏览器兼容性问题如何解决?
答案:
1. CSS 兼容性:
/* 使用 Autoprefixer */
/* 自动添加浏览器前缀 */
display: flex;
/* 自动转换为 */
display: -webkit-box;
display: -ms-flexbox;
display: flex;
/* 使用 PostCSS */
/* 自动处理兼容性 */
/* 使用 normalize.css 或 reset.css */
/* 统一浏览器默认样式 */
2. JavaScript 兼容性:
// 使用 Babel 转译
// ES6+ → ES5
// Polyfill
import 'core-js/stable'
import 'regenerator-runtime/runtime'
// 条件判断
if (typeof Promise === 'undefined') {
// 加载 Promise polyfill
}
// 特性检测
if ('IntersectionObserver' in window) {
// 使用 IntersectionObserver
} else {
// 降级方案
}
3. 使用工具:
// package.json
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
4. 渐进增强:
// 先实现基础功能
// 再添加增强功能
// 检测支持情况
const supportsWebP = () => {
const canvas = document.createElement('canvas')
return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0
}
18. 如何处理移动端适配?
答案:
1. 视口设置:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
2. rem 方案:
// 动态设置根字体大小
function setRem() {
const width = document.documentElement.clientWidth
const fontSize = width / 375 * 16 // 基于 375px 设计稿
document.documentElement.style.fontSize = fontSize + 'px'
}
setRem()
window.addEventListener('resize', setRem)
// 使用 postcss-pxtorem
// 自动转换 px 为 rem
3. vw/vh 方案:
/* 使用 vw 单位 */
.container {
width: 100vw;
font-size: 4vw; /* 基于视口宽度 */
}
/* 使用 postcss-px-to-viewport */
/* 自动转换 px 为 vw */
4. 媒体查询:
/* 响应式设计 */
@media (max-width: 768px) {
.container {
width: 100%;
}
}
/* 移动端优先 */
.container {
width: 100%;
}
@media (min-width: 768px) {
.container {
width: 750px;
}
}
19. 如何优化 JavaScript 执行性能?
答案:
1. 避免阻塞主线程:
// 使用 Web Worker
const worker = new Worker('worker.js')
worker.postMessage(data)
worker.onmessage = (e) => {
console.log(e.data)
}
// 使用 requestIdleCallback
requestIdleCallback(() => {
// 低优先级任务
})
// 使用 setTimeout 分片
function processChunk(items, index = 0) {
const chunk = items.slice(index, index + 100)
chunk.forEach(processItem)
if (index < items.length) {
setTimeout(() => processChunk(items, index + 100), 0)
}
}
2. 优化循环:
// ❌ 在循环中查询 DOM
for (let i = 0; i < items.length; i++) {
document.getElementById('item-' + i).style.color = 'red'
}
// ✅ 批量操作
const fragment = document.createDocumentFragment()
items.forEach(item => {
const el = document.createElement('div')
fragment.appendChild(el)
})
container.appendChild(fragment)
3. 防抖节流:
// 防抖:延迟执行
function debounce(func, wait) {
let timeout
return function(...args) {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}
// 节流:限制频率
function throttle(func, limit) {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
4. 使用事件委托:
// ❌ 每个元素绑定事件
items.forEach(item => {
item.addEventListener('click', handleClick)
})
// ✅ 事件委托
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e)
}
})
20. 如何处理内存泄漏?
答案:
1. 清理事件监听器:
// Vue
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
// React
useEffect(() => {
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
2. 清理定时器:
const timer = setInterval(() => {
// 逻辑
}, 1000)
// 清理
clearInterval(timer)
3. 避免闭包引用:
// ❌ 可能导致内存泄漏
function createHandler() {
const largeData = new Array(1000000).fill(0)
return function() {
// largeData 一直被引用
}
}
// ✅ 使用后清理
function createHandler() {
const largeData = new Array(1000000).fill(0)
return function() {
// 使用 largeData
// 使用后设置为 null
largeData = null
}
}
4. 清理 DOM 引用:
const element = document.getElementById('myElement')
// 使用后清理
element = null
5. 使用 WeakMap/WeakSet:
// 弱引用,不会阻止垃圾回收
const weakMap = new WeakMap()
weakMap.set(element, data)
21. 什么是跨域?如何解决跨域问题?
答案:
跨域定义:
- 协议、域名、端口任一不同即为跨域
- 浏览器同源策略限制
解决方案:
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') // 允许携带 cookie
next()
})
// 预检请求处理
if (req.method === 'OPTIONS') {
res.sendStatus(200)
return
}
2. 代理(Proxy):
// webpack devServer
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
// Vite
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
3. JSONP:
// 只支持 GET 请求
function jsonp(url, callback) {
const script = document.createElement('script')
script.src = `${url}?callback=${callback}`
document.body.appendChild(script)
window[callback] = (data) => {
// 处理数据
document.body.removeChild(script)
}
}
4. postMessage:
// 窗口间通信
// 父窗口
iframe.contentWindow.postMessage('data', 'http://child-domain.com')
// 子窗口
window.addEventListener('message', (e) => {
if (e.origin === 'http://parent-domain.com') {
console.log(e.data)
}
})
22. CORS 的预检请求(OPTIONS)是什么?
答案:
预检请求触发条件:
- 非简单请求(复杂请求)
- 包含自定义头部
- 使用 PUT、DELETE 等方法
- Content-Type 为 application/json
简单请求:
// GET、POST、HEAD
// Content-Type: text/plain, application/x-www-form-urlencoded, multipart/form-data
// 无自定义头部
复杂请求示例:
// 会触发预检请求
fetch('http://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ name: 'Vue' })
})
// 浏览器先发送 OPTIONS 请求
// OPTIONS /data HTTP/1.1
// Origin: http://example.com
// Access-Control-Request-Method: PUT
// Access-Control-Request-Headers: content-type,authorization
服务端处理:
// 处理预检请求
app.options('/api/*', (req, res) => {
res.header('Access-Control-Allow-Origin', req.headers.origin)
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization')
res.header('Access-Control-Max-Age', '86400') // 缓存 24 小时
res.sendStatus(200)
})
23. 如何实现跨域携带 Cookie?
答案:
前端设置:
// 必须设置 withCredentials
fetch('http://api.example.com/data', {
credentials: 'include', // 或 'same-origin'
headers: {
'Content-Type': 'application/json'
}
})
// XMLHttpRequest
const xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open('GET', 'http://api.example.com/data')
xhr.send()
服务端设置:
// 必须设置
res.header('Access-Control-Allow-Credentials', 'true')
// 不能使用通配符 *
res.header('Access-Control-Allow-Origin', 'http://example.com') // 必须指定域名
注意事项:
Access-Control-Allow-Origin不能是*- 必须明确指定允许的域名
- Cookie 的 SameSite 属性影响跨域
24. 什么是同源策略?如何绕过?
答案:
同源策略定义:
- 协议、域名、端口必须完全相同
- 限制:Cookie、LocalStorage、DOM、AJAX
绕过方法:
1. CORS:
// 服务端设置响应头
Access-Control-Allow-Origin: *
2. 代理服务器:
// 同源请求代理服务器
// 代理服务器转发到目标服务器
3. JSONP:
// 利用 script 标签不受同源限制
<script src="http://api.example.com/data?callback=handleData"></script>
4. postMessage:
// 窗口间通信
window.postMessage(data, targetOrigin)
5. document.domain:
// 仅适用于主域相同的情况
// a.example.com 和 b.example.com
document.domain = 'example.com'
25. HTTP 和 HTTPS 的区别?
答案:
| 特性 | HTTP | HTTPS |
|---|---|---|
| 协议 | 超文本传输协议 | 安全超文本传输协议 |
| 端口 | 80 | 443 |
| 加密 | 明文传输 | SSL/TLS 加密 |
| 证书 | 不需要 | 需要 CA 证书 |
| 性能 | 快 | 稍慢(加密开销) |
| SEO | 一般 | 更好(Google 推荐) |
HTTPS 工作原理:
1. 客户端请求 HTTPS 连接
2. 服务器返回证书
3. 客户端验证证书
4. 双方协商加密算法
5. 建立加密连接
6. 开始安全传输
为什么需要 HTTPS:
- 防止中间人攻击
- 保护数据隐私
- 提升 SEO 排名
- 浏览器标记不安全
26. HTTP 缓存机制是什么?
答案:
强缓存:
# 响应头
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT
# Cache-Control 优先级更高
# max-age: 缓存时间(秒)
# no-cache: 需要验证
# no-store: 不缓存
# private: 仅客户端缓存
# public: 可被任何缓存
协商缓存:
# Last-Modified / If-Modified-Since
Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2020 07:28:00 GMT
# ETag / If-None-Match(优先级更高)
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
缓存策略:
// 1. 强缓存优先
// 2. 强缓存失效,使用协商缓存
// 3. 协商缓存 304,使用本地缓存
// 4. 协商缓存 200,返回新资源
// 最佳实践
// HTML: no-cache(总是验证)
// CSS/JS: max-age=31536000(长期缓存,文件名带 hash)
// 图片: max-age=2592000(30天)
27. 什么是 HTTP/2?有什么优势?
答案:
HTTP/2 特性:
1. 多路复用(Multiplexing):
// HTTP/1.1: 每个请求一个连接
// HTTP/2: 一个连接多个请求并行
// 解决了队头阻塞问题
2. 服务器推送(Server Push):
// 服务器主动推送资源
// 减少往返次数
3. 头部压缩(HPACK):
// 压缩 HTTP 头部
// 减少传输数据量
4. 二进制分帧:
// 二进制协议
// 更高效解析
优势对比:
HTTP/1.1:
- 每个请求一个连接
- 头部重复传输
- 队头阻塞
HTTP/2:
- 多路复用
- 头部压缩
- 服务器推送
- 性能提升 50%+
28. JavaScript 事件循环(Event Loop)原理?
答案:
核心概念:
- JavaScript 是单线程语言
- 事件循环负责调度异步任务
- 分为调用栈(Call Stack)、消息队列(Message Queue)、微任务队列(Microtask Queue)
执行顺序:
1. 执行同步代码(调用栈)
2. 执行所有微任务(Promise.then, queueMicrotask, MutationObserver)
3. 执行一个宏任务(setTimeout, setInterval, I/O)
4. 重复步骤 2-3
代码示例:
console.log('1') // 同步
setTimeout(() => {
console.log('2') // 宏任务
}, 0)
Promise.resolve().then(() => {
console.log('3') // 微任务
})
console.log('4') // 同步
// 输出顺序:1, 4, 3, 2
执行流程:
同步代码 → 微任务队列 → 宏任务队列 → 微任务队列 → ...
29. 宏任务(MacroTask)和微任务(MicroTask)的区别?
答案:
| 特性 | 宏任务 | 微任务 |
|---|---|---|
| 执行时机 | 每次事件循环执行一个 | 每次事件循环执行全部 |
| 优先级 | 低 | 高 |
| 常见 API | setTimeout, setInterval, I/O | Promise.then, queueMicrotask |
| 执行顺序 | 在微任务之后 | 在宏任务之前 |
宏任务类型:
// 宏任务
setTimeout(() => {}, 0)
setInterval(() => {}, 0)
setImmediate() // Node.js
I/O 操作
UI 渲染
微任务类型:
// 微任务
Promise.then()
Promise.catch()
Promise.finally()
queueMicrotask()
MutationObserver
process.nextTick() // Node.js(优先级最高)
执行示例:
console.log('start')
setTimeout(() => {
console.log('timeout1') // 宏任务
Promise.resolve().then(() => {
console.log('promise1') // 微任务
})
}, 0)
Promise.resolve().then(() => {
console.log('promise2') // 微任务
setTimeout(() => {
console.log('timeout2') // 宏任务
}, 0)
})
console.log('end')
// 输出:start, end, promise2, timeout1, promise1, timeout2
关键点:
- 微任务优先级高于宏任务
- 每次宏任务执行后,会清空所有微任务
process.nextTick优先级最高(Node.js)
宏任务和微任务列表
宏任务(MacroTask)
- setTimeout
- setInterval
- setImmediate(仅 Node.js)
- I/O 操作(文件读取、网络请求等)
- UI 渲染(浏览器)
- MessageChannel
- postMessage
- script 标签加载
- requestAnimationFrame(浏览器)
- Web Workers
微任务(MicroTask)
- Promise(包括 .then、.catch、.finally)
- queueMicrotask
- MutationObserver
- process.nextTick(仅 Node.js,优先级最高)
- async/await 的后续代码
30. Node.js 的事件循环和浏览器有什么区别?
答案:
浏览器事件循环:
同步代码 → 微任务 → 宏任务 → 微任务 → ...
Node.js 事件循环(6 个阶段):
1. timers(定时器阶段)
- 执行 setTimeout, setInterval
2. pending callbacks(待处理回调)
- 执行延迟的 I/O 回调
3. idle, prepare(内部使用)
4. poll(轮询阶段)
- 获取新的 I/O 事件
- 执行 I/O 相关回调
5. check(检查阶段)
- 执行 setImmediate 回调
6. close callbacks(关闭回调)
- 执行 close 事件回调
关键区别:
1. process.nextTick 优先级最高:
console.log('1')
setTimeout(() => console.log('2'), 0)
setImmediate(() => console.log('3'))
process.nextTick(() => console.log('4'))
// Node.js 输出:1, 4, 2, 3
// process.nextTick 优先级最高
2. setImmediate vs setTimeout:
// 在 I/O 回调中
fs.readFile('file.txt', () => {
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))
// 输出:immediate, timeout
// setImmediate 在 check 阶段,先于 timers 阶段
})
3. 微任务执行时机:
// Node.js 中,微任务在每个阶段之间执行
// 浏览器中,微任务在宏任务之后执行
执行顺序(Node.js):
同步代码 → process.nextTick → 微任务 → 宏任务阶段 → process.nextTick → 微任务 → ...