JavaScript 跨域、事件循环、性能优化面试题解析教程

25 阅读13分钟

你有没有在面试时被问到这样的问题:“浏览器中为什么会出现跨域?如何解决?”、“事件循环到底是怎么执行的?”、“你怎么优化一个页面的加载速度?”这些看似基础的问题,其实背后藏着大量的底层原理。如果你只是会“套答案”,那 HR 和技术面试官可不买账。

我们将通过一个个典型的 JavaScript 面试题,深入讲解跨域的原理与各种解决方式(CORS、JSONP、代理等)、事件循环的执行过程(宏任务、微任务、任务队列)以及常见的前端性能优化策略。从原理讲起,到实际案例分析,帮你建立真正扎实的 JS 基础,轻松应对中高级前端面试。就算不面试,这些知识对实际开发提升也非常有用,准备好了吗?我们正式开始。

跨域解决方案

跨域的定义与原理

跨域(Cross-Origin Resource Sharing,CORS)是指浏览器基于同源策略(Same-Origin Policy)限制不同源的资源访问。同源要求协议、域名、端口号三者相同,例如:

  • https://example.com:443https://api.example.com:443 不同源(域名不同)。
  • http://example.com:80https://example.com:443 不同源(协议不同)。

同源策略的限制

  • 禁止通过 fetchXMLHttpRequest 访问不同源的 API。
  • 限制不同源 iframe 的 DOM 操作。
  • 限制 Cookie、LocalStorage 的跨域访问。

跨域的场景

  • 前端调用后端 API(如 api.example.com)。
  • 加载第三方资源(如 CDN 的脚本、字体、图片)。
  • 微前端架构中子模块通信。

跨域原理

  • 浏览器在发送跨域请求时,添加 Origin 请求头。
  • 服务器响应 Access-Control-Allow-Origin 头,控制允许的源。
  • 复杂请求(如 PUTDELETE)触发预检请求(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));

逐步分析

  1. 服务器设置 Access-Control-Allow-Origin,允许 https://frontend.example.com 访问。
  2. Access-Control-Allow-Methods 指定允许的 HTTP 方法。
  3. 复杂请求触发 OPTIONS 预检,服务器响应允许的头和方法。
  4. 前端通过 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);

逐步分析

  1. 前端动态创建 <script>,请求带 callback 参数。
  2. 服务器返回 handleData({...}),浏览器执行回调。
  3. 数据通过回调函数传递到前端。

优缺点

  • 优点:简单,兼容老浏览器。
  • 缺点:仅支持 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));

逐步分析

  1. 前端请求同源代理(如 /api/data)。
  2. Nginx 将请求转发到目标服务器。
  3. 服务器响应数据,代理返回给前端。

优缺点

  • 优点:灵活,支持所有请求类型。
  • 缺点:需额外配置代理服务器。

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):存储宏任务(如 setTimeoutsetInterval)。
  • 微任务队列(Microtask Queue):存储微任务(如 PromiseMutationObserver)。
  • Event Loop:循环检查队列,将任务推入调用栈。

执行流程

  1. 执行同步代码,清空调用栈。
  2. 检查微任务队列,执行所有微任务。
  3. 检查宏任务队列,执行一个宏任务。
  4. 重复步骤 2-3。

示例

console.log('Start');

setTimeout(() => console.log('Timeout'), 0);

Promise.resolve().then(() => console.log('Promise'));

console.log('End');

输出

Start
End
Promise
Timeout

逐步分析

  1. console.log('Start') 入栈,执行,输出。
  2. setTimeout 注册宏任务,推入任务队列。
  3. Promise.then 注册微任务,推入微任务队列。
  4. console.log('End') 入栈,执行,输出。
  5. 调用栈清空,执行微任务 Promise,输出。
  6. 执行宏任务 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 提供链式调用,解决回调嵌套。
  • 状态:pendingfulfilledrejected

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

逐步分析

  1. 同步代码:
    • 输出 script start
    • 调用 async1,输出 async1 start
    • 调用 async2,输出 async2
    • await async2() 注册微任务(async1 end)。
    • 输出 promise1then 注册微任务(promise2)。
    • 输出 script end
  2. 微任务队列:promise2, async1 end
    • 输出 promise2
    • 输出 async1 end
  3. 宏任务队列: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);
}

异步的应用场景

  1. 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;
    }
}
  1. 动画与定时任务
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)。
    • 减少服务器延迟。

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 防御措施

  1. 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
    });
}

逐步分析

  1. 前端获取 CSRF Token,存储在请求头。

  2. 服务器验证 X-CSRF-Token 与 Session 中的 Token。

  3. credentials: 'include' 确保 Cookie 跨域发送。

  4. CORS 配置允许自定义头 X-CSRF-Token

  5. 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));

答案

  • 原因
    1. Content-Type 拼写为 Content-Type-,导致预检失败。
    2. Authorization 是自定义头,需服务器允许。
    3. 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(后进先出)执行同步代码。
  • 任务队列
    • 宏任务setTimeoutsetInterval、I/O、UI 渲染。
    • 微任务Promise.thenMutationObserverqueueMicrotask
  • 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

逐步分析

  1. 同步代码:输出 StartEnd
  2. 微任务:Promise 输出。
  3. 宏任务: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

分析

  1. 同步:Start, End
  2. 微任务:Promise 1 -> Promise 2 -> Promise 3
  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 阶段:
    1. 微任务:Promise
    2. 定时器:Timeout
    3. Check 阶段:Immediate
    4. 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 使用率自动调整副本数。
  • 提高高流量场景的稳定性。