《前后端面試題
》專欄集合了前后端各個知識模塊的面試題,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目錄
- 一、本文面試題目錄
- 16. Symbol 有什么作用?
- 17. 什么是內存泄漏?
- 18. 什么是垃圾回收機制?
- 19. 事件循環(event loop)是什么?
- 20. 什么是微任務和宏任務?
- 21. 解釋節流函數 throttle 的實現原理
- 22. 如何實現函數防抖(debounce)?
- 23. 什么是淺拷貝和深拷貝?
- 24. 什么是立即執行函數(IIFE)?有什么作用?
- 25. 如何將字符串轉換為數字?
- 26. 如何判斷一個值是否為 NaN?
- 27. 數組扁平化的方法有哪些?
- 28. 什么是函數柯里化(Currying)?舉例說明
- 29. 什么是擴展運算符(...)?有哪些用途?
- 30. 如何交換兩個變量的值(不使用臨時變量)?
- 二、150道面試題目錄列表
一、本文面試題目錄
16. Symbol 有什么作用?
Symbol
用于創建獨一無二的值,常用于對象屬性名,可避免屬性名沖突。例如:
const key1 = Symbol('key1');
const key2 = Symbol('key1');
let obj = {};
obj[key1] = 'value1';
obj[key2] = 'value2';
console.log(obj[key1]); // value1
console.log(obj[key2]); // value2
console.log(key1 === key2); // false
17. 什么是內存泄漏?
內存泄漏是指程序中不再使用的對象仍然被引用,導致內存無法被垃圾回收機制釋放,從而造成內存浪費。常見場景有全局變量未及時清理、閉包使用不當、定時器未清除、DOM引用未釋放等。
18. 什么是垃圾回收機制?
瀏覽器通常采用標記清除算法或引用計數算法來實現垃圾回收。標記清除算法會先標記所有活動對象(被引用的對象),然后清除未被標記的對象(不再被引用的對象)占用的內存。引用計數算法是通過記錄對象的引用次數,當引用次數為0時,就回收該對象的內存,但這種算法存在循環引用的問題。
19. 事件循環(event loop)是什么?
事件循環是JavaScript實現非阻塞編程的機制。主線程先執行同步任務,當同步任務執行完后,會將異步任務產生的回調放入任務隊列中。事件循環會不斷檢查任務隊列,將符合條件的任務(宏任務或微任務)放入主線程執行。微任務會在當前同步任務執行完后立即執行,宏任務則是在微任務隊列清空后執行。例如:
console.log('start');
setTimeout(() => {console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {console.log('promise then');
});
console.log('end');
// 輸出:start -> end -> promise then -> setTimeout
20. 什么是微任務和宏任務?
- 微任務:通常在當前同步任務執行完后立即執行,常見的微任務有
Promise.then
、MutationObserver
等。 - 宏任務:會在微任務隊列清空后執行,常見的宏任務有
setTimeout
、setInterval
、requestAnimationFrame
、DOM渲染
等。
21. 解釋節流函數 throttle 的實現原理
節流函數用于限制函數在一定時間內只能執行一次,常用于處理頻繁觸發的事件,如滾動事件、 resize事件等。實現原理是通過記錄上一次執行的時間,當本次觸發時間與上一次時間間隔大于設定的延遲時間時,才執行函數。示例代碼如下:
function throttle(fn, delay) {let last = 0;return function() {const now = Date.now();if (now - last >= delay) {fn.apply(this, arguments);last = now;}};
}
function handleScroll() {console.log('scrolling');
}
window.addEventListener('scroll', throttle(handleScroll, 500));
22. 如何實現函數防抖(debounce)?
防抖函數是指在事件觸發后,延遲一段時間再執行函數,如果在這段時間內事件又被觸發,則重新計時,直到最后一次觸發后的延遲時間結束才執行函數。常用于搜索框輸入、按鈕多次點擊等場景。示例代碼如下:
function debounce(fn, delay) {let timer;return function() {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, arguments);}, delay);};
}
function handleInput() {console.log('input event handled');
}
let input = document.getElementById('input');
input.addEventListener('input', debounce(handleInput, 300));
23. 什么是淺拷貝和深拷貝?
-
淺拷貝:只復制對象的第一層屬性,如果屬性值是引用類型(如對象、數組),拷貝的是引用地址,原對象和拷貝對象會共享該引用類型的值,修改其中一個會影響另一個。
常見的淺拷貝方法有Object.assign()
、擴展運算符...
、數組的slice()
和concat()
等。// 淺拷貝示例 const obj1 = { a: 1, b: { c: 2 } }; const obj2 = Object.assign({}, obj1); // 淺拷貝 obj2.b.c = 3; console.log(obj1.b.c); // 輸出3,因為obj1和obj2共享b對象的引用
-
深拷貝:完全復制對象的所有層級屬性,包括嵌套的引用類型,原對象和拷貝對象互不影響。
實現深拷貝的方法有遞歸遍歷拷貝、JSON.parse(JSON.stringify())
(有局限性)等。// 深拷貝示例(遞歸實現) function deepClone(obj) {if (obj === null || typeof obj!== 'object') return obj;let clone = Array.isArray(obj)? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key]); // 遞歸拷貝嵌套屬性}}return clone; } const obj3 = { a: 1, b: { c: 2 } }; const obj4 = deepClone(obj3); obj4.b.c = 3; console.log(obj3.b.c); // 輸出2,原對象不受影響
24. 什么是立即執行函數(IIFE)?有什么作用?
立即執行函數(Immediately Invoked Function Expression,IIFE)是定義后立即執行的函數,語法上通過將函數包裹在括號中,再添加執行括號實現。
作用:
- 創建獨立的作用域,避免變量污染全局作用域。
- 可以封裝私有變量和邏輯,只暴露需要的接口。
示例代碼:
// 基本語法
(function() {const msg = "IIFE執行了";console.log(msg); // 輸出"IIFE執行了"
})();// 帶參數的IIFE
(function(a, b) {console.log(a + b); // 輸出3
})(1, 2);// 避免全局污染示例
const globalVar = "全局變量";
(function() {const globalVar = "局部變量"; // 不影響外部全局變量console.log(globalVar); // 輸出"局部變量"
})();
console.log(globalVar); // 輸出"全局變量"
25. 如何將字符串轉換為數字?
JavaScript 中常見的字符串轉數字方法有:
Number()
:通用轉換函數,可轉換整數、小數、空字符串(轉為0)等。parseInt()
:解析整數,可指定基數(進制),忽略非數字字符。parseFloat()
:解析浮點數,只能處理十進制,忽略非數字字符。- 一元運算符
+
:簡潔的轉換方式,效果類似Number()
。
示例代碼:
const str1 = "123";
const str2 = "123.45";
const str3 = "123abc";
const str4 = "";console.log(Number(str1)); // 123(整數)
console.log(Number(str2)); // 123.45(浮點數)
console.log(Number(str3)); // NaN(包含非數字字符)
console.log(Number(str4)); // 0(空字符串)console.log(parseInt(str1)); // 123
console.log(parseInt(str2)); // 123(忽略小數部分)
console.log(parseInt(str3)); // 123(忽略"abc")
console.log(parseInt("11", 2)); // 3(解析二進制的"11"為十進制3)console.log(parseFloat(str2)); // 123.45
console.log(parseFloat(str3)); // 123console.log(+str1); // 123(同Number())
console.log(+str4); // 0
26. 如何判斷一個值是否為 NaN?
NaN
(Not a Number)是一個特殊的數值,表示“不是數字”,但 typeof NaN
會返回 'number'
,且 NaN
不等于任何值(包括自身)。
判斷方法:
- 使用全局函數
isNaN()
:但會先將參數轉換為數字,非數字轉換后為NaN
則返回true
(有缺陷)。 - 使用
Number.isNaN()
:ES6 新增,僅當參數是NaN
時返回true
(更準確)。 - 利用
NaN!== NaN
的特性:通過值是否不等于自身判斷。
示例代碼:
const a = NaN;
const b = "not a number";
const c = 123;console.log(isNaN(a)); // true
console.log(isNaN(b)); // true("not a number"轉換為數字是NaN)
console.log(isNaN(c)); // falseconsole.log(Number.isNaN(a)); // true
console.log(Number.isNaN(b)); // false(b是字符串,不是NaN)
console.log(Number.isNaN(c)); // falseconsole.log(a!== a); // true(NaN的獨特特性)
console.log(b!== b); // false
27. 數組扁平化的方法有哪些?
數組扁平化是將多維數組轉換為一維數組,常見方法:
Array.prototype.flat(depth)
:ES6 方法,depth
為扁平化深度(默認1,Infinity
表示完全扁平化)。- 遞歸遍歷:通過遞歸判斷元素是否為數組,非數組則添加到結果。
toString()
+split()
:將數組轉為字符串后分割(僅適用于純數字數組)。
示例代碼:
const arr = [1, [2, [3, [4]], 5]];// 方法1:flat()
console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5]// 方法2:遞歸
function flatten(arr) {let result = [];for (let item of arr) {if (Array.isArray(item)) {result = result.concat(flatten(item)); // 遞歸處理子數組} else {result.push(item);}}return result;
}
console.log(flatten(arr)); // [1, 2, 3, 4, 5]// 方法3:toString() + split()(僅數字數組)
console.log(arr.toString().split(',').map(Number)); // [1, 2, 3, 4, 5]
28. 什么是函數柯里化(Currying)?舉例說明
函數柯里化是將接收多個參數的函數轉換為一系列接收單個參數的函數的過程,每次調用返回一個新函數,直到接收完所有參數后執行原函數邏輯。
作用:參數復用、延遲執行、提高函數靈活性。
示例代碼:
// 普通函數:計算a + b + c
function add(a, b, c) {return a + b + c;
}// 柯里化后的函數
function curryAdd(a) {return function(b) {return function(c) {return a + b + c;};};
}console.log(add(1, 2, 3)); // 6
console.log(curryAdd(1)(2)(3)); // 6// 參數復用示例:固定第一個參數為10
const add10 = curryAdd(10);
console.log(add10(2)(3)); // 15(10 + 2 + 3)
console.log(add10(5)(5)); // 20(10 + 5 + 5)
29. 什么是擴展運算符(…)?有哪些用途?
擴展運算符(...
)用于將可迭代對象(數組、字符串、Set等)展開為獨立元素,常見用途:
- 復制數組或對象(淺拷貝)。
- 合并數組或對象。
- 將類數組轉換為數組。
- 函數傳參時展開數組作為參數。
示例代碼:
// 1. 復制數組
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // [1, 2, 3]// 2. 合并數組
const arr3 = [4, 5];
const mergedArr = [...arr1, ...arr3]; // [1, 2, 3, 4, 5]// 3. 復制對象
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // {a:1, b:2, c:3}// 4. 合并對象(后面的屬性覆蓋前面的)
const obj3 = { b: 3, d: 4 };
const mergedObj = { ...obj1, ...obj3 }; // {a:1, b:3, d:4}// 5. 轉換類數組(如arguments)
function sum() {return [...arguments].reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3)); // 6// 6. 展開字符串
const str = "hello";
console.log([...str]); // ['h', 'e', 'l', 'l', 'o']
30. 如何交換兩個變量的值(不使用臨時變量)?
不使用臨時變量交換兩個變量的值,可通過以下方法:
- 數組解構賦值(ES6,最簡潔)。
- 算術運算(僅適用于數字)。
- 異或運算(適用于整數,利用二進制特性)。
示例代碼:
// 方法1:數組解構賦值(通用)
let x = 10, y = 20;
[x, y] = [y, x];
console.log(x, y); // 20 10// 方法2:算術運算(僅數字)
let a = 5, b = 8;
a = a + b; // a = 13
b = a - b; // b = 13 - 8 = 5
a = a - b; // a = 13 - 5 = 8
console.log(a, b); // 8 5// 方法3:異或運算(適用于整數)
let m = 3, n = 5;
m = m ^ n; // m = 3 ^ 5 = 6(二進制11 ^ 101 = 110)
n = m ^ n; // n = 6 ^ 5 = 3(110 ^ 101 = 011)
m = m ^ n; // m = 6 ^ 3 = 5(110 ^ 011 = 101)
console.log(m, n); // 5 3
二、150道面試題目錄列表
文章序號 | Javascript面試題150道 |
---|---|
1 | Javascript面試題及答案150道(001-015) |
2 | Javascript面試題及答案150道(016-030) |
3 | Javascript面試題及答案150道(031-045) |
4 | Javascript面試題及答案150道(046-060) |
5 | Javascript面試題及答案150道(061-075) |
6 | Javascript面試題及答案150道(076-090) |
7 | Javascript面試題及答案150道(091-105) |
8 | Javascript面試題及答案150道(106-120) |
9 | Javascript面試題及答案150道(121-135) |
10 | Javascript面試題及答案150道(136-150) |