你有没有在面试时被问到这样的问题:“浏览器中为什么会出现跨域?如何解决?”、“事件循环到底是怎么执行的?”、“你怎么优化一个页面的加载速度?”这些看似基础的问题,其实背后藏着大量的底层原理。如果你只是会“套答案”,那 HR 和技术面试官可不买账。
我们将通过一个个典型的 JavaScript 面试题,深入讲解跨域的原理与各种解决方式(CORS、JSONP、代理等)、事件循环的执行过程(宏任务、微任务、任务队列)以及常见的前端性能优化策略。从原理讲起,到实际案例分析,帮你建立真正扎实的 JS 基础,轻松应对中高级前端面试。就算不面试,这些知识对实际开发提升也非常有用,准备好了吗?我们正式开始。
跨域解决方案
跨域的定义与原理
跨域(Cross-Origin Resource Sharing,CORS)是指浏览器基于同源策略(Same-Origin Policy)限制不同源的资源访问。同源要求协议、域名、端口号三者相同,例如:
https://example.com:443
和https://api.example.com:443
不同源(域名不同)。http://example.com:80
和https://example.com:443
不同源(协议不同)。
同源策略的限制:
- 禁止通过
fetch
或XMLHttpRequest
访问不同源的 API。 - 限制不同源 iframe 的 DOM 操作。
- 限制 Cookie、LocalStorage 的跨域访问。
跨域的场景:
- 前端调用后端 API(如
api.example.com
)。 - 加载第三方资源(如 CDN 的脚本、字体、图片)。
- 微前端架构中子模块通信。
跨域原理:
- 浏览器在发送跨域请求时,添加
Origin
请求头。 - 服务器响应
Access-Control-Allow-Origin
头,控制允许的源。 - 复杂请求(如
PUT
、DELETE
)触发预检请求(OPTIONS
)。
跨域解决方案
1. CORS(跨域资源共享)
CORS 是最标准的跨域解决方案,通过服务器配置允许跨域请求。
实现(Node.js + Express):
const express = require('express');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://frontend.example.com'); // 允许特定源
res.header('Access-Control-Allow-Methods', 'GET, POST,PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
app.get('/api/data', (req, res) => {
res.json({ message: 'Cross-domain data' });
});
app.listen(3000, () => console.log('Server on port 3000'));
前端调用:
fetch('https://api.example.com/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
逐步分析:
- 服务器设置
Access-Control-Allow-Origin
,允许https://frontend.example.com
访问。 Access-Control-Allow-Methods
指定允许的 HTTP 方法。- 复杂请求触发
OPTIONS
预检,服务器响应允许的头和方法。 - 前端通过
fetch
获取数据,浏览器验证 CORS 头。
注意事项:
- 使用
'*'
允许所有源,但不能与withCredentials
(携带 Cookie)一起使用。 - 需后端配合,前端无法单方面解决。
2. JSONP(JSON with Padding)
JSONP 利用 <script>
标签无跨域限制的特性,通过回调函数传递数据。
实现(服务器):
app.get('/api/jsonp', (req, res) => {
const callback = req.query.callback;
const data = { message: 'JSONP data' };
res.send(`${callback}(${JSON.stringify(data)})`);
});
前端调用:
function handleData(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'https://api.example.com/api/jsonp?callback=handleData';
document.body.appendChild(script);
逐步分析:
- 前端动态创建
<script>
,请求带callback
参数。 - 服务器返回
handleData({...})
,浏览器执行回调。 - 数据通过回调函数传递到前端。
优缺点:
- 优点:简单,兼容老浏览器。
- 缺点:仅支持 GET 请求,存在安全风险(如 XSS)。
3. 代理服务器
通过代理服务器中转请求,绕过浏览器同源限制。
实现(Nginx 配置):
server {
listen 80;
server_name frontend.example.com;
location /api/ {
proxy_pass https://api.example.com/;
proxy_set_header Host api.example.com;
}
}
前端调用:
fetch('/api/data') // 实际请求 https://api.example.com/data
.then(response => response.json())
.then(data => console.log(data));
逐步分析:
- 前端请求同源代理(如
/api/data
)。 - Nginx 将请求转发到目标服务器。
- 服务器响应数据,代理返回给前端。
优缺点:
- 优点:灵活,支持所有请求类型。
- 缺点:需额外配置代理服务器。
4. WebSocket
WebSocket 是一种全双工通信协议,不受同源策略限制。
实现(服务器 - Node.js):
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
ws.on('message', message => {
ws.send(`Received: ${message}`);
});
});
前端调用:
const ws = new WebSocket('ws://api.example.com:8080');
ws.onopen = () => ws.send('Hello');
ws.onmessage = event => console.log(event.data);
分析:
- WebSocket 建立持久连接,适合实时通信。
- 不依赖 HTTP 头,跨域无需额外配置。
5. PostMessage(窗口间通信)
postMessage
用于不同窗口(如 iframe、弹出窗口)间的跨域通信。
实现(发送端):
const iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage({ message: 'Hello' }, 'https://api.example.com');
接收端(iframe 内):
window.addEventListener('message', event => {
if (event.origin !== 'https://frontend.example.com') return;
console.log(event.data); // { message: 'Hello' }
});
分析:
event.origin
验证消息来源,防止安全问题。- 适合嵌入第三方页面或微前端。
跨域面试题
面试题 1:CORS 预检请求
问题:以下请求为何触发预检?如何优化?
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Custom-Header': 'value'
},
body: JSON.stringify({ key: 'value' })
});
答案:
- 原因:非简单请求(自定义头
Custom-Header
、非 GET/POST/HEAD 方法)触发OPTIONS
预检。 - 优化:
-
移除自定义头,使用标准头。
-
服务器设置
Access-Control-Max-Age
缓存预检结果:res.header('Access-Control-Max-Age', '86400'); // 缓存 1 天
-
面试题 2:JSONP 安全问题
问题:JSONP 有哪些安全风险?如何 mitigmitigate?
答案:
- 风险:XSS 攻击,恶意脚本可能注入回调。
- 缓解:
-
验证回调函数名:
if (!/^[a-zA-Z0-9_]+$/.test(callback)) { res.status(400).send('Invalid callback'); return; }
-
使用 CORS 替代 JSONP。
-
面试题 3:代理与 CORS 比较
问题:代理和 CORS 各适合哪些场景?
答案:
- CORS:适合后端可控场景,标准、安全。
- 代理:适合后端不可控(如第三方 API)或开发环境。
Event Loop 与异步
Event Loop 的定义与原理
Event Loop 是 JavaScript 单线程模型的核心,负责协调任务执行。JavaScript 运行时包括:
- 调用栈(Call Stack):执行同步代码。
- 任务队列(Task Queue):存储宏任务(如
setTimeout
、setInterval
)。 - 微任务队列(Microtask Queue):存储微任务(如
Promise
、MutationObserver
)。 - Event Loop:循环检查队列,将任务推入调用栈。
执行流程:
- 执行同步代码,清空调用栈。
- 检查微任务队列,执行所有微任务。
- 检查宏任务队列,执行一个宏任务。
- 重复步骤 2-3。
示例:
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
输出:
Start
End
Promise
Timeout
逐步分析:
console.log('Start')
入栈,执行,输出。setTimeout
注册宏任务,推入任务队列。Promise.then
注册微任务,推入微任务队列。console.log('End')
入栈,执行,输出。- 调用栈清空,执行微任务
Promise
,输出。 - 执行宏任务
Timeout
,输出。
异步编程机制
JavaScript 的异步编程依赖回调、Promise 和 async/await。
回调
function fetchData(callback) {
setTimeout(() => callback('Data'), 1000);
}
fetchData(data => console.log(data)); // Data
问题:回调地狱(Callback Hell)。
Promise
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve('Data'), 1000);
});
}
fetchData().then(data => console.log(data)); // Data
分析:
Promise
提供链式调用,解决回调嵌套。- 状态:
pending
、fulfilled
、rejected
。
Async/Await
async function getData() {
const data = await fetchData();
console.log(data);
}
getData(); // Data
分析:
async/await
是 Promise 的语法糖,代码更直观。await
暂停执行,直到 Promise 解析。
Event Loop 面试题
面试题 1:微任务与宏任务
问题:以下代码输出什么?
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
promise2
async1 end
setTimeout
逐步分析:
- 同步代码:
- 输出
script start
。 - 调用
async1
,输出async1 start
。 - 调用
async2
,输出async2
。 await async2()
注册微任务(async1 end
)。- 输出
promise1
,then
注册微任务(promise2
)。 - 输出
script end
。
- 输出
- 微任务队列:
promise2
,async1 end
。- 输出
promise2
。 - 输出
async1 end
。
- 输出
- 宏任务队列:
setTimeout
。- 输出
setTimeout
。
- 输出
面试题 2:Promise 错误处理
问题:以下代码有何问题?如何修复?
Promise.resolve().then(() => {
throw new Error('Error');
}).then(() => console.log('Next'));
输出:无输出,错误未捕获。
修复:
Promise.resolve().then(() => {
throw new Error('Error');
}).catch(err => {
console.error(err.message); // Error
}).then(() => console.log('Next')); // Next
分析:
catch
捕获链中的错误。then
在错误处理后继续执行。
面试题 3:Async/Await vs Promise
问题:以下代码等价吗?
// Promise
function fetchData() {
return fetch('/api/data').then(res => res.json());
}
// Async/Await
async function fetchDataAsync() {
const res = await fetch('/api/data');
return res.json();
}
答案:
- 功能等价,但
async/await
更易读。 - 差异:
async
函数返回 Promise。await
暂停执行,需注意性能(如并发请求)。
并发优化:
async function fetchMultiple() {
const [data1, data2] = await Promise.all([
fetch('/api/data1').then(res => res.json()),
fetch('/api/data2').then(res => res.json())
]);
console.log(data1, data2);
}
异步的应用场景
- API 请求:
async function loadUser(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Network error');
return await response.json();
} catch (err) {
console.error(err);
throw err;
}
}
- 动画与定时任务:
function animate(element, duration) {
return new Promise(resolve => {
element.style.transition = `transform ${duration}ms`;
element.style.transform = 'translateX(100px)';
element.addEventListener('transitionend', resolve, { once: true });
});
}
async function runAnimation() {
const el = document.getElementById('box');
await animate(el, 1000);
console.log('Animation complete');
}
前端性能优化
性能优化的重要性
前端性能直接影响用户体验、SEO 和转化率。关键指标包括:
- FCP(First Contentful Paint):首次内容绘制时间。
- TTI(Time to Interactive):页面可交互时间。
- LCP(Largest Contentful Paint):最大内容绘制时间。
优化目标:
- 减少加载时间。
- 优化渲染性能。
- 降低 CPU 和内存占用。
优化策略
1. 资源加载优化
-
压缩与合并:
-
使用 Webpack 压缩 JS/CSS:
// webpack.config.js module.exports = { optimization: { minimize: true } };
-
合并小文件减少请求数。
-
-
懒加载:
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));
- CDN 加速:
- 使用 CDN 加载库(如
https://cdn.jsdelivr.net/npm/vue
)。 - 减少服务器延迟。
- 使用 CDN 加载库(如
2. 渲染优化
-
减少重排(Reflow)与重绘(Repaint):
-
批量修改 DOM:
const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; fragment.appendChild(div); } document.getElementById('container').appendChild(fragment);
-
使用 CSS 替代 JS 动画:
.animate { transition: transform 0.3s; } .animate.active { transform: translateX(100px); }
-
-
虚拟列表:
function createVirtualList(container, items, itemHeight, visibleHeight) {
let startIndex = 0;
const visibleCount = Math.ceil(visibleHeight / itemHeight);
function render() {
container.innerHTML = '';
const endIndex = Math.min(startIndex + visibleCount, items.length);
for (let i = startIndex; i < endIndex; i++) {
const div = document.createElement('div');
div.style.height = `${itemHeight}px`;
div.textContent = items[i];
container.appendChild(div);
}
container.style.paddingTop = `${startIndex * itemHeight}px`;
}
container.addEventListener('scroll', () => {
startIndex = Math.floor(container.scrollTop / itemHeight);
render();
});
render();
}
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);
createVirtualList(document.getElementById('list'), items, 50, 500);
分析:
- 仅渲染可视区域,降低 DOM 节点数。
- 时间复杂度:O(k),k 为可视项数。
3. 网络优化
- HTTP/2:启用多路复用,减少连接开销。
- 缓存:
-
设置
Cache-Control
头:location /static/ { expires 1y; add_header Cache-Control "public"; }
-
使用 Service Worker 缓存:
self.addEventListener('install', event => { event.waitUntil( caches.open('v1').then(cache => { return cache.addAll([ '/index.html', '/styles.css', '/script.js' ]); }) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(response => { return response || fetch(event.request); }) ); });
-
4. JavaScript 性能优化
- 节流与防抖:
function throttle(fn, delay) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last >= delay) {
fn.apply(this, args);
last = now;
}
};
}
const scrollHandler = throttle(() => console.log('Scrolled'), 1000);
window.addEventListener('scroll', scrollHandler);
-
Tree Shaking:
-
使用 ES 模块,Webpack 自动移除未使用代码:
// utils.js export function used() { console.log('Used'); } export function unused() { console.log('Unused'); } // main.js import { used } from './utils.js'; used();
-
-
WebAssembly:
-
将性能敏感代码编译为 WASM:
// wasm_module.c int add(int a, int b) { return a + b; } // main.js WebAssembly.instantiateStreaming(fetch('module.wasm')) .then(({ instance }) => { console.log(instance.exports.add(2, 3)); // 5 });
-
性能优化面试题
面试题 1:重排优化
问题:以下代码有何性能问题?如何优化?
const container = document.getElementById('container');
for (let i = 0; i < 1000; i++) {
container.innerHTML += `<div>Item ${i}</div>`;
}
答案:
-
问题:每次
innerHTML +=
触发重排和重绘,性能差。 -
优化:
const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; fragment.appendChild(div); } container.appendChild(fragment);
面试题 2:懒加载实现
问题:实现图片懒加载。
答案:
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
}, { threshold: 0.1 });
images.forEach(img => observer.observe(img));
分析:
IntersectionObserver
检测图片是否进入视口。- 减少初始加载时间,提升性能。
面试题 3:Service Worker 缓存
问题:如何用 Service Worker 实现离线访问?
答案:
// sw.js
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js'
]);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
caches.open('v1').then(cache => {
cache.put(event.request, fetchResponse.clone());
});
return fetchResponse;
});
})
);
});
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
分析:
- 缓存静态资源,支持离线访问。
- 动态缓存新请求,提升后续加载速度。
企业级实践
微前端架构
使用 Module Federation 实现微前端:
// webpack.config.js (host)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
chartApp: 'chartApp@http://localhost:3001/remoteEntry.js'
}
})
]
};
// webpack.config.js (chartApp)
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'chartApp',
filename: 'remoteEntry.js',
exposes: {
'./Chart': './src/Chart.js'
}
})
]
};
// host.js
import('chartApp/Chart').then(Chart => {
Chart.render(document.getElementById('chart'));
});
分析:
- 微前端模块化开发,独立部署。
- 适合复杂前端应用,如数据可视化仪表盘。
CI/CD 集成
使用 GitHub Actions 自动化部署:
name: Deploy Frontend
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm install
- run: npm run build
- name: Deploy to S3
run: aws s3 sync ./dist s3://my-bucket
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
分析:
- 自动化构建、测试、部署。
- 使用 S3 托管静态前端。
Kubernetes 部署
部署前端服务:
kubectl create -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: frontend_app:latest
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
EOF
分析:
- 部署多个副本,确保高可用。
- Service 提供负载均衡。
深入跨域解决方案
跨域的进阶场景
CSRF 防御与跨域
跨域请求可能引发 跨站请求伪造(CSRF),攻击者利用用户已认证的 Cookie 发起恶意请求。CORS 配置需结合 CSRF 防御。
CSRF 防御措施:
- CSRF Token:
- 服务器生成唯一 Token,嵌入表单或请求头。
- 客户端发送请求时携带 Token,服务器验证。
实现(Express 后端):
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://frontend.example.com');
res.header('Access-Control-Allow-Headers', 'Content-Type, X-CSRF-Token');
next();
});
// 生成 CSRF Token
app.get('/api/csrf-token', (req, res) => {
const token = crypto.randomBytes(32).toString('hex');
req.session.csrfToken = token; // 假设使用 session
res.json({ csrfToken: token });
});
// 验证 CSRF Token
app.post('/api/data', (req, res) => {
const clientToken = req.headers['x-csrf-token'];
if (clientToken !== req.session.csrfToken) {
return res.status(403).send('Invalid CSRF Token');
}
res.json({ message: 'Data saved' });
});
app.listen(3000);
前端调用:
async function fetchData(data) {
const response = await fetch('/api/csrf-token');
const { csrfToken } = await response.json();
await fetch('https://api.example.com/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data),
credentials: 'include' // 携带 Cookie
});
}
逐步分析:
-
前端获取 CSRF Token,存储在请求头。
-
服务器验证
X-CSRF-Token
与 Session 中的 Token。 -
credentials: 'include'
确保 Cookie 跨域发送。 -
CORS 配置允许自定义头
X-CSRF-Token
。 -
SameSite Cookie:
-
设置 Cookie 的
SameSite
属性,限制跨站请求:res.cookie('session', 'value', { sameSite: 'Strict' });
-
Strict
:仅同站请求携带 Cookie。 -
Lax
:允许部分跨站 GET 请求。
-
CORS 复杂场景
带凭证的 CORS 请求:
-
当请求需要携带 Cookie(
credentials: 'include'
),Access-Control-Allow-Origin
不能为*
。 -
服务器需明确指定允许的源,并设置
Access-Control-Allow-Credentials
:res.header('Access-Control-Allow-Origin', 'https://frontend.example.com'); res.header('Access-Control-Allow-Credentials', 'true');
动态 CORS 配置:
-
根据请求的
Origin
动态设置允许的源:const allowedOrigins = ['https://frontend.example.com', 'https://dev.example.com']; app.use((req, res, next) => { const origin = req.headers.origin; if (allowedOrigins.includes(origin)) { res.header('Access-Control-Allow-Origin', origin); } next(); });
面试题 4:CORS 复杂请求
问题:以下请求失败,原因是什么?如何修复?
fetch('https://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
credentials: 'include',
body: JSON.stringify({ key: 'value' })
})
.catch(err => console.error(err));
答案:
- 原因:
Content-Type
拼写为Content-Type-
,导致预检失败。Authorization
是自定义头,需服务器允许。credentials: 'include'
要求Access-Control-Allow-Credentials
。
- 修复:
-
前端:
fetch('https://api.example.com/data', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }, credentials: 'include', body: JSON.stringify({ key: 'value' }) });
-
后端:
app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'https://frontend.example.com'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); res.header('Access-Control-Allow-Credentials', 'true'); next(); });
-
WebSocket 优化
WebSocket 的跨域通信可通过负载均衡和高可用性优化。
实现(带心跳检测的 WebSocket 服务器):
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
ws.isAlive = true;
ws.on('pong', () => ws.isAlive = true);
ws.on('message', message => {
ws.send(`Received: ${message}`);
});
});
// 心跳检测
setInterval(() => {
wss.clients.forEach(ws => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
前端:
const ws = new WebSocket('ws://api.example.com:8080');
ws.onopen = () => {
ws.send('Hello');
setInterval(() => ws.send('ping'), 25000); // 客户端心跳
};
ws.onmessage = event => console.log(event.data);
分析:
- 心跳检测防止连接中断,优化长连接稳定性。
- WebSocket 不受同源限制,适合实时应用(如聊天、数据监控)。
跨域面试题(进阶)
面试题 5:代理服务器的性能优化
问题:代理服务器如何优化跨域请求性能?
答案:
-
缓存响应:
location /api/ { proxy_pass https://api.example.com/; proxy_cache my_cache; proxy_cache_valid 200 300s; }
-
压缩数据:
gzip on; gzip_types application/json;
-
连接池:
upstream backend { server api.example.com; keepalive 32; }
分析:
- 缓存减少后端请求,压缩降低传输时间,连接池复用 TCP 连接。
面试题 6:PostMessage 安全
问题:以下 postMessage
代码有何安全问题?如何修复?
window.addEventListener('message', event => {
console.log(event.data);
});
答案:
-
问题:未验证
event.origin
,可能接收恶意消息。 -
修复:
window.addEventListener('message', event => { if (event.origin !== 'https://frontend.example.com') return; console.log(event.data); });
深入 Event Loop 与异步
Event Loop 的底层实现
Event Loop 的实现依赖 JavaScript 运行时(浏览器或 Node.js)和 V8 引擎。以下是核心组件:
- 调用栈:单线程,LIFO(后进先出)执行同步代码。
- 任务队列:
- 宏任务:
setTimeout
、setInterval
、I/O、UI 渲染。 - 微任务:
Promise.then
、MutationObserver
、queueMicrotask
。
- 宏任务:
- libuv(Node.js):处理异步 I/O 和事件循环。
浏览器 vs Node.js:
- 浏览器:
- 每个标签页一个 Event Loop。
- 渲染进程与 JS 引擎共享线程,UI 渲染是宏任务。
- Node.js:
- 单线程 Event Loop,libuv 管理多阶段队列(定时器、I/O、idle 等)。
- 无 UI 渲染,微任务优先级更高。
示例(Node.js Event Loop):
const fs = require('fs');
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
fs.readFile('data.txt', () => console.log('File read'));
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
输出:
Start
End
Promise
Timeout
File read
逐步分析:
- 同步代码:输出
Start
、End
。 - 微任务:
Promise
输出。 - 宏任务:
Timeout
(定时器阶段),File read
(I/O 阶段)。
异步优化
微任务优化
微任务可能导致调用栈阻塞,需谨慎管理。
问题代码:
Promise.resolve().then(() => {
while (true) {} // 阻塞
});
console.log('Blocked');
优化:
-
使用
queueMicrotask
控制微任务:queueMicrotask(() => console.log('Microtask'));
并发请求优化
使用 Promise.all
并行处理请求:
async function fetchMultiple() {
const urls = ['/api/data1', '/api/data2', '/api/data3'];
try {
const responses = await Promise.all(
urls.map(url => fetch(url).then(res => res.json()))
);
console.log(responses);
} catch (err) {
console.error(err);
}
}
分析:
Promise.all
并行发送请求,减少总时间。- 错误处理捕获任一请求失败。
异步迭代器
ES2018 引入异步迭代器,优化流式数据处理:
async function* fetchPages() {
let page = 1;
while (page <= 3) {
const response = await fetch(`/api/page/${page}`);
yield await response.json();
page++;
}
}
(async () => {
for await (const data of fetchPages()) {
console.log(data);
}
})();
分析:
- 异步生成器适合分页 API,逐页加载数据。
- 减少内存占用,提升性能。
异步面试题(进阶)
面试题 4:微任务嵌套
问题:以下代码输出什么?
console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
return Promise.resolve().then(() => console.log('Promise 2'));
})
.then(() => console.log('Promise 3'));
setTimeout(() => console.log('Timeout 2'), 0);
console.log('End');
输出:
Start
End
Promise 1
Promise 2
Promise 3
Timeout 1
Timeout 2
分析:
- 同步:
Start
,End
。 - 微任务:
Promise 1
->Promise 2
->Promise 3
。 - 宏任务:
Timeout 1
->Timeout 2
。
面试题 5:Node.js Event Loop
问题:Node.js 中以下代码输出顺序?
const fs = require('fs');
setImmediate(() => console.log('Immediate'));
fs.readFile('data.txt', () => console.log('File'));
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
输出:
Promise
Timeout
Immediate
File
分析:
- Node.js Event Loop 阶段:
- 微任务:
Promise
。 - 定时器:
Timeout
。 - Check 阶段:
Immediate
。 - I/O 阶段:
File
。
- 微任务:
面试题 6:Async/Await 错误处理
问题:优化以下错误处理:
async function fetchData() {
try {
const res = await fetch('/api/data');
return await res.json();
} catch (err) {
console.error(err);
}
}
优化:
async function fetchData() {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
return res.json();
} catch (err) {
console.error('Fetch failed:', err.message);
throw err; // 向上抛出,便于调用者处理
}
}
async function main() {
try {
const data = await fetchData();
console.log(data);
} catch (err) {
console.error('Main error:', err);
}
}
main();
分析:
- 检查
res.ok
确保 HTTP 状态正常。 - 抛出错误便于上层捕获。
前端性能优化(进阶)
WebGPU 优化
WebGPU 是下一代图形 API,适合高性能渲染。
示例(简单渲染):
async function render() {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.getElementById('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const shader = device.createShaderModule({
code: `
@vertex
fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
let pos = array<vec2<f32>, 3>(
vec2<f32>(0.0, 0.5),
vec2<f32>(-0.5, -0.5),
vec2<f32>(0.5, -0.5)
);
return vec4<f32>(pos[i], 0.0, 1.0);
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
`
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module: shader, entryPoint: 'vs_main' },
fragment: { module: shader, entryPoint: 'fs_main', targets: [{ format }] }
});
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
passEncoder.setPipeline(pipeline);
passEncoder.draw(3);
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
}
render();
分析:
- WebGPU 提供 GPU 加速,适合数据可视化、游戏。
- 性能优于 WebGL,接近原生。
Progressive Web Apps(PWA)
PWA 提升离线体验和性能。
实现:
// sw.js
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js'
]))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
caches.open('v1').then(cache => {
cache.put(event.request, fetchResponse.clone());
});
return fetchResponse;
});
})
);
});
// manifest.json
{
"name": "My PWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#007bff",
"icons": [
{
"src": "/icon.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
// index.html
<link rel="manifest" href="/manifest.json">
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
</script>
分析:
- Service Worker 缓存资源,支持离线。
- Manifest 提供原生应用体验。
虚拟 DOM 源码分析
React 的虚拟 DOM 优化渲染性能。
简化的虚拟 DOM 实现:
function createElement(type, props, ...children) {
return { type, props: { ...props, children } };
}
function render(vnode, container) {
if (typeof vnode === 'string') {
container.appendChild(document.createTextNode(vnode));
return;
}
const dom = document.createElement(vnode.type);
for (const [key, value] of Object.entries(vnode.props || {})) {
if (key === 'children') continue;
dom[key] = value;
}
(vnode.props.children || []).forEach(child => render(child, dom));
container.appendChild(dom);
}
const vdom = createElement('div', { id: 'app' },
createElement('h1', null, 'Hello'),
createElement('p', null, 'World')
);
render(vdom, document.body);
分析:
- 虚拟 DOM 构建内存树,Diff 算法比较变化。
- 批量更新 DOM,减少重排。
性能优化面试题(进阶)
面试题 4:React 性能优化
问题:如何优化 React 组件渲染?
答案:
-
使用
React.memo
:const MyComponent = React.memo(({ data }) => { return <div>{data}</div>; });
-
PureComponent:
class MyComponent extends React.PureComponent { render() { return <div>{this.props.data}</div>; } }
-
useMemo/useCallback:
function List({ items }) { const sortedItems = useMemo(() => items.sort(), [items]); const handleClick = useCallback(() => console.log('Clicked'), []); return <ul onClick={handleClick}>{sortedItems.map(item => <li>{item}</li>)}</ul>; }
分析:
React.memo
防止不必要的重新渲染。useMemo
缓存计算结果,useCallback
缓存函数引用。
面试题 5:Web Vitals
问题:如何优化 LCP(Largest Contentful Paint)?
答案:
-
服务器端渲染(SSR):
// Next.js export async function getServerSideProps() { const data = await fetchData(); return { props: { data } }; }
-
预加载关键资源:
<link rel="preload" href="/hero.jpg" as="image">
-
优化字体:
@font-face { font-family: 'Custom'; src: url('/font.woff2') format('woff2'); font-display: swap; }
分析:
- SSR 减少客户端渲染时间。
- 预加载和
font-display: swap
加速内容显示。
面试题 6:Tree Shaking
问题:以下代码为何未被 Tree Shaking 移除?如何优化?
// utils.js
export function used() { console.log('Used'); }
export function unused() { console.log('Unused'); }
// main.js
import * as utils from './utils.js';
utils.used();
答案:
-
原因:
import * as utils
导入整个模块,Webpack 无法确定unused
未使用。 -
优化:
import { used } from './utils.js'; used();
分析:
- 按需导入支持静态分析,移除未使用代码。
现代前端生态整合
React 与 TypeScript
使用 TypeScript 增强 React 组件类型安全:
import React, { FC } from 'react';
interface Props {
data: string[];
onClick: (item: string) => void;
}
const List: FC<Props> = ({ data, onClick }) => {
return (
<ul>
{data.map(item => (
<li key={item} onClick={() => onClick(item)}>{item}</li>
))}
</ul>
);
};
export default List;
分析:
- TypeScript 提供静态类型检查,减少运行时错误。
FC
定义函数组件类型。
Vue 3 与 Composition API
优化 Vue 组件性能:
import { defineComponent, ref, computed } from 'vue';
export default defineComponent({
setup() {
const items = ref(['Item 1', 'Item 2']);
const filteredItems = computed(() => items.value.filter(item => item.includes('1')));
const addItem = () => {
items.value.push(`Item ${items.value.length + 1}`);
};
return { items, filteredItems, addItem };
}
});
分析:
computed
缓存计算结果,优化渲染。ref
管理响应式状态。
GraphQL 集成
使用 Apollo Client 查询数据:
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache()
});
const QUERY = gql`
query GetData {
data {
id
name
}
}
`;
client.query({ query: QUERY })
.then(result => console.log(result.data))
.catch(err => console.error(err));
分析:
- GraphQL 按需查询字段,减少数据传输。
- Apollo Client 提供缓存和状态管理。
企业级实践(进阶)
微前端进阶
使用 Qiankun 实现微前端:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:3001',
container: '#reactContainer',
activeRule: '/react'
},
{
name: 'vueApp',
entry: '//localhost:3002',
container: '#vueContainer',
activeRule: '/vue'
}
]);
start();
分析:
- Qiankun 支持多框架微前端,动态加载子应用。
- 适合大型企业项目。
CI/CD 优化
使用 GitHub Actions 集成测试:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm install
- run: npm test
分析:
- 自动化测试确保代码质量。
- 集成 Jest、Cypress 等测试框架。
Kubernetes 进阶
自动扩缩容前端服务:
kubectl create -f - <<EOF
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: frontend-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: frontend
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
EOF
分析:
- 根据 CPU 使用率自动调整副本数。
- 提高高流量场景的稳定性。