Interview-06

128 阅读11分钟

1. React vs Vue 框架对比

问题: React 和 Vue 在设计思路上有什么本质的区别?

详细答案:

  • 数据绑定机制
    • Vue:采用双向数据绑定,通过 v-model 实现表单元素与数据的自动同步
    • React:单向数据流,数据通过 props 向下传递,事件通过回调向上传递
  • 模板语法
    • Vue:基于 HTML 的模板语法,学习成本低,支持指令(v-if、v-for)
    • React:JSX 语法,JavaScript 和 HTML 混合,更灵活但需要学习成本
  • 响应式系统
    • Vue 2:基于 Object.defineProperty,Vue 3:基于 Proxy
    • React:通过 setState 或 useState 手动触发更新
  • 组件通信
    • Vue:props/emit、provide/inject、Vuex
    • React:props/callback、Context API、Redux

2. React Key 的作用机制

问题: React 里的 key 有什么用?为什么不能用 index 作为 key?

详细答案:

  • Diff 算法中的作用
    • React 使用 key 来识别哪些列表项发生了变化
    • 在 reconciliation 过程中,React 会比较新旧虚拟 DOM 树
    • key 帮助 React 快速定位相同的元素,避免不必要的重新创建
  • index 作为 key 的问题
    // 错误示例
    {items.map((item, index) => (
      <Item key={index} data={item} />
    ))}
    
    // 当删除第一个元素时,所有元素的 index 都会改变
    // 导致 React 认为所有元素都发生了变化
    
  • 正确的 key 选择
    // 正确示例
    {items.map(item => (
      <Item key={item.id} data={item} />
    ))}
    
  • 性能影响:使用稳定的 key 可以避免不必要的 DOM 操作,提升渲染性能

3. React Hooks 实现原理

问题: React Hooks 和 Class 组件相比,解决了什么问题?它的实现原理是什么?

详细答案:

  • 解决的核心问题
    • 逻辑复用困难:HOC 和 render props 会导致组件嵌套地狱
    • 复杂组件难以理解:生命周期方法中混合了不相关的逻辑
    • this 指向问题:需要手动绑定 this
  • 实现原理
 // 用于记录当前 hook 的索引(类似 React 中的 hook 调用顺序)
let hookIndex = 0;

// 用来存储所有 hook 的值(每次渲染后保持状态)
let hooks = [];

/**
 * 自定义 useState 实现
 * @param {*} initialValue - 初始状态值
 * @returns [state, setState] - 返回当前状态值和更新状态的函数
 */
function useState(initialValue) {
  // 保存当前这个 useState 调用的索引
  const currentIndex = hookIndex;

  // 如果这个索引位置还没有值,就使用初始值;否则使用已有的值(保持状态)
  hooks[currentIndex] = hooks[currentIndex] || initialValue;

  // 定义更新状态的函数
  const setState = (newValue) => {
    // 更新这个索引位置的状态值
    hooks[currentIndex] = newValue;

    // 触发重新渲染(模拟 React 的更新机制)
    render();
  };

  // 使用完一个 hook 后索引递增,给下一个 hook 使用
  hookIndex++;

  // 返回当前状态值和更新函数
  return [hooks[currentIndex], setState];
}

  • Fiber 架构中的 Hooks
    • 每个 Fiber 节点都有一个 memoizedState 属性
    • Hooks 以链表形式存储在 memoizedState 中
    • 通过调用顺序来维护 Hook 的对应关系

4. Event Loop 详细机制

问题: 浏览器的 Event Loop 机制,宏任务和微任务的执行顺序是怎样的?

详细答案:

  • 执行栈和任务队列
    console.log('1'); // 同步任务
    
    setTimeout(() => {
      console.log('2'); // 宏任务
    }, 0);
    
    Promise.resolve().then(() => {
      console.log('3'); // 微任务
    });
    
    console.log('4'); // 同步任务
    // 输出顺序:1, 4, 3, 2
    
  • 详细执行流程
    1. 执行同步代码,遇到异步任务放入对应队列
    2. 同步代码执行完毕,检查微任务队列
    3. 执行所有微任务,直到微任务队列为空
    4. 执行一个宏任务
    5. 重复步骤 2-4
  • 微任务类型
    • Promise.then/catch/finally
    • queueMicrotask
    • MutationObserver
    • process.nextTick (Node.js)
  • 宏任务类型
    • setTimeout/setInterval
    • I/O 操作
    • UI 渲染
    • MessageChannel

5. CSS Position 定位详解

问题: CSS 中,position 有哪些值?它们分别是根据什么定位的?

详细答案:

  • static(默认值)
    • 元素按照正常文档流排列
    • top、right、bottom、left 属性无效
  • relative(相对定位)
    • 相对于元素在正常文档流中的位置进行偏移
    • 不脱离文档流,原来的空间仍然保留
    .relative {
      position: relative;
      top: 10px; /* 向下偏移 10px */
      left: 20px; /* 向右偏移 20px */
    }
    
  • absolute(绝对定位)
    • 相对于最近的非 static 定位祖先元素
    • 脱离文档流,不占据原来的空间
    • 如果没有定位祖先,则相对于初始包含块(通常是 viewport)
  • fixed(固定定位)
    • 相对于视口(viewport)定位
    • 脱离文档流,滚动时位置不变
  • sticky(粘性定位)
    • 结合了 relative 和 fixed 的特性
    • 在滚动到指定位置前表现为 relative
    • 滚动到指定位置后表现为 fixed

6. Webpack vs Vite 深度对比

问题: Webpack 和 Vite 有什么区别?为什么 Vite 的开发环境启动速度会快很多?

详细答案:

  • 构建原理差异
    • Webpack:基于打包(Bundle-based)
      • 开发时需要打包整个应用
      • 使用各种 loader 转换文件
      • 生成一个或多个 bundle 文件
    • Vite:基于 ESM(ES Module)
      • 开发时利用浏览器原生 ESM 支持
      • 按需编译,只处理当前页面需要的模块
      • 生产环境使用 Rollup 打包
  • 开发服务器启动速度
    // Webpack 启动流程
    1. 分析依赖图
    2. 打包所有模块
    3. 启动开发服务器
    
    // Vite 启动流程
    1. 启动开发服务器(几乎瞬间)
    2. 按需编译请求的模块
    
  • 热更新机制
    • Webpack HMR:需要重新打包相关模块
    • Vite HMR:直接替换单个模块,速度更快
  • 生产环境构建
    • Webpack:使用自身的打包能力
    • Vite:使用 Rollup,生成更小的 bundle

7. Vue 3 Composition API 原理

问题: Vue 3 与 Vue 2 相比有哪些重要的改进?

详细答案:

  • 响应式系统重写
    // Vue 2 - Object.defineProperty
    Object.defineProperty(obj, 'key', {
      get() { /* 依赖收集 */ },
      set(newVal) { /* 触发更新 */ }
    });
    
    // Vue 3 - Proxy
    new Proxy(target, {
      get(target, key) { /* 依赖收集 */ },
      set(target, key, value) { /* 触发更新 */ }
    });
    
  • Composition API 优势
    // 逻辑复用更简单
    function useCounter() {
      const count = ref(0);
      const increment = () => count.value++;
      return { count, increment };
    }
    
    // 在组件中使用
    export default {
      setup() {
        const { count, increment } = useCounter();
        return { count, increment };
      }
    };
    
  • 性能优化
    • Tree-shaking:未使用的 API 不会被打包
    • 编译时优化:静态提升、内联组件 props
    • Proxy 响应式:支持数组索引和对象属性的动态添加
  • TypeScript 支持
    • 更好的类型推导
    • 内置 TypeScript 支持

8. JavaScript 深拷贝实现

问题: 手写一个深拷贝函数。

详细答案:

function deepClone(obj, map = new WeakMap()) {
  // 处理基本类型
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理日期对象
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }
  
  // 处理正则对象
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }
  
  // 处理函数
  if (typeof obj === 'function') {
    return obj; // 函数通常不需要深拷贝
  }
  
  // 处理循环引用
  if (map.has(obj)) {
    return map.get(obj);
  }
  
  // 处理数组和对象
  const cloned = Array.isArray(obj) ? [] : {};
  map.set(obj, cloned);
  
  // 处理 Symbol 属性
  const symbolKeys = Object.getOwnPropertySymbols(obj);
  symbolKeys.forEach(key => {
    cloned[key] = deepClone(obj[key], map);
  });
  
  // 处理普通属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloned[key] = deepClone(obj[key], map);
    }
  }
  
  return cloned;
}

// 测试用例
const original = {
  name: 'test',
  date: new Date(),
  regex: /test/g,
  nested: { value: 42 },
  arr: [1, 2, { inner: 'value' }]
};

// 创建循环引用
original.self = original;

const cloned = deepClone(original);
console.log(cloned !== original); // true
console.log(cloned.self === cloned); // true

9. let、const、var 详细区别

问题: let、const 和 var 的区别是什么?什么是暂时性死区?

详细答案:

  • 作用域差异
    // var - 函数作用域
    function example1() {
      if (true) {
        var x = 1;
      }
      console.log(x); // 1,可以访问
    }
    
    // let/const - 块级作用域
    function example2() {
      if (true) {
        let y = 1;
        const z = 2;
      }
      console.log(y); // ReferenceError
      console.log(z); // ReferenceError
    }
    
  • 变量提升机制
    // var 提升并初始化为 undefined
    console.log(a); // undefined
    var a = 1;
    
    // let/const 提升但不初始化(暂时性死区)
    console.log(b); // ReferenceError
    let b = 2;
    
    console.log(c); // ReferenceError
    const c = 3;
    
  • 暂时性死区(TDZ)
    function example() {
      // TDZ 开始
      console.log(x); // ReferenceError
      
      let x = 1; // TDZ 结束
      console.log(x); // 1
    }
    
  • 重复声明
    // var 允许重复声明
    var name = 'first';
    var name = 'second'; // 正常
    
    // let/const 不允许重复声明
    let age = 18;
    let age = 20; // SyntaxError
    

10. CSS 盒模型详解

问题: 盒模型的理解,标准盒模型和 IE 盒模型的区别是什么?

详细答案:

  • 标准盒模型(W3C)
    .standard-box {
      width: 200px;        /* 内容区宽度 */
      padding: 20px;       /* 内边距 */
      border: 5px solid;   /* 边框 */
      margin: 10px;        /* 外边距 */
    }
    /* 实际占用宽度 = 200 + 20*2 + 5*2 + 10*2 = 270px */
    /* 元素总宽度 = 200 + 20*2 + 5*2 = 250px */
    
  • IE 盒模型(怪异模式)
    .ie-box {
      box-sizing: border-box;
      width: 200px;        /* 包含 content + padding + border */
      padding: 20px;
      border: 5px solid;
      margin: 10px;
    }
    /* 内容区宽度 = 200 - 20*2 - 5*2 = 150px */
    /* 实际占用宽度 = 200 + 10*2 = 220px */
    
  • box-sizing 属性
    /* 标准盒模型 */
    .content-box {
      box-sizing: content-box; /* 默认值 */
    }
    
    /* IE 盒模型 */
    .border-box {
      box-sizing: border-box;
    }
    
  • 实际应用
    /* 常用的全局设置 */
    *, *::before, *::after {
      box-sizing: border-box;
    }
    

11. HTTP 状态码详解

问题: HTTP 常见的状态码有哪些?301 和 302 有什么区别?

详细答案:

  • 1xx 信息响应
    • 100 Continue:客户端应继续请求
    • 101 Switching Protocols:协议切换
  • 2xx 成功响应
    • 200 OK:请求成功
    • 201 Created:资源已创建
    • 204 No Content:无内容返回
    • 206 Partial Content:部分内容(断点续传)
  • 3xx 重定向
    // 301 永久重定向
    // 搜索引擎会更新索引,浏览器会缓存重定向
    app.get('/old-page', (req, res) => {
      res.redirect(301, '/new-page');
    });
    
    // 302 临时重定向
    // 搜索引擎不会更新索引,浏览器不会缓存
    app.get('/temp-redirect', (req, res) => {
      res.redirect(302, '/temporary-page');
    });
    
    • 304 Not Modified:资源未修改,使用缓存
  • 4xx 客户端错误
    • 400 Bad Request:请求语法错误
    • 401 Unauthorized:未授权
    • 403 Forbidden:禁止访问
    • 404 Not Found:资源不存在
    • 405 Method Not Allowed:方法不允许
  • 5xx 服务器错误
    • 500 Internal Server Error:服务器内部错误
    • 502 Bad Gateway:网关错误
    • 503 Service Unavailable:服务不可用
    • 504 Gateway Timeout:网关超时

12. React Fiber 架构原理

问题: React Fiber 架构,它解决了什么问题?相比之前的 Stack Reconciler,主要有哪些变化?

详细答案:

  • Stack Reconciler 的问题
    // 旧的递归调和过程
    function reconcileChildren(element) {
      // 递归处理子元素,无法中断
      element.children.forEach(child => {
        reconcileChildren(child); // 深度优先,一直递归到底
      });
    }
    
  • Fiber 架构的核心概念
    // Fiber 节点结构
    const fiber = {
      type: 'div',           // 组件类型
      props: {},             // 属性
      child: null,           // 第一个子节点
      sibling: null,         // 兄弟节点
      return: null,          // 父节点
      alternate: null,       // 对应的 Fiber 节点(双缓存)
      effectTag: null,       // 副作用标记
      expirationTime: 0,     // 过期时间
    };
    
  • 时间切片(Time Slicing)
    function workLoop(deadline) {
      while (nextUnitOfWork && deadline.timeRemaining() > 1) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
      
      if (nextUnitOfWork) {
        // 还有工作未完成,继续调度
        requestIdleCallback(workLoop);
      }
    }
    
    requestIdleCallback(workLoop);
    
  • 双缓存机制
    // current 树:当前显示的 UI
    // workInProgress 树:正在构建的新树
    
    function commitRoot() {
      // 交换 current 和 workInProgress
      root.current = root.finishedWork;
      root.finishedWork = null;
    }
    
  • 优先级调度
    const priorities = {
      Immediate: 1,        // 立即执行
      UserBlocking: 2,     // 用户交互
      Normal: 3,           // 正常优先级
      Low: 4,              // 低优先级
      Idle: 5              // 空闲时执行
    };
    

13. JavaScript 原型链详解

问题: 原型和原型链,proto 和 prototype 分别是什么?

详细答案:

  • prototype 属性
    // 函数的 prototype 属性指向原型对象
    function Person(name) {
      this.name = name;
    }
    
    Person.prototype.sayHello = function() {
      console.log(`Hello, I'm ${this.name}`);
    };
    
    console.log(Person.prototype); // { sayHello: function, constructor: Person }
    
  • proto 属性
    // 对象的 __proto__ 属性指向构造函数的 prototype
    const person = new Person('Alice');
    
    console.log(person.__proto__ === Person.prototype); // true
    console.log(person.__proto__.constructor === Person); // true
    
  • 原型链查找机制
    function Animal(name) {
      this.name = name;
    }
    Animal.prototype.eat = function() {
      console.log(`${this.name} is eating`);
    };
    
    function Dog(name, breed) {
      Animal.call(this, name);
      this.breed = breed;
    }
    
    // 设置原型链
    Dog.prototype = Object.create(Animal.prototype);
    Dog.prototype.constructor = Dog;
    
    Dog.prototype.bark = function() {
      console.log(`${this.name} is barking`);
    };
    
    const dog = new Dog('Buddy', 'Golden Retriever');
    
    // 原型链查找过程:
    // dog.bark() -> dog.__proto__.bark (Dog.prototype)
    // dog.eat()  -> dog.__proto__.__proto__.eat (Animal.prototype)
    
  • 完整的原型链
    console.log(dog.__proto__ === Dog.prototype);                    // true
    console.log(dog.__proto__.__proto__ === Animal.prototype);       // true
    console.log(dog.__proto__.__proto__.__proto__ === Object.prototype); // true
    console.log(dog.__proto__.__proto__.__proto__.__proto__ === null);    // true
    

14. 浏览器渲染原理

问题: 描述一下浏览器渲染页面的完整过程?

详细答案:

  • 渲染流程详解
    1. 解析 HTML → DOM 树
    2. 解析 CSS → CSSOM 树
    3. DOM + CSSOM → 渲染树(Render Tree)
    4. 布局(Layout/Reflow)→ 计算元素位置和大小
    5. 绘制(Paint)→ 填充像素
    6. 合成(Composite)→ 图层合并
    
  • 关键渲染路径优化
    <!-- 优化 CSS 加载 -->
    <link rel="preload" href="critical.css" as="style">
    <link rel="stylesheet" href="critical.css">
    
    <!-- 非关键 CSS 异步加载 -->
    <link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    
    <!-- 优化 JavaScript 加载 -->
    <script src="app.js" defer></script>
    <script src="analytics.js" async></script>
    
  • 重排(Reflow)和重绘(Repaint)
    // 触发重排的操作
    element.style.width = '200px';     // 改变尺寸
    element.style.display = 'none';    // 改变显示状态
    element.offsetWidth;               // 读取布局属性
    
    // 只触发重绘的操作
    element.style.color = 'red';       // 改变颜色
    element.style.backgroundColor = 'blue'; // 改变背景色
    
    // 优化:批量修改样式
    element.style.cssText = 'width: 200px; height: 100px; color: red;';
    
    // 或使用 CSS 类
    element.className = 'optimized-style';
    
  • 图层合成优化
    /* 创建新的合成层 */
    .optimized {
      transform: translateZ(0); /* 或 will-change: transform */
      /* GPU 加速,避免重排重绘 */
    }
    
    /* 动画优化 */
    @keyframes slide {
      from { transform: translateX(0); }
      to { transform: translateX(100px); }
    }
    

15. XSS 和 CSRF 防护详解

问题: 如何防止 XSS 和 CSRF 攻击?

详细答案:

  • XSS(跨站脚本攻击)防护

    // 1. 输入验证和过滤
    function sanitizeInput(input) {
      return input
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#x27;')
        .replace(/\//g, '&#x2F;');
    }
    
    // 2. 使用安全的 DOM 操作
    // 危险:innerHTML
    element.innerHTML = userInput; // 可能执行脚本
    
    // 安全:textContent
    element.textContent = userInput; // 只设置文本内容
    
    // 3. CSP(内容安全策略)
    // HTTP 头或 meta 标签
    
    <meta http-equiv="Content-Security-Policy" 
          content="default-src 'self'; script-src 'self' 'unsafe-inline';">
    
    // 4. HttpOnly Cookie
    res.cookie('sessionId', sessionId, {
      httpOnly: true,    // 防止 JavaScript 访问
      secure: true,      // 只在 HTTPS 下传输
      sameSite: 'strict' // 防止 CSRF
    });
    
  • CSRF(跨站请求伪造)防护

    // 1. CSRF Token
    // 服务端生成 token
    const csrfToken = crypto.randomBytes(32).toString('hex');
    req.session.csrfToken = csrfToken;
    
    // 前端发送请求时携带 token
    fetch('/api/transfer', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      body: JSON.stringify({ amount: 1000 })
    });
    
    // 服务端验证 token
    if (req.headers['x-csrf-token'] !== req.session.csrfToken) {
      return res.status(403).json({ error: 'Invalid CSRF token' });
    }
    
    // 2. SameSite Cookie
    res.cookie('sessionId', sessionId, {
      sameSite: 'strict' // 严格模式,跨站请求不发送 cookie
    });
    
    // 3. 验证 Referer
    const referer = req.headers.referer;
    if (!referer || !referer.startsWith('https://trusted-domain.com')) {
      return res.status(403).json({ error: 'Invalid referer' });
    }
    
    // 4. 双重 Cookie 验证
    const csrfCookie = req.cookies.csrfToken;
    const csrfHeader = req.headers['x-csrf-token'];
    if (csrfCookie !== csrfHeader) {
      return res.status(403).json({ error: 'CSRF token mismatch' });
    }
    

16. WebSocket 实时通信实现

问题: 如何使用 WebSockets 来实现实时数据通信?

详细答案:

  • 客户端实现

    class WebSocketClient {
      constructor(url) {
        this.url = url;
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
        this.reconnectInterval = 1000;
        this.heartbeatInterval = 30000;
        this.heartbeatTimer = null;
      }
      
      connect() {
        try {
          this.ws = new WebSocket(this.url);
          this.setupEventListeners();
        } catch (error) {
          console.error('WebSocket connection failed:', error);
          this.handleReconnect();
        }
      }
      
      setupEventListeners() {
        this.ws.onopen = (event) => {
          console.log('WebSocket connected');
          this.reconnectAttempts = 0;
          this.startHeartbeat();
          this.onOpen?.(event);
        };
        
        this.ws.onmessage = (event) => {
          try {
            const data = JSON.parse(event.data);
            if (data.type === 'pong') {
              // 心跳响应
              return;
            }
            this.onMessage?.(data);
          } catch (error) {
            console.error('Failed to parse message:', error);
          }
        };
        
        this.ws.onclose = (event) => {
          console.log('WebSocket disconnected:', event.code, event.reason);
          this.stopHeartbeat();
          this.onClose?.(event);
          
          if (!event.wasClean) {
            this.handleReconnect();
          }
        };
        
        this.ws.onerror = (error) => {
          console.error('WebSocket error:', error);
          this.onError?.(error);
        };
      }
      
      send(data) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify(data));
        } else {
          console.warn('WebSocket is not connected');
        }
      }
      
      startHeartbeat() {
        this.heartbeatTimer = setInterval(() => {
          this.send({ type: 'ping', timestamp: Date.now() });
        }, this.heartbeatInterval);
      }
      
      stopHeartbeat() {
        if (this.heartbeatTimer) {
          clearInterval(this.heartbeatTimer);
          this.heartbeatTimer = null;
        }
      }
      
      handleReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
          this.reconnectAttempts++;
          console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
          
          setTimeout(() => {
            this.connect();
          }, this.reconnectInterval * this.reconnectAttempts);
        } else {
          console.error('Max reconnection attempts reached');
        }
      }
      
      close() {
        this.stopHeartbeat();
        if (this.ws) {
          this.ws.close(1000, 'Client closing connection');
        }
      }
    }
    
    // 使用示例
    const client = new WebSocketClient('ws://localhost:8080');
    
    client.onOpen = () => {
      console.log('Connected to server');
    };
    
    client.onMessage = (data) => {
      console.log('Received:', data);
      // 处理接收到的数据
    };
    
    client.connect();
    
  • 服务端实现(Node.js)

    const WebSocket = require('ws');
    const http = require('http');
    
    class WebSocketServer {
      constructor(port) {
        this.port = port;
        this.clients = new Map();
        this.server = http.createServer();
        this.wss = new WebSocket.Server({ server: this.server });
        this.setupServer();
      }
      
      setupServer() {
        this.wss.on('connection', (ws, req) => {
          const clientId = this.generateClientId();
          const clientInfo = {
            id: clientId,
            ws: ws,
            ip: req.socket.remoteAddress,
            userAgent: req.headers['user-agent'],
            connectedAt: new Date()
          };
          
          this.clients.set(clientId, clientInfo);
          console.log(`Client ${clientId} connected`);
          
          ws.on('message', (message) => {
            try {
              const data = JSON.parse(message);
              this.handleMessage(clientId, data);
            } catch (error) {
              console.error('Failed to parse message:', error);
            }
          });
          
          ws.on('close', (code, reason) => {
            console.log(`Client ${clientId} disconnected:`, code, reason);
            this.clients.delete(clientId);
          });
          
          ws.on('error', (error) => {
            console.error(`Client ${clientId} error:`, error);
          });
          
          // 发送欢迎消息
          this.sendToClient(clientId, {
            type: 'welcome',
            clientId: clientId,
            message: 'Connected to server'
          });
        });
      }
      
      handleMessage(clientId, data) {
        switch (data.type) {
          case 'ping':
            this.sendToClient(clientId, { type: 'pong', timestamp: Date.now() });
            break;
          case 'broadcast':
            this.broadcast(data.message, clientId);
            break;
          case 'private':
            this.sendToClient(data.targetId, {
              type: 'private',
              from: clientId,
              message: data.message
            });
            break;
          default:
            console.log(`Unknown message type: ${data.type}`);
        }
      }
      
      sendToClient(clientId, data) {
        const client = this.clients.get(clientId);
        if (client && client.ws.readyState === WebSocket.OPEN) {
          client.ws.send(JSON.stringify(data));
        }
      }
      
      broadcast(message, excludeClientId = null) {
        this.clients.forEach((client, clientId) => {
          if (clientId !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
            client.ws.send(JSON.stringify({
              type: 'broadcast',
              message: message,
              timestamp: Date.now()
            }));
          }
        });
      }
      
      generateClientId() {
        return Math.random().toString(36).substr(2, 9);
      }
      
      start() {
        this.server.listen(this.port, () => {
          console.log(`WebSocket server listening on port ${this.port}`);
        });
      }
    }
    
    // 启动服务器
    const server = new WebSocketServer(8080);
    server.start();
    

17. TypeScript 高级特性

问题: TypeScript 的优势及其在项目中的应用。

详细答案:

  • 类型系统优势

    // 1. 接口定义
    interface User {
      id: number;
      name: string;
      email: string;
      roles: Role[];
    }
    
    interface Role {
      id: number;
      name: string;
      permissions: Permission[];
    }
    
    // 2. 泛型约束
    function updateEntity<T extends { id: number }>(entity: T, updates: Partial<T>): T {
      return { ...entity, ...updates };
    }
    
    // 3. 条件类型
    type ApiResponse<T> = T extends string 
      ? { message: T } 
      : { data: T };
    
    // 4. 映射类型
    type ReadonlyUser = Readonly<User>;
    type PartialUser = Partial<User>;
    type UserKeys = keyof User; // 'id' | 'name' | 'email' | 'roles'
    
    // 5. 工具类型
    type CreateUserRequest = Omit<User, 'id'>;
    type UpdateUserRequest = Pick<User, 'id'> & Partial<Omit<User, 'id'>>;
    
  • 高级类型应用

    // 1. 函数重载
    function processData(data: string): string;
    function processData(data: number): number;
    function processData(data: boolean): boolean;
    function processData(data: string | number | boolean) {
      if (typeof data === 'string') {
        return data.toUpperCase();
      } else if (typeof data === 'number') {
        return data * 2;
      } else {
        return !data;
      }
    }
    
    // 2. 装饰器
    function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      const originalMethod = descriptor.value;
      descriptor.value = function(...args: any[]) {
        console.log(`Calling ${propertyKey} with args:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`${propertyKey} returned:`, result);
        return result;
      };
    }
    
    class UserService {
      @Log
      createUser(userData: CreateUserRequest): Promise<User> {
        // 实现创建用户逻辑
        return Promise.resolve({} as User);
      }
    }
    
    // 3. 模块声明
    declare module '*.vue' {
      import { DefineComponent } from 'vue';
      const component: DefineComponent<{}, {}, any>;
      export default component;
    }
    
    // 4. 命名空间
    namespace API {
      export interface Request {
        url: string;
        method: 'GET' | 'POST' | 'PUT' | 'DELETE';
        headers?: Record<string, string>;
        body?: any;
      }
      
      export interface Response<T = any> {
        status: number;
        data: T;
        message?: string;
      }
    }
    
  • 项目配置最佳实践

    // tsconfig.json
    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "lib": ["ES2020", "DOM", "DOM.Iterable"],
        "moduleResolution": "node",
        "strict": true,
        "noImplicitAny": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "exactOptionalPropertyTypes": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "baseUrl": ".",
        "paths": {
          "@/*": ["src/*"],
          "@/components/*": ["src/components/*"],
          "@/utils/*": ["src/utils/*"]
        }
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist"]
    }