常見的前端面試手寫考題,deepClone、debounce、throttle
> 大陸面試題常考的八股文題目
相關函式的詳細解釋這邊就不做特別說明,這篇只專注在 code 的部分
#deepClone
請實作一個簡易版的 deepClone
(2023/5/22 更新) Web API 有一個叫做structuredClone的 API,已經可以實現 deepClone 的功能,但是各家的瀏覽器都是近幾個版本才支援的,所以如果有舊版本瀏覽器的話,要做好兼容性。
Copy!// 最簡易,但要考慮會有什麼限制? 為什麼這樣可以? const cloned = JSON.parse(JSON.stringify(objectToClone))
Copy!/** * 簡易版 deepClone * 有許多 Edge cases 沒有考慮到, * 如果是產品的見還是用 loadsh的 _.cloneDeep 比較保險 * * 用遞歸的方式判斷假如是object就不斷在進到下一層, * 若不是object的話就賦值給 target * @param {Object} source * @returns {Object} */ export function deepClone(source) { if (!source && typeof source !== 'object') { throw new Error('Error arguments', 'deepClone') } // 可以用 source.constructor 或是 Array.isArray(source) 來判斷是 Array 還是 Object const target = source.constructor === Array ? [] : {} Object.keys(source).forEach((keys) => { if (source[keys] && typeof source[keys] === 'object') { target[keys] = deepClone(source[keys]) } else { target[keys] = source[keys] } }) return target }
延伸問題:
- 有哪些 function(filter、map、slice、splice)實作出來是 deep copy ? 、哪些是 shallow copy ?
#debounce
請實作一個 debounce 的 function,以及請說明 debounce 的應用場合
Copy!/** * 簡易版 * @param {Function} func func The function to debounce. * @param {number} wait * @return {Function} Returns the new debounced function. */ function debounce(fun, delay) { return function (args) { let that = this let _args = args // 每次執行的時候重置setTimeout clearTimeout(fun.time) fun.time = setTimeout(function () { // 執行傳入的fun1(透過call方法傳遞參數_args) fun.call(that, _args) }, delay) } }
Copy!/** * 簡易版 + 立即執行 * @param {Function} func func The function to debounce. * @param {number} wait * @param {boolean} immediate is immediate execute * @return {Function} Returns the new debounced function. */ function debounce(func, wait, immediate) { let timeout, args, context, timestamp, result const later = function () { // 據上一次觸發時間間隔 const last = +new Date() - timestamp // 上次被包裝函數被調用時間間隔 last 小於設定時間間隔 wait if (last < wait && last > 0) { timeout = setTimeout(later, wait - last) } else { timeout = null // 如果設定為 immediate === true,因為開始邊界已經調用過了此處無需調用 if (!immediate) { result = func.apply(context, args) if (!timeout) context = args = null } } } return function (...args) { context = this timestamp = +new Date() const callNow = immediate && !timeout // 如果延時不存在,重新設定延時 if (!timeout) timeout = setTimeout(later, wait) if (callNow) { result = func.apply(context, args) context = args = null } return result } }
#使用情境
-
監聽 resize 事件時,不用每次變動就立即執行 function,只要最後停止時在做計算即可
-
以 Search bar 的功能來說,不用使用者每輸入一個字都去 call API,只要最後停下來後再去要資料就好了
#Throttle
Copy!function throttle(func, timeout = 250) { let last let timer return function () { const context = this const args = arguments const now = +new Date() if (last && now < last + timeout) { clearTimeout(timer) timer = setTimeout(function () { last = now func.apply(context, args) }, timeout) } else { last = now func.apply(context, args) } } }
#使用情境
- 有個 button 有綁定 click 事件,若沒有 throttle 在不斷點擊按鈕,會按幾次就觸發幾次,用 throttle 可以有效限制在固定時間內只會觸發一次而已