前端核心知識點梳理與面試題詳解
1. Promise
核心知識點
- Promise 是異步編程的解決方案,用于處理異步操作
- 三種狀態:pending(進行中)、fulfilled(已成功)、rejected(已失敗)
- 狀態一旦改變就不會再變,從 pending 到 fulfilled 或 rejected
- 常用方法:then()、catch()、finally()、all()、race()、allSettled() 等
面試題:實現 Promise.all
Promise.myAll = function(promises) {return new Promise((resolve, reject) => {// 判斷傳入的是否是可迭代對象if (!Array.isArray(promises)) {return reject(new TypeError('The input must be an array'));}const results = [];let completedCount = 0;const total = promises.length;if (total === 0) {return resolve(results);}promises.forEach((promise, index) => {// 確保每個元素都是 Promise 對象Promise.resolve(promise).then((value) => {results[index] = value;completedCount++;// 所有 Promise 都成功時才 resolveif (completedCount === total) {resolve(results);}},(reason) => {// 有一個 Promise 失敗就立即 rejectreject(reason);});});});
};
面試題:實現 Promise 串行執行
// 實現一個函數,讓多個Promise按順序執行
function promiseSerial(tasks) {// 初始返回一個已 resolved 的 Promisereturn tasks.reduce((prev, current) => {return prev.then((results) => {// 等待當前任務執行完成return current().then((result) => {// 將結果添加到數組中return [...results, result];});});}, Promise.resolve([]));
}// 使用示例
const createTask = (time, value) => {return () => new Promise(resolve => {setTimeout(() => {console.log(value);resolve(value);}, time);});
};const tasks = [createTask(1000, '任務1'),createTask(500, '任務2'),createTask(800, '任務3')
];promiseSerial(tasks).then(results => {console.log('所有任務完成:', results);
});
2. 原型鏈
核心知識點
- 每個對象都有
__proto__
屬性,指向其構造函數的 prototype - 構造函數有 prototype 屬性,是其實例的原型
- 原型鏈是由
__proto__
連接而成的鏈式結構 - 當訪問對象的屬性或方法時,會先在自身查找,找不到則沿原型鏈向上查找
Object.prototype
是原型鏈的終點,其__proto__
為 null
面試題:原型鏈相關輸出題
function Foo() {getName = function() { console.log(1); };return this;
}Foo.getName = function() { console.log(2); };Foo.prototype.getName = function() { console.log(3); };var getName = function() { console.log(4); };function getName() { console.log(5); }// 以下輸出結果是什么?
Foo.getName(); // 2 - 訪問Foo的靜態方法
getName(); // 4 - 函數聲明提升后被變量聲明覆蓋
Foo().getName(); // 1 - Foo()執行后修改了全局getName
getName(); // 1 - 已經被Foo()修改
new Foo.getName(); // 2 - 優先級問題,相當于new (Foo.getName)()
new Foo().getName(); // 3 - 實例化后訪問原型上的方法
new new Foo().getName();// 3 - 先實例化Foo,再調用其原型上的getName并實例化
面試題:實現 instanceof
function myInstanceof(left, right) {// 基本類型直接返回falseif (typeof left !== 'object' || left === null) return false;// 獲取對象的原型let proto = Object.getPrototypeOf(left);// 遍歷原型鏈while (true) {// 找到盡頭仍未匹配,返回falseif (proto === null) return false;// 找到匹配的原型,返回trueif (proto === right.prototype) return true;// 繼續向上查找proto = Object.getPrototypeOf(proto);}
}// 測試
function Person() {}
const p = new Person();
console.log(myInstanceof(p, Person)); // true
console.log(myInstanceof(p, Object)); // true
console.log(myInstanceof(p, Array)); // false
console.log(myInstanceof([], Array)); // true
3. 生成器(Generator)
核心知識點
- 生成器函數使用
function*
聲明,內部使用yield
關鍵字 - 調用生成器函數返回迭代器對象,而非直接執行函數體
- 通過迭代器的
next()
方法控制函數執行,每次遇到yield
暫停 next()
方法返回包含value
和done
屬性的對象- 可用于實現異步操作的同步化表達、自定義迭代器等
面試題:使用 Generator 實現異步操作
// 模擬異步操作
function fetchData(url) {return new Promise((resolve) => {setTimeout(() => {resolve(`數據: ${url}`);}, 1000);});
}// 使用Generator處理異步
function* getData() {console.log('開始請求數據1');const data1 = yield fetchData('url1');console.log('獲取到數據1:', data1);console.log('開始請求數據2');const data2 = yield fetchData('url2');console.log('獲取到數據2:', data2);return '所有數據獲取完成';
}// 執行生成器
function runGenerator(gen) {const iterator = gen();function handleResult(result) {if (result.done) {console.log('最終結果:', result.value);return;}// 處理Promiseresult.value.then(data => {handleResult(iterator.next(data));});}handleResult(iterator.next());
}// 運行
runGenerator(getData);
4. 閉包
核心知識點
- 閉包是函數及其捆綁的周邊環境狀態的引用的組合
- 形成條件:函數嵌套、內部函數引用外部函數的變量、內部函數被外部引用
- 作用:實現私有變量、模塊化、柯里化、保存狀態等
- 注意:不當使用可能導致內存泄漏
面試題:實現防抖函數
function debounce(fn, delay, immediate = false) {let timer = null;let isInvoked = false;// 返回閉包函數return function(...args) {const context = this;// 如果存在定時器,清除它if (timer) {clearTimeout(timer);}// 立即執行的情況if (immediate && !isInvoked) {fn.apply(context, args);isInvoked = true;} else {// 延遲執行timer = setTimeout(() => {fn.apply(context, args);isInvoked = false;timer = null;}, delay);}};
}// 使用示例
const handleSearch = (keyword) => {console.log('搜索:', keyword);
};// 防抖處理,延遲500ms執行,不立即執行
const debouncedSearch = debounce(handleSearch, 500);// 模擬頻繁調用
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc'); // 只有最后一次會在500ms后執行
面試題:實現節流函數
function throttle(fn, interval) {let lastTime = 0;let timer = null;return function(...args) {const context = this;const now = Date.now();const remainingTime = interval - (now - lastTime);// 如果時間間隔已到,直接執行if (remainingTime <= 0) {if (timer) {clearTimeout(timer);timer = null;}fn.apply(context, args);lastTime = now;} else if (!timer) {// 否則設置定時器,確保最后一次一定會執行timer = setTimeout(() => {fn.apply(context, args);lastTime = Date.now();timer = null;}, remainingTime);}};
}// 使用示例
const handleScroll = () => {console.log('滾動事件觸發');
};// 節流處理,每100ms最多執行一次
const throttledScroll = throttle(handleScroll, 100);// 監聽滾動事件
window.addEventListener('scroll', throttledScroll);
5. 異步與事件循環
核心知識點
- JavaScript 是單線程語言,通過事件循環實現異步
- 事件循環:調用棧 -> 微任務隊列 -> 宏任務隊列 -> UI渲染
- 微任務優先級高于宏任務,常見微任務:Promise.then/catch/finally、process.nextTick、MutationObserver
- 常見宏任務:setTimeout、setInterval、DOM事件、I/O操作、setImmediate
面試題:事件循環輸出題
console.log('1');setTimeout(function() {console.log('2');new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4');});
}, 0);new Promise(function(resolve) {console.log('5');resolve();
}).then(function() {console.log('6');
});setTimeout(function() {console.log('7');new Promise(function(resolve) {console.log('8');resolve();}).then(function() {console.log('9');});
}, 0);console.log('10');// 輸出順序:1 5 10 6 2 3 4 7 8 9
6. Map
核心知識點
- Map 是 ES6 新增的鍵值對集合
- 與對象相比,Map 的鍵可以是任意類型,而對象的鍵只能是字符串或 Symbol
- 常用方法:set()、get()、has()、delete()、clear()、size 屬性
- 可以通過 for…of 直接迭代,迭代順序是插入順序
- 適合用于頻繁添加/刪除鍵值對的場景
面試題:實現 Map 的基本功能
class MyMap {constructor() {// 存儲鍵值對的數組this.items = [];}// 向Map中添加元素set(key, value) {// 檢查鍵是否已存在const index = this.items.findIndex(item => this.isEqual(item.key, key));if (index !== -1) {// 鍵存在則更新值this.items[index].value = value;} else {// 鍵不存在則添加新鍵值對this.items.push({ key, value });}return this;}// 獲取指定鍵的值get(key) {const item = this.items.find(item => this.isEqual(item.key, key));return item ? item.value : undefined;}// 檢查是否包含指定鍵has(key) {return this.items.some(item => this.isEqual(item.key, key));}// 刪除指定鍵delete(key) {const index = this.items.findIndex(item => this.isEqual(item.key, key));if (index !== -1) {this.items.splice(index, 1);return true;}return false;}// 清空Mapclear() {this.items = [];}// 獲取Map的大小get size() {return this.items.length;}// 判斷兩個鍵是否相等isEqual(a, b) {// 處理NaN的情況,NaN !== NaN但在Map中視為相等if (a !== a && b !== b) return true;// 處理+0和-0的情況,在Map中視為相等if (a === 0 && b === 0) return 1 / a === 1 / b;return a === b;}// 迭代器,用于for...of循環*[Symbol.iterator]() {for (const item of this.items) {yield [item.key, item.value];}}
}// 使用示例
const map = new MyMap();
map.set('name', '張三');
map.set(1, '數字1');
map.set(NaN, 'NaN值');
console.log(map.get('name')); // 張三
console.log(map.size); // 3
console.log(map.has(NaN)); // truefor (const [key, value] of map) {console.log(key, value);
}
7. 數組
核心知識點
- 數組是有序的元素集合,具有動態長度
- 常用方法:push()、pop()、shift()、unshift()、slice()、splice() 等
- 高階函數:map()、filter()、reduce()、forEach()、find()、some()、every() 等
- 數組去重、扁平化、排序是常見操作
面試題:數組扁平化
// 方法1:使用遞歸
function flatten(arr, depth = Infinity) {if (depth <= 0) return arr.slice();return arr.reduce((acc, item) => {if (Array.isArray(item)) {// 遞歸扁平化,并減少深度return acc.concat(flatten(item, depth - 1));} else {return acc.concat(item);}}, []);
}// 方法2:使用ES6的flat方法
// const flattened = arr.flat(depth);// 測試
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArray)); // [1, 2, 3, 4, 5]
console.log(flatten(nestedArray, 1)); // [1, 2, [3, [4]], 5]
面試題:數組去重
// 方法1:使用Set
function unique1(arr) {return [...new Set(arr)];
}// 方法2:使用filter和indexOf
function unique2(arr) {return arr.filter((item, index) => {return arr.indexOf(item) === index;});
}// 方法3:使用對象鍵值對
function unique3(arr) {const obj = {};return arr.filter(item => {// 處理不同類型但值相同的情況,如1和'1'const key = typeof item + item;if (!obj[key]) {obj[key] = true;return true;}return false;});
}// 方法4:使用Map
function unique4(arr) {const map = new Map();return arr.filter(item => {if (!map.has(item)) {map.set(item, true);return true;}return false;});
}// 測試
const testArray = [1, 2, 2, '3', '3', true, true, false, false, null, null, undefined, undefined, NaN, NaN];
console.log(unique1(testArray)); // Set方法能正確去重NaN
console.log(unique2(testArray)); // indexOf無法識別NaN,會保留重復的NaN
console.log(unique3(testArray)); // 能區分不同類型的值
console.log(unique4(testArray)); // Map方法也能正確去重NaN
以上梳理了前端核心知識點及常見面試題,涵蓋了Promise、原型鏈、生成器、閉包、異步、事件循環、Map和數組等內容。這些知識點不僅是面試高頻考點,也是日常開發中經常用到的核心概念,掌握這些內容對于前端工程師至關重要。
前端對象與字符串知識點梳理及面試題詳解
一、對象(Object)
核心知識點
- 對象是鍵值對的集合,鍵可以是字符串或Symbol,值可以是任意類型
- 對象的創建方式:對象字面量
{}
、new Object()
、構造函數、Object.create()
等 - 對象屬性的訪問方式:點語法(
obj.key
)和方括號語法(obj['key']
) - 可枚舉屬性與不可枚舉屬性:可枚舉屬性會被
for...in
遍歷到 - 對象的特性:
writable
(可寫)、enumerable
(可枚舉)、configurable
(可配置) - 常見方法:
Object.keys()
、Object.values()
、Object.entries()
、Object.assign()
等
面試題:實現對象的深拷貝
function deepClone(obj, hash = new WeakMap()) {// 處理null和基本類型if (obj === null || typeof obj !== 'object') {return obj;}// 處理日期if (obj instanceof Date) {return new Date(obj);}// 處理正則if (obj instanceof RegExp) {return new RegExp(obj.source, obj.flags);}// 處理循環引用if (hash.has(obj)) {return hash.get(obj);}// 區分數組和對象const cloneObj = Array.isArray(obj) ? [] : {};hash.set(obj, cloneObj);// 遍歷屬性(包括Symbol鍵)Reflect.ownKeys(obj).forEach(key => {cloneObj[key] = deepClone(obj[key], hash);});return cloneObj;
}// 測試
const obj = {a: 1,b: { c: 2 },d: [3, 4],e: new Date(),f: /test/,[Symbol('g')]: 5
};
obj.self = obj; // 循環引用const cloned = deepClone(obj);
console.log(cloned);
面試題:實現對象的扁平化解構
function flattenObject(obj, prefix = '', result = {}) {for (const key in obj) {if (obj.hasOwnProperty(key)) {const value = obj[key];const newKey = prefix ? `${prefix}.${key}` : key;if (typeof value === 'object' && value !== null && !Array.isArray(value)) {// 遞歸處理嵌套對象flattenObject(value, newKey, result);} else {result[newKey] = value;}}}return result;
}// 測試
const nestedObj = {a: 1,b: {c: 2,d: {e: 3,f: 4}},g: 5
};console.log(flattenObject(nestedObj));
// 輸出: { a: 1, 'b.c': 2, 'b.d.e': 3, 'b.d.f': 4, g: 5 }
面試題:實現對象的屬性攔截(類似Vue2的響應式原理)
function observe(obj) {if (typeof obj !== 'object' || obj === null) {return;}// 遍歷對象屬性Object.keys(obj).forEach(key => {let value = obj[key];// 遞歸處理嵌套對象observe(value);// 重定義屬性Object.defineProperty(obj, key, {get() {console.log(`獲取屬性${key}的值: ${value}`);return value;},set(newValue) {console.log(`設置屬性${key}的值: ${newValue}`);// 處理新值為對象的情況observe(newValue);value = newValue;}});});
}// 測試
const data = {name: '張三',age: 20,address: {city: '北京'}
};observe(data);
data.name; // 觸發get
data.age = 21; // 觸發set
data.address.city = '上海'; // 觸發get和set
二、字符串(String)
核心知識點
- 字符串是字符的有序序列,在JavaScript中是不可變的
- 常見創建方式:字符串字面量(
''
或""
)、模板字符串(` `
)、new String()
- 常用屬性:
length
(長度) - 常用方法:
- 查找:
indexOf()
、lastIndexOf()
、includes()
、startsWith()
、endsWith()
- 截取:
slice()
、substring()
、substr()
- 轉換:
toUpperCase()
、toLowerCase()
、trim()
- 其他:
split()
、replace()
、charAt()
、concat()
- 查找:
- 模板字符串支持多行文本和變量插值(
${}
)
面試題:實現字符串反轉
// 方法1:使用數組方法
function reverseString1(str) {return str.split('').reverse().join('');
}// 方法2:使用for循環
function reverseString2(str) {let result = '';for (let i = str.length - 1; i >= 0; i--) {result += str[i];}return result;
}// 方法3:使用遞歸
function reverseString3(str) {if (str === '') {return '';} else {return reverseString3(str.substr(1)) + str.charAt(0);}
}// 測試
console.log(reverseString1('hello')); // 'olleh'
console.log(reverseString2('world')); // 'dlrow'
console.log(reverseString3('test')); // 'tset'
面試題:實現字符串中的單詞反轉(不改變單詞順序)
function reverseWords(str) {// 分割單詞(處理多個空格的情況)const words = str.split(/\s+/);// 反轉每個單詞const reversedWords = words.map(word => {return word.split('').reverse().join('');});// 拼接回字符串return reversedWords.join(' ');
}// 測試
console.log(reverseWords('Hello World')); // 'olleH dlroW'
console.log(reverseWords('I love JavaScript')); // 'I evol tpircSavaJ'
console.log(reverseWords(' Hello there ')); // ' olleH ereht '
面試題:實現千位分隔符格式化數字
function formatNumber(num) {// 處理非數字情況if (typeof num !== 'number' || isNaN(num)) {return '0';}// 轉換為字符串并分割整數和小數部分const parts = num.toString().split('.');let integerPart = parts[0];const decimalPart = parts[1] || '';// 處理負數let sign = '';if (integerPart[0] === '-') {sign = '-';integerPart = integerPart.slice(1);}// 反轉字符串便于處理let reversed = integerPart.split('').reverse().join('');let formatted = '';// 每三位添加一個逗號for (let i = 0; i < reversed.length; i++) {if (i !== 0 && i % 3 === 0) {formatted += ',';}formatted += reversed[i];}// 反轉回來并拼接符號和小數部分formatted = sign + formatted.split('').reverse().join('');return decimalPart ? `${formatted}.${decimalPart}` : formatted;
}// 測試
console.log(formatNumber(1234567)); // '1,234,567'
console.log(formatNumber(1234567.89)); // '1,234,567.89'
console.log(formatNumber(-123456)); // '-123,456'
console.log(formatNumber(0)); // '0'
面試題:實現字符串匹配算法(KMP算法)
// 構建部分匹配表
function buildLPS(pattern) {const lps = new Array(pattern.length).fill(0);let len = 0; // 最長前綴后綴的長度for (let i = 1; i < pattern.length; ) {if (pattern[i] === pattern[len]) {len++;lps[i] = len;i++;} else {if (len !== 0) {len = lps[len - 1];} else {lps[i] = 0;i++;}}}return lps;
}// KMP匹配算法
function kmpSearch(text, pattern) {const n = text.length;const m = pattern.length;if (m === 0) return 0; // 空模式匹配開頭const lps = buildLPS(pattern);let i = 0; // text的索引let j = 0; // pattern的索引while (i < n) {if (pattern[j] === text[i]) {i++;j++;if (j === m) {return i - j; // 找到匹配,返回起始索引}} else {if (j !== 0) {j = lps[j - 1];} else {i++;}}}return -1; // 未找到匹配
}// 測試
console.log(kmpSearch('ABC ABCDAB ABCDABCDABDE', 'ABCDABD')); // 15
console.log(kmpSearch('hello world', 'world')); // 6
console.log(kmpSearch('test string', 'xyz')); // -1
對象和字符串是JavaScript中最基礎也是最常用的數據類型,掌握它們的特性和操作方法對于前端開發至關重要。上述知識點和面試題覆蓋了常見的考點和實際開發需求,深入理解這些內容有助于提升代碼質量和解決問題的能力。
以下是對前端核心知識點(Promise、原型鏈、閉包、異步、事件循環、Map、數組)的系統梳理及高頻手寫題目詳解,結合面試考點和實現細節,助你高效備戰。
一、核心知識梳理與面試詳解
? 1. Promise與異步編程
- 核心機制:
- 三種狀態:
Pending
(等待)、Fulfilled
(成功)、Rejected
(失敗),狀態不可逆。 - 鏈式調用:
.then()
處理成功狀態,.catch()
捕獲失敗,.finally()
無論成敗均執行。
- 三種狀態:
- 手寫
Promise.all
:
得分點:function myPromiseAll(promises) {return new Promise((resolve, reject) => {// 校驗輸入是否為可迭代對象if (typeof promises?.[Symbol.iterator] !== 'function') {reject(new TypeError('Argument is not iterable'));return;}const promiseArray = Array.from(promises);const results = new Array(promiseArray.length);let completedCount = 0;// 空數組直接返回if (promiseArray.length === 0) resolve(results);promiseArray.forEach((p, i) => {Promise.resolve(p) // 包裝非Promise值.then(res => {results[i] = res; // 按索引存儲結果if (++completedCount === promiseArray.length) resolve(results);}).catch(err => reject(err)); // 任一失敗立即終止});}); }
- 校驗可迭代對象、處理空數組、非Promise包裝。
- 結果順序與輸入一致(避免
push
導致錯亂)。
🔗 2. 原型鏈與繼承
- 核心概念:
- 原型(Prototype):構造函數(如
Array
)的prototype
屬性,存放共享方法(如Array.prototype.map
)。 - 原型鏈:對象通過
__proto__
向上查找屬性,終點為Object.prototype.__proto__ === null
。
- 原型(Prototype):構造函數(如
- 繼承實現:
面試重點:// 寄生組合繼承(最優解) function Parent(name) { this.name = name; } Parent.prototype.say = function() { console.log(this.name); }; function Child(name, age) {Parent.call(this, name); // 繼承實例屬性this.age = age; } Child.prototype = Object.create(Parent.prototype); // 繼承方法 Child.prototype.constructor = Child; // 修復構造函數指向
- 避免組合繼承的兩次調用父類構造函數問題。
- ES6的
class
本質是語法糖(如super()
調用父類構造)。
🔒 3. 閉包與生成器
- 閉包(Closure):
- 原理:函數嵌套,內層函數訪問外層作用域的變量(即使外層已銷毀)。
- 應用:
優勢:狀態隔離(多個生成器互不干擾)。// 自增ID生成器 function createIdGenerator(init = 0) {let id = init;return () => ++id; // 閉包保存id狀態 } const gen = createIdGenerator(); gen(); // 1, gen(); // 2
- 生成器(Generator):
- 特性:
function*
定義,yield
暫停執行,next()
恢復執行。 - 異步應用:配合
co
庫實現類似async/await
的異步控制(已逐漸被替代)。
- 特性:
🔁 4. 事件循環(Event Loop)
- 執行順序:
- 同步代碼 → 微任務(
Promise.then
、MutationObserver
) → 宏任務(setTimeout
、DOM事件
)。
- 同步代碼 → 微任務(
- 經典面試題:
原理:微任務優先級高于宏任務,同步代碼執行完畢后清空微任務隊列。console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4'); // 輸出:1 → 4 → 3 → 2
🗺? 5. Map與數組高頻操作
-
Map vs. Object:
- Map優勢:鍵可為任意類型(對象、函數)、有序迭代、性能更優(頻繁增刪場景)。
- API:
set()
,get()
,has()
,delete()
。
-
數組重點方法:
方法 用途 是否改變原數組 map()
映射新數組 ? filter()
過濾滿足條件的元素 ? reduce()
累計計算(如求和、扁平化) ? splice()
刪除/插入元素 ? slice()
截取子數組 ? 手寫數組扁平化:
function flatten(arr) {return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []); } flatten([1, [2, [3]]]); // [1, 2, 3]
二、高頻手撕題目詳解
1. 防抖(Debounce)與節流(Throttle)
- 防抖:連續觸發時只執行最后一次:
function debounce(fn, delay) {let timer;return (...args) => {clearTimeout(timer);timer = setTimeout(() => fn.apply(this, args), delay);}; } // 場景:搜索框輸入聯想(停止輸入300ms后請求)
- 節流:固定間隔執行一次:
function throttle(fn, interval) {let lastTime = 0;return (...args) => {const now = Date.now();if (now - lastTime >= interval) {fn.apply(this, args);lastTime = now;}}; } // 場景:滾動事件(每200ms計算位置)
2. 手寫深拷貝
function deepClone(obj, map = new Map()) {if (typeof obj !== 'object' || obj === null) return obj;if (map.has(obj)) return map.get(obj); // 解決循環引用const clone = Array.isArray(obj) ? [] : {};map.set(obj, clone);for (const key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key], map);}}return clone;
}
邊界處理:循環引用(Map
記錄已拷貝對象)、函數和正則等特殊類型。
三、面試策略與復習建議
- 手寫題要點:
- 先口述思路(如防抖的“清除定時器+延遲執行”),再寫代碼。
- 處理邊界:空輸入、非預期類型(如
Promise.all
校驗可迭代對象)。
- 框架結合:
- React:閉包在
useCallback
中的應用(依賴項變化才更新函數)。 - Vue:響應式原理依賴
Object.defineProperty
的getter/setter
(類閉包狀態保存)。
- React:閉包在
- 性能優化:
- 減少閉包濫用(避免內存泄漏),如事件監聽后及時移除。
- 數組操作優先
map
/filter
(返回新數組,避免forEach
副作用)。
本文完整題目及擴展解析參考:https://blog.csdn.net/weixin_48377020/article/details/132241816 | https://blog.csdn.net/T_iAn_T/article/details/145597660。
在 JavaScript 中,對象(引用類型)和字符串(基本類型)的拷貝機制存在本質差異,這直接影響開發中的數據處理邏輯。以下是關鍵知識點梳理和對比:
📦 一、對象的拷貝
1. 淺拷貝(Shallow Copy)
- 特點:僅復制對象的第一層屬性。嵌套對象仍共享內存地址。
- 實現方式:
Object.assign({}, obj)
- 展開運算符
{ ...obj }
- 數組方法如
slice()
、concat()
- 示例:
const obj = { a: 1, b: { c: 2 } }; const shallowCopy = { ...obj }; shallowCopy.b.c = 3; // 修改嵌套屬性 console.log(obj.b.c); // 3(原對象被影響)
2. 深拷貝(Deep Copy)
- 特點:遞歸復制所有層級,新舊對象完全獨立。
- 實現方式:
JSON.parse(JSON.stringify(obj))
- ? 簡單快捷
- ? 無法處理函數、
undefined
、Symbol
、Date
(轉為字符串)、循環引用
- 遞歸實現:
function deepClone(obj, hash = new WeakMap()) {if (typeof obj !== 'object' || obj === null) return obj;if (hash.has(obj)) return hash.get(obj); // 解決循環引用const clone = Array.isArray(obj) ? [] : {};hash.set(obj, clone);for (const key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key], hash);}}return clone; }
- 特殊類型處理:
Date
→new Date(obj.getTime())
RegExp
→new RegExp(obj)
Map
/Set
→ 遞歸復制元素
- 現代 API:
structuredClone()
- ? 支持循環引用、
Date
、Map
、Set
等 - ? 不支持函數、DOM 節點
- ? 支持循環引用、
- 第三方庫:
_.cloneDeep(obj)
(Lodash)
🔤 二、字符串的拷貝
- 字符串是基本類型(Primitive Type),賦值時直接復制值,而非引用地址。
- 示例:
const str1 = "hello"; const str2 = str1; // 復制值 str2 = "world"; // 修改不影響 str1 console.log(str1); // "hello"
- 特點:
- 無需深拷貝/淺拷貝概念,每次賦值均創建獨立副本。
- 操作(如拼接、切片)均返回新字符串,原字符串不變。
?? 三、關鍵差異總結
特性 | 對象(引用類型) | 字符串(基本類型) |
---|---|---|
拷貝機制 | 賦值傳遞引用地址 | 賦值直接復制值 |
修改影響 | 淺拷貝時嵌套屬性互相影響 | 修改后原數據不變 |
深拷貝需求 | 必需(需遞歸處理嵌套引用) | 無需 |
內存占用 | 淺拷貝節省內存,深拷貝開銷大 | 每次修改均創建新副本 |
💡 四、實際應用建議
- 對象拷貝場景:
- 簡單數據且無特殊類型 →
JSON.parse(JSON.stringify())
- 復雜對象(含循環引用、
Map
等)→structuredClone()
或 Lodash 的_.cloneDeep()
- 簡單數據且無特殊類型 →
- 字符串操作:
- 直接賦值或使用
slice()
、substring()
等返回新字符串的方法。
- 直接賦值或使用
- 性能優化:
- 避免對大對象頻繁深拷貝,改用 不可變數據(Immutable.js) 或 按需拷貝。
完整實現代碼及邊界案例可參考:https://blog.csdn.net/qq_53353440/article/details/148548048、https://developer.mozilla.org/en-US/docs/Web/API/structuredClone。