在本文中,我們將探討十個前端開發人員經常遇到的手寫代碼問題。這些問題通常出現在面試或自我評估過程中,旨在測試開發者對基礎概念的理解以及編寫高效且可維護的JavaScript代碼的能力。以下就是這十道題目及其詳細的解決方案:
1. 深拷貝與淺拷貝的區別:
- 淺拷貝只會複製對象中的第一層屬性值,如果子對象的引用發生變化,則兩個對象上的該子對象將指向同一個內存地址。
- 深拷貝則會遞歸地複製整個對象結構,確保每個子對象都有其獨立的副本,即使它們的引用發生了變化也不會影響彼此的數據獨立性。
2. 實現一個Promise封裝函數,使得異步操作更加簡潔易讀:
function MyPromise(executor) {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
executor(resolve, reject); // 在構造函數內部調用 executor 函數
return promise;
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
if (this instanceof MyPromise === false) return this; // 判斷是否爲new關鍵字創建實例
return new MyPromise((res, rej) => {
onFulfilled && res(onFulfilled); // 如果存在fulfilled回調,執行它並將結果傳遞給新的resolve
onRejected && rej(onRejected); // 如果存在rejected回調,執行它並將結果傳遞給新的reject
}).catch((error) => error); // 將任何錯誤都視爲最終狀態
};
3. JavaScript的事件循環機制:
- 事件循環分爲多個階段,包括“消息隊列”、“任務隊列”和“宏任務”、“微任務”等不同類型的任務。
- “宏任務”(如setTimeout、setInterval、script標籤內的所有代碼)會按照先進先出的順序依次執行。
- “微任務”(如Promise的then/catch方法)會在當前正在執行的宏任務的尾部添加到微任務隊列中,並在下一個事件循環週期開始時立即處理。
- 每次事件循環的主要步驟如下:
1. 處理所有掛起的宏任務。
2. 清空當前的微任務隊列,即執行所有的微任務。
3. 檢查是否有更多的宏任務需要處理。如果沒有,渲染瀏覽器界面。如果有,進入下一次事件循環。
4. 使用原生JavaScript實現一個簡單的圖片懶加載效果:
// 爲img元素添加數據屬性data-src作爲真正的圖片URL
document.querySelectorAll('img[data-src]').forEach(img => {
img.addEventListener('load', () => img.removeAttribute('data-src')); // 移除data-src屬性
img.addEventListener('error', e => img.style.display = 'none'); // 如果加載失敗,隱藏圖像
img.src = '/path/to/placeholder.jpg'; // 設置佔位符圖片的地址
});
5. 如何檢測某個字符串是否包含大寫字母:
function containsUpperCaseLetters(str) {
return str.match(/[A-Z]/g) !== null;
}
6. 用JavaScript模擬繼承的方式:
- 在ES6之前,我們可以通過原型鏈繼承或者組合模式來實現類之間的繼承關係。
- 從ES6開始,我們有了class語法和extends關鍵字來更直觀地表達類的繼承。
- 在沒有class的情況下,我們可以使用以下方式來模擬繼承:
var Parent = function() {}
Parent.prototype.someMethod = function() { /* 父類的方法 */ }
var Child = function() {}
Child.prototype = Object.create(Parent.prototype); // 創建一個新的對象,它的__proto__指向Parent.prototype
Child.prototype.constructor = Child; // 修復Child構造函數的原型鏈指向
7. 實現一個debounce函數,用來限制一段時間內只能觸發一次函數調用:
function debounce(func, wait) {
let timeoutId;
return function() {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, arguments), wait);
};
}
8. 實現一個throttle函數,用來保證在一個時間窗口內只執行一次函數調用:
function throttle(fn, delay) {
let lastTime = Date.now();
return function() {
const now = Date.now();
if (now - lastTime > delay) {
lastTime = now;
fn.apply(this, arguments);
}
};
}
9. 如何獲取頁面滾動條的位置:
- `window.pageYOffset`或`document.body.scrollTop`可以獲得垂直方向上頁面的滾動距離。
- `window.pageXOffset`或`document.body.scrollLeft`可以獲得水平方向上頁面的滾動距離。
- `document.documentElement.clientHeight`可以獲取可視區域的高度。
- `Math.max(document.body.scrollHeight, document.documentElement.offsetHeight)`可以獲取內容區總高度。
10. 如何在數組中去重:
- 對於無序數組,可以使用排序後相鄰元素對比的方式進行去重。
- 對於有鍵的對象集合,可以直接遍歷並檢查是否存在相同的key。
// 使用Set來簡化數組去重的過程
Array.from(new Set(array))
以上這些問題是前端工程師經常會遇到的挑戰,它們不僅考驗了程序員的編碼能力,還要求他們對JavaScript的基礎知識有着深入的瞭解。通過解決這些問題,程序員可以更好地理解語言特性、設計模式和性能優化技巧,從而提升自己的專業素養和技術實力。