Skip to content

腾讯音乐秋招二面

  1. 自我介绍。

  2. 离职情况、现况简单了解。

  3. 介绍 Zustand、Redux、Dva 和各自的应用场景。

  4. 编辑历史的前进和后退有没有解决方案?存历史版本的想法和 Diff 存储策略?

  5. BFF 怎么做容灾?接口 QPS 峰值是多少?

  6. 输出是什么?

    1.js
    js
    function Person(name, sex) {
      this.name = name;
      this.sex = sex;
      var evil = "我是私有属性";
      var pickNose = function () {
        console.log("我是私有方法");
      };
      this.drawing = function () {
        console.log("我是公有方法");
      };
    }
    Person.fight = function () {
      console.log("我是静态方法");
    };
    Person.prototype.protoMethod = function () {
      console.log("构造函数的原型对象方法");
    };
    var p1 = new Person("xiaomao", "cat");
    
    // 面试回答
    console.log(p1.name); // xiaomao
    console.log(p1.evil); // 我是私有属性
    p1.drawing(); // 我是公有方法
    p1.pickNose(); // 我是私有方法
    p1.fight(); // 静态方法
    p1.protoMethod(); // 原型
    Person.fight(); // 静态方法
    Person.protoMethod(); // 原型
    console.log(Person.sex); // undefined
    
    /* ------------------------------ DIVIDER ----------------------------- */
    
    // 正确理解
    console.log(p1.name); // ✅ "xiaomao"
    console.log(p1.evil); // ❌ undefined (evil 是私有变量)
    p1.drawing(); // ✅ "我是公有方法"
    
    // ❌ TypeError: p1.pickNose is not a function
    p1.pickNose();
    
    // ❌ TypeError: p1.fight is not a function (这是静态方法, 不能实例调)
    p1.fight();
    p1.protoMethod(); // ✅ "构造函数的原型对象方法"
    Person.fight(); // ✅ "我是静态方法"
    
    // ❌ TypeError: Person.protoMethod is not a function 在原型链不在本身
    Person.protoMethod();
    
    // ❌ undefined (sex 是实例属性, 不是构造函数属性)
    console.log(Person.sex);
  7. 输出是什么?

    2.js
    js
    var name = "global";
    var obj = {
      name: "local",
      foo: function () {
        this.name = "foo";
        console.log(this.name); //foo
      }.bind(window),
    };
    var bar = new obj.foo();
    setTimeout(function () {
      console.log(window.name); //global
    }, 0);
    console.log(bar.name); // foo
    
    var bar3 = (bar2 = bar);
    bar2.name = "foo2";
    console.log(bar3.name); // foo2
  8. 判断给定的矩形能覆盖几行?

    🔍 覆盖一行的示例
    A
    B
    C
    🔍 展开代码
    3.js
    js
    const data = [
      { id: 0, x: 0, y: 0, height: 40, width: 100 },
      { id: 1, x: 120, y: 0, height: 40, width: 100 },
      { id: 2, x: 0, y: 60, height: 40, width: 220 },
      { id: 3, x: 0, y: 120, height: 40, width: 60 },
      { id: 4, x: 80, y: 120, height: 40, width: 60 },
      { id: 5, x: 160, y: 120, height: 40, width: 60 },
      { id: 6, x: 0, y: 180, height: 40, width: 220 },
      { id: 7, x: 240, y: 0, height: 100, width: 80 },
      { id: 8, x: 240, y: 120, height: 70, width: 80 },
      { id: 9, x: 240, y: 200, height: 20, width: 80 },
    ];
    
    const set = new Set();
    const zones = [];
    data.forEach((rec) => {
      const start = rec.y;
      const end = rec.y + rec.height;
      const feature = `${start}-${end}`;
      if (!set.has(feature)) {
        set.add(feature);
        zones.push([start, end]);
      }
    });
    
    zones.sort((a, b) => a[0] - b[0]);
    
    const results = [];
    for (const z of zones) {
      if (!results.length) results.push(z);
      else {
        const [last_s, last_e] = results[results.length - 1];
        const [cur_s, cur_e] = z;
        if (cur_s <= last_e) {
          const final = [Math.min(cur_s, last_s), Math.max(cur_e, last_e)];
          results.pop();
          results.push(final);
        } else {
          results.push(z);
        }
      }
    }
    
    for (const r of results) {
      console.log(r);
    }
    
    console.log(results.length);
  9. 实现 Omit。

    4.ts
    ts
    type Person = {
      name: string;
      age: number;
      job: string;
    };
    
    type Child = Omit<Person, "job">;
    
    // ❌ 面试写错了
    // type MyOmit<T,key extends keyof T> = {
    //     key in keyof T ? Exclusive<T,Partial<T,key>> : never;
    // }
    // type Exclusive<T,U> = {
    //     [K in keyof T]: k in keyof U ? never: T[k];
    // }
    
    type MyOmit<T, key extends keyof T> = Pick<T, Exclude<keyof T, key>>;
    
    type _MyOmit<T, key extends keyof T> = {
      [K in keyof T as K extends key ? never : K]: T[K];
    };
  10. flex-basis/flex-shrink/flex-grow 是做什么的?下面的渲染效果是怎么样的?如果想让宽度是预设值怎么修改?

    5.jsx
    jsx
    <div style={{ width: "200px", display: "flex" }}>
      <div style={{ width: "150px" }}></div>
      <div style={{ width: "150px" }}></div>
    </div>;
  11. 手写一个 select 的 DOM 结构?如果菜单外容器有 overflow: hidden 怎么优化 select 能保证正常渲染?

  12. 下拉框可能是支持搜索的,但你的搜索不一定能够返回最新的数据,你如何处理?

    🔍 展开代码
    6.jsx
    jsx
    import React, { useState, useRef, useEffect, useCallback } from "react";
    
    const SearchSelect = () => {
      // 状态管理
      const [inputValue, setInputValue] = useState("");
      const [options, setOptions] = useState([]);
      const [status, setStatus] = useState("idle"); // idle, loading, error, empty
      const [lastUpdated, setLastUpdated] = useState(null);
      const [showDropdown, setShowDropdown] = useState(false);
    
      // 缓存和请求管理
      const dataCache = useRef(new Map()); // {keyword: {data, timestamp}}
      const abortControllerRef = useRef(null);
      const lastRequestTime = useRef(0);
      const cacheExpireTime = 30000; // 30秒缓存过期
    
      // 防抖处理
      const debouncedSearch = useCallback(
        debounce((keyword) => {
          searchData(keyword);
        }, 300),
        [],
      );
    
      // 输入变化处理
      const handleInputChange = (e) => {
        const value = e.target.value.trim();
        setInputValue(value);
        setShowDropdown(true);
    
        if (value) {
          debouncedSearch(value);
        } else {
          setOptions([]);
          setStatus("idle");
        }
      };
    
      // 搜索数据
      const searchData = async (keyword, forceRefresh = false) => {
        // 取消上一次请求
        if (abortControllerRef.current) {
          abortControllerRef.current.abort();
        }
    
        // 检查缓存
        const cachedData = dataCache.current.get(keyword);
        const now = Date.now();
    
        // 有有效缓存且不强制刷新时直接使用
        if (
          cachedData &&
          !forceRefresh &&
          now - cachedData.timestamp < cacheExpireTime
        ) {
          setOptions(cachedData.data);
          setStatus(cachedData.data.length ? "success" : "empty");
          setLastUpdated(cachedData.timestamp);
          return;
        }
    
        // 显示加载状态
        setStatus("loading");
    
        // 创建新的请求控制器
        const abortController = new AbortController();
        abortControllerRef.current = abortController;
        const requestTime = Date.now();
        lastRequestTime.current = requestTime;
    
        try {
          // 实际项目中替换为真实API请求
          const data = await fetchData(keyword, {
            signal: abortController.signal,
          });
    
          // 只处理最新的请求响应
          if (requestTime === lastRequestTime.current) {
            // 更新缓存
            dataCache.current.set(keyword, {
              data,
              timestamp: Date.now(),
            });
    
            setOptions(data);
            setStatus(data.length ? "success" : "empty");
            setLastUpdated(Date.now());
          }
        } catch (error) {
          if (error.name !== "AbortError") {
            setStatus("error");
          }
        }
      };
    
      // 模拟API请求
      const fetchData = (keyword, options) => {
        return new Promise((resolve) => {
          setTimeout(() => {
            // 模拟一些数据
            const mockData = [
              `选项 ${keyword} 1`,
              `选项 ${keyword} 2`,
              `选项 ${keyword} 3`,
            ].filter((item) => item.includes(keyword));
    
            resolve(mockData);
          }, 800);
        });
      };
    
      // 选择选项
      const handleSelectOption = (option) => {
        setInputValue(option);
        setShowDropdown(false);
      };
    
      // 手动刷新
      const handleRefresh = () => {
        if (inputValue.trim()) {
          searchData(inputValue.trim(), true);
        }
      };
    
      // 点击外部关闭下拉框
      useEffect(() => {
        const handleClickOutside = (event) => {
          const dropdown = document.querySelector(".options-container");
          const input = document.querySelector(".search-input");
    
          if (
            dropdown &&
            input &&
            !dropdown.contains(event.target) &&
            !input.contains(event.target)
          ) {
            setShowDropdown(false);
          }
        };
    
        document.addEventListener("mousedown", handleClickOutside);
        return () => {
          document.removeEventListener("mousedown", handleClickOutside);
        };
      }, []);
    
      // 防抖函数
      function debounce(func, delay) {
        let timer;
        return function (...args) {
          clearTimeout(timer);
          timer = setTimeout(() => func.apply(this, args), delay);
        };
      }
    
      // 格式化时间显示
      const formatTime = (timestamp) => {
        if (!timestamp) return "";
        return new Date(timestamp).toLocaleTimeString();
      };
    
      return (
        <div className="search-select" style={styles.container}>
          <input
            type="text"
            className="search-input"
            placeholder="搜索..."
            value={inputValue}
            onChange={handleInputChange}
            onFocus={() => inputValue && setShowDropdown(true)}
            style={styles.input}
          />
    
          {showDropdown && (
            <div className="options-container" style={styles.dropdown}>
              {status === "loading" && (
                <div style={styles.statusText}>加载中...</div>
              )}
    
              {status === "error" && (
                <div style={styles.statusText}>
                  加载失败
                  <span style={styles.refreshBtn} onClick={handleRefresh}>
                    重试
                  </span>
                </div>
              )}
    
              {status === "empty" && (
                <div style={styles.statusText}>无匹配结果</div>
              )}
    
              {status === "success" && options.length > 0 && (
                <>
                  {options.map((option, index) => (
                    <div
                      key={index}
                      style={styles.optionItem}
                      onClick={() => handleSelectOption(option)}
                    >
                      {option}
                    </div>
                  ))}
                  <div style={styles.statusText}>
                    数据更新于 {formatTime(lastUpdated)}
                    <span style={styles.refreshBtn} onClick={handleRefresh}>
                      刷新
                    </span>
                  </div>
                </>
              )}
            </div>
          )}
        </div>
      );
    };
    
    // 样式定义
    const styles = {
      container: {
        position: "relative",
        width: "300px",
        margin: "20px",
      },
      input: {
        width: "100%",
        padding: "8px 12px",
        border: "1px solid #ddd",
        borderRadius: "4px",
        fontSize: "14px",
        boxSizing: "border-box",
      },
      dropdown: {
        position: "absolute",
        top: "100%",
        left: 0,
        width: "100%",
        maxHeight: "200px",
        overflowY: "auto",
        border: "1px solid #ddd",
        borderTop: "none",
        borderRadius: "0 0 4px 4px",
        background: "#fff",
        zIndex: 1000,
        boxSizing: "border-box",
      },
      optionItem: {
        padding: "8px 12px",
        cursor: "pointer",
      },
      statusText: {
        padding: "8px 12px",
        color: "#666",
        fontSize: "13px",
      },
      refreshBtn: {
        marginLeft: "8px",
        color: "#007bff",
        cursor: "pointer",
        fontSize: "12px",
      },
    };
    
    export default SearchSelect;

    📌 面试官问有没有更简单的

    除了防抖、参考帧流概念的加时间戳、请求取消、缓存策略,可以对上一次请求的标志做记录,下次请求时先判断 keyword 或者 timeStamp 是否一致,lastRequestTime.current = requestTime 才发送请求。

    策略分类具体实现方式核心目标关键代码/逻辑参考
    输入请求优化防抖(Debounce)减少高频输入触发的无效请求,降低接口压力useCallback 包裹防抖函数,延迟 300ms 执行搜索,避免输入过程中频繁发请求
    旧请求拦截记录上一次请求标志(Keyword/TimeStamp),新请求前先校验避免相同关键词重复请求、或旧请求覆盖新请求1. 新请求前检查当前 inputValue(Keyword)是否与缓存关键词一致;
    2. 仅当关键词变化/强制刷新时,才发起新请求
    响应顺序控制时间戳标记(参考帧流思路)+ lastRequestTime 一致性判断确保只有“最后一次请求”的响应能更新状态,防止旧响应覆盖新数据1. 发送请求时记录 requestTime = Date.now()
    2. 响应返回时校验 requestTime === lastRequestTime.current,一致才更新状态
    无效请求终止AbortController 主动取消上一次未完成请求从源头终止过时请求,减少无效响应产生,避免网络资源浪费1. 新请求前调用 abortControllerRef.current?.abort()
    2. 请求携带 signal 参数,支持中断
    数据新鲜度保障带有效期的缓存策略(Cache + 过期时间)+ 手动刷新触发平衡性能(复用缓存)与数据新鲜度(过期失效),赋予用户主动更新控制权1. dataCache 存储关键词对应的 {data, timestamp},缓存有效期 30s;
    2. 缓存未过期直接复用,过期/强制刷新时重新请求
    状态可视化反馈明确区分请求状态(loading/error/empty/success)+ 显示数据更新时间让用户感知数据时效性,减少“数据是否最新”的困惑,提升体验1. status 状态控制 UI 显示(加载中/失败重试/无结果);
    2. 成功后显示 lastUpdated 格式化时间,提供“刷新”按钮
  13. 一行代码求数组平均值。

JavaScript
const arr = [1, 2, 3, 4, 5];
// 面试官觉得这并不是一行 只是写在一行格式化后的 reduce 参数
const avg = arr.reduce((a, b) => a + b, 0) / arr.length;
const avg2 = eval(arr.join("+")) / arr.length;
  1. 「反问」部门氛围、业务趋势、入职挑战。