以下是騰訊及騰訊音樂娛樂(TME)前端崗位高頻手撕題目詳解,結合真題及考察要點整理,覆蓋面試核心考點:
?? 一、核心手撕題(騰訊/TME 必考)
1. Promise 并發控制(90%場次出現)
- 題目:手寫
Promise.all
(需處理錯誤短路、位置保持) - 核心思路:
- 校驗輸入為數組,空數組直接
resolve([])
。 - 遍歷 Promise 數組,用
Promise.resolve
包裝非 Promise 值。 - 計數完成量,全部成功時返回結果數組;任一失敗立即
reject
。
- 校驗輸入為數組,空數組直接
- 邊界處理:
Promise.myAll = (promises) => {if (!Array.isArray(promises)) return Promise.reject(new TypeError('Argument must be an array'));let count = 0, results = [];return new Promise((resolve, reject) => {promises.forEach((p, i) => {Promise.resolve(p).then(res => {results[i] = res; // 保持結果位置if (++count === promises.length) resolve(results);}).catch(reject); // 短路邏輯});if (promises.length === 0) resolve(results);}); };
2. 數組扁平化(80%場次出現)
- 題目:實現多層嵌套數組降維(如
[1, [2, [3]]] → [1, 2, 3]
) - 方案對比:
- 遞歸法:深度優先遍歷,遇到數組則遞歸展開。
flat
API:直接調用arr.flat(Infinity)
(需注意瀏覽器兼容性)。- Reduce 遞歸:
const flatten = (arr) => arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? flatten(cur) : cur), []);
3. 深拷貝(70%場次出現)
- 考點:處理循環引用、特殊對象(Date/RegExp)
- 代碼關鍵點:
- 使用
WeakMap
緩存已拷貝對象,避免循環引用導致的棧溢出。 - 特殊對象單獨處理(如
new Date(obj)
)。
function deepClone(obj, map = new WeakMap()) {if (obj === null || typeof obj !== 'object') return obj;if (map.has(obj)) return map.get(obj);const clone = obj instanceof Date ? new Date(obj) : obj instanceof RegExp ? new RegExp(obj): Array.isArray(obj) ? [] : {};map.set(obj, clone);Reflect.ownKeys(obj).forEach(key => {clone[key] = deepClone(obj[key], map);});return clone; }
- 使用
?? 二、特色場景題(騰訊音樂TME高頻)
1. 頁面通信與崩潰監控
- 題目:從頁面A打開頁面B,B關閉(含崩潰)時通知A
- 解決方案:
- 正常關閉:在B的
window.onbeforeunload
中通過localStorage
或postMessage
傳參,A監聽storage
或message
事件。 - 崩潰監控:
- B頁面定時(5s)向Service Worker發送"心跳"。
- Service Worker檢測超時(15s無心跳)判定崩潰,通知A頁面。
// B頁面心跳發送 setInterval(() => navigator.serviceWorker.controller.postMessage({ type: 'heartbeat' }), 5000); // Service Worker檢測邏輯 if (Date.now() - lastHeartbeat > 15000) reportCrash();
- 正常關閉:在B的
2. 大數相加(校招重點)
- 題目:實現超過JS精度限制的數字加法(如
"9999999999999999" + "1"
) - 思路:
- 字符串反轉,按位相加并處理進位。
- 注意高位補位(如最終進位不為0)。
function addBigNumbers(a, b) {const arr1 = a.split('').reverse(), arr2 = b.split('').reverse();let result = [], carry = 0;for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {const sum = (parseInt(arr1[i] || 0) + parseInt(arr2[i] || 0) + carry);result.push(sum % 10);carry = Math.floor(sum / 10);}if (carry) result.push(carry);return result.reverse().join(''); }
3. 二叉樹遍歷(基礎算法)
- 題目:實現二叉樹前序/中序/后序遍歷(遞歸與非遞歸)
- 遞歸示例:
const preorder = (root, res = []) => {if (!root) return res;res.push(root.val); // 前序:根左右preorder(root.left, res);preorder(root.right, res);return res; };
💡 三、答題技巧與避坑點
- 原理深挖:
- 騰訊必問實現邏輯(如
Promise.all
的并發控制、深拷貝的循環引用處理)。 - 避免只答API用法(如被追問“
flat
的內部實現”)。
- 騰訊必問實現邏輯(如
- 邊界處理:
- 空輸入、極端用例(如大數相加的進位溢出)需顯式處理。
- 工程化思維:
- 結合業務場景(如頁面崩潰監控需說明Service Worker的獨立線程特性)。
騰訊系面試注重原理實現深度與場景落地能力,建議優先掌握以上高頻題,并擴展練習虛擬DOM Diff、響應式原理(Proxy/defineProperty)等進階題。
JavaScript 算法詳解
1. 最大公共前綴
/*** 查找字符串數組中的最長公共前綴* @param {string[]} strs 字符串數組* @return {string} 最長公共前綴*/
function longestCommonPrefix(strs) {let res='';if(strs.length===0)return res;const val=strs[0];for(let i=0;i<val.length;i++){let curChar=val[i];for(let j=0;j<strs.length;j++){if(strs[j][i]!==curChar)return res;if(j===strs.length-1) res+=curChar;}}return res;
}// 示例
console.log(longestCommonPrefix(["flower","flow","flight"])); // "fl"
console.log(longestCommonPrefix(["dog","racecar","car"])); // ""
算法思路:
- 如果數組為空,直接返回空字符串
- 以第一個字符串作為初始公共前綴
- 遍歷數組中的每個字符串,與當前公共前綴進行比較
- 如果不匹配,則縮短公共前綴,直到匹配或變為空字符串
- 返回最終的公共前綴
2. 最大子序列和
/*** 查找數組中連續子序列的最大和(Kadane算法)* @param {number[]} nums 數字數組* @return {number} 最大子序列和*/
function maxSubArray(nums) {let max=Math.max(...arr);let curSum=0;for(let i=0;i<arr.length;i++){curSum+=arr[i];max=Math.max(curSum,max);if(curSum<0)curSum=0;}return max;
}// 示例
console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4])); // 6 (對應子序列 [4,-1,2,1])
console.log(maxSubArray([-1,-2,-3])); // -1
算法思路(Kadane算法):
- 初始化當前最大值和全局最大值為第一個元素
- 遍歷數組:
- 對于每個元素,決定是將其加入當前子序列還是開始新的子序列
- 更新全局最大值
- 返回全局最大值
3. 重復子字符串
/*** 判斷字符串是否可以由它的一個子串重復多次構成* @param {string} s 輸入字符串* @return {boolean} 是否可以由子串重復構成*/
function repeatedSubstringPattern(s) {// 將字符串與自身拼接,然后去掉首尾字符const doubled = s + s;const sliced = doubled.slice(1, -1);// 如果原字符串出現在拼接后的字符串中,則可以由子串重復構成return sliced.includes(s);
}// 示例
console.log(repeatedSubstringPattern("abab")); // true (可由 "ab" 重復構成)
console.log(repeatedSubstringPattern("aba")); // false
console.log(repeatedSubstringPattern("abcabcabc")); // true (可由 "abc" 重復構成)
算法思路:
- 將原字符串與自身拼接
- 去掉拼接后字符串的首尾字符
- 如果原字符串出現在處理后的字符串中,則說明可以由子串重復構成
- 這種方法利用了字符串旋轉和模式匹配的原理
數學解釋:
- 如果一個字符串S可以由子串重復構成,那么S = n*sub
- 將S+S = 2n*sub
- 去掉首尾字符后,中間至少包含一個完整的S = n*sub
- 因此S會出現在處理后的字符串中
這三個算法分別展示了字符串處理和動態規劃的經典問題解決方案。