腾讯音乐秋招二面
自我介绍。
离职情况、现况简单了解。
介绍 Zustand、Redux、Dva 和各自的应用场景。
编辑历史的前进和后退有没有解决方案?存历史版本的想法和 Diff 存储策略?
BFF 怎么做容灾?接口 QPS 峰值是多少?
输出是什么?
jsfunction 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);输出是什么?
jsvar 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判断给定的矩形能覆盖几行?
🔍 覆盖一行的示例
ABC🔍 展开代码
jsconst 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);实现 Omit。
tstype 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]; };flex-basis/flex-shrink/flex-grow 是做什么的?下面的渲染效果是怎么样的?如果想让宽度是预设值怎么修改?
jsx<div style={{ width: "200px", display: "flex" }}> <div style={{ width: "150px" }}></div> <div style={{ width: "150px" }}></div> </div>;手写一个 select 的 DOM 结构?如果菜单外容器有
overflow: hidden怎么优化 select 能保证正常渲染?下拉框可能是支持搜索的,但你的搜索不一定能够返回最新的数据,你如何处理?
🔍 展开代码
jsximport 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格式化时间,提供“刷新”按钮一行代码求数组平均值。
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;- 「反问」部门氛围、业务趋势、入职挑战。
