JavaScript中的幾種內(nèi)置數(shù)據(jù)結(jié)構(gòu)
前言:從算法題說起
找出下列數(shù)組中重復(fù)的元素(假設(shè)所有數(shù)字最多只出現(xiàn)2次):
const arr = [1, 2, 3, 4, 5, 1, 2, 3];
解法1:使用Array方法(時間復(fù)雜度O(n²))
function findDuplicatesWithArray(arr) {
return arr.filter((item, index) => arr.indexOf(item) !== index);
}
解法2:使用Set(時間復(fù)雜度O(n))
function findDuplicatesWithSet(arr) {
const seen = new Set();
const duplicates = new Set();
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item);
} else {
seen.add(item);
}
}
return Array.from(duplicates);
}解法3:使用Object(時間復(fù)雜度O(n))
function findDuplicatesWithObject(arr) {
const seen = {};
const duplicates = [];
for (const item of arr) {
if (seen[item]) {
duplicates.push(item);
} else {
seen[item] = true;
}
}
return duplicates;
}上述解法都能解決這一問題,但隨著數(shù)據(jù)量增大,這些方案的性能差異會非常明顯。要理解為什么,我們就需要深入了解每種數(shù)據(jù)結(jié)構(gòu)的特點。
Map vs Object:鍵值對的現(xiàn)代選擇
Map
Map的基本使用
使用 new 關(guān)鍵字和 Map 構(gòu)造函數(shù),可以創(chuàng)建一個空的 Map 映射:
const map = new Map();
當然,我們也可以在創(chuàng)建的時候,同時初始化實例,只需要給Map 構(gòu)造函數(shù)傳入一個可迭代的對象即可:
const map = new Map([{
a: 1
}]);Map的核心特性
1. 允許任意類型的鍵
map.set('string', '字符串鍵');
map.set(123, '數(shù)字鍵');
map.set(true, '布爾鍵');
map.set({}, '對象鍵');
map.set([], '數(shù)組鍵');
map.set(() => {}, '函數(shù)鍵');
map.set(null, 'null鍵');
map.set(undefined, 'undefined鍵');2. 順序保持
map.set('first', 1);
map.set('second', 2);
map.set('third', 3);
for (const [key, value] of map) {
console.log(key, '-', value); // 保持插入順序
}3. 可以直接獲取大小
console.log('Map大小:', map.size);
4. 高效的查找和刪除
console.log('查找key為123:', map.has(123)); // true
console.log('獲取key為123的值:', map.get(123)); // '數(shù)字鍵'
map.delete(123);
console.log('刪除后查找key為123:', map.has(123)); // false5. 允許批量清除等操作
map.clear();
console.log('清空后的大小:', map.size); // 0
Object
Object的基本使用
在 JavaScript 中,顯式地創(chuàng)建 Object 的實例有兩種方式:一種是使用 new 關(guān)鍵字和 Object 構(gòu)造函數(shù);另一種是使用對象字面量表示法。
new關(guān)鍵字和Object構(gòu)造函數(shù)
const obj = new Object();
對象字面量表示法
const obj = {};
Object的核心特性
1. 鍵只能是字符串或Symbol
obj.string = '字符串鍵'; // 鍵會被轉(zhuǎn)換為字符串
obj[123] = '數(shù)字鍵'; // 數(shù)字鍵會被轉(zhuǎn)換為字符串 '123'
obj[true] = '布爾鍵'; // 布爾鍵會被轉(zhuǎn)換為字符串 'true'
obj[{}] = '對象鍵'; // 對象鍵會被轉(zhuǎn)換為字符串 '[object Object]'
obj[[]] = '數(shù)組鍵'; // 數(shù)組鍵會被轉(zhuǎn)換為字符串 ''
obj[null] = 'null鍵'; // null鍵會被轉(zhuǎn)換為字符串 'null'
obj[undefined] = 'undefined鍵'; // undefined鍵會被轉(zhuǎn)換為字符串 'undefined'2. 順序問題(ES6后字符串鍵按插入順序,但仍有特殊情況)
const obj2 = {};
obj2['2'] = 'two';
obj2['1'] = 'one';
obj2['10'] = 'ten';
console.log('Object遍歷順序:');
for (const key in obj2) {
console.log(key, '-', obj2[key]); // 1 - one, 2 - two, 10 - ten
// 數(shù)字字符串鍵會被排序!
}3. 大小需要手動計算
console.log('Object鍵的數(shù)量:', Object.keys(obj).length);
4. 繼承的屬性和方法
console.log('toString' in obj); // true(來自原型鏈)
console.log(obj.hasOwnProperty('toString')); // false
何時選擇Map?何時選擇Object?
場景1:鍵是動態(tài)的,且類型多樣的:使用Map
class DynamicKeyRegistry {
constructor() {
this.registry = new Map();
}
set(key, value) {
this.registry.set(key, value);
return this;
}
get(key) {
return this.registry.get(key);
}
// 可以存儲任何類型的鍵
registerComponent(component) {
this.registry.set(component, {
mounted: false,
instances: []
});
}
}場景2:需要頻繁增刪鍵值對:使用Map
class Cache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map(); // Map保持插入順序
}
get(key) {
if (!this.cache.has(key)) return -1;
return this.cache.get(key);
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// 刪除最舊的(Map第一個鍵)
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, value);
}
}場景3:JSON數(shù)據(jù)、配置對象、純數(shù)據(jù)存儲:使用Object
const user = {
name: '張三',
age: 30,
email: 'zhangsan@example.com',
// 可以被JSON.stringify直接序列化
// 可以被解構(gòu)賦值
// 語法更簡潔
};
場景4:需要原型繼承、方法共享:使用Object
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating`);
}
}
// 繼承和原型鏈是Object的強項
const dog = new Animal('Dog');
dog.eat();Set vs Array:集合操作的藝術(shù)
Set
Set的基本使用
使用 new 關(guān)鍵字和 Set 構(gòu)造函數(shù),可以創(chuàng)建一個空的 Set 集合:
const set = new Set();
當然,我們也可以在創(chuàng)建的時候,同時初始化實例,只需要給 Set 構(gòu)造函數(shù)傳入一個可迭代的對象即可:
const set = new Set([1, 2, 3]);
Set的核心特性
1. 自動去重與大小計算
set.add(1);
set.add(2);
set.add(3);
set.add(1); // 重復(fù),不會添加
set.add(2); // 重復(fù),不會添加
console.log('Set內(nèi)容:', set); // Set(3) {1, 2, 3}
console.log('Set大小:', set.size); // 32. 支持任意類型的值
set.add('string');
set.add({ name: 'obj' });
set.add([1, 2, 3]);
set.add(null);
set.add(undefined);
set.add(NaN);
set.add(NaN); // NaN在Set中也被視為相同值
3. 高效的成員檢查
console.log('是否包含1:', set.has(1)); // true
4. 集合運算:并集 & 交集 & 差集
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 并集
const union = new Set([...setA, ...setB]);
console.log('并集:', union); // Set(6) {1, 2, 3, 4, 5, 6}
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log('交集:', intersection); // Set(2) {3, 4}
// 差集(A - B)
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log('差集(A-B):', difference); // Set(2) {1, 2}與Array互轉(zhuǎn)
const set = new Set([1, 2, 3, 4]); const arrFromSet = Array.from(set); const setFromArr = new Set([1, 2, 2, 3, 3, 4]);
Array
Array的基本使用
在 JavaScript 中,創(chuàng)建 Array 的實例有兩種方式:一種是使用 new 關(guān)鍵字和 Array 構(gòu)造函數(shù);另一種是使用數(shù)組字面量表示法。
new關(guān)鍵字和Array構(gòu)造函數(shù)
使用 new 關(guān)鍵字和 Array 構(gòu)造函數(shù),可以創(chuàng)建一個空的 Array 數(shù)組:
const arr = new Array();
當然,我們也可以在創(chuàng)建的時候,同時初始化實例,只需要給 Set 構(gòu)造函數(shù)傳入一個可迭代的對象即可:
const arr = new Array(10 , 20, 30);
值得注意的是:當我們使用
new Array(10 , 20, 30);這種方式創(chuàng)建數(shù)組時,數(shù)組值為[10,20,30];但當參數(shù)只有一個時:new Array(10);這時候會創(chuàng)建一個長度為10的數(shù)組。
數(shù)組字面量表示法
const arr = [1, 2, 3];
Array的核心特性
1. 允許重復(fù)和直接計算大小
arr.push(1);
arr.push(2);
arr.push(3);
arr.push(1); // 允許重復(fù)
arr.push(2); // 允許重復(fù)
console.log('Array內(nèi)容:', arr); // [1, 2, 3, 1, 2]
console.log('Array長度:', arr.length); // 52. 豐富的數(shù)組方法 - 會改變原數(shù)組
填充fill()方法
const arr = new Array(5); arr.fill(5); // [5, 5, 5, 5, 5]
棧方法 push() 和 pop()
- push() 方法用于數(shù)組模擬在棧頂?shù)娜霔2僮?,它接收任意?shù)量的參數(shù),并將它們添加到數(shù)組末尾。
- pop() 方法用于數(shù)組模擬在棧頂?shù)某鰲2僮?,它不接收任何參?shù),用于刪除數(shù)組的最后一項。
const arr = [1, 2, 3, 4, 5]; arr.push(6, 7); // [1, 2, 3, 4, 5, 6, 7] arr.pop(); // [1, 2, 3, 4, 5, 6]
隊列方法 shift() 和 unshift()
- shift() 方法用于數(shù)組模式在隊列頭部出隊操作,它不接收任何參數(shù),用于刪除數(shù)組的第一項。
- unshift() 方法用于數(shù)組模擬在隊列頭部的入隊操作,它接收任意數(shù)量的參數(shù),并將它們添加到數(shù)組頭部。
const arr = [1, 2, 3, 4, 5]; arr.unshift()(6, 7); // [6, 7, 1, 2, 3, 4, 5] arr.shift(); // [7, 1, 2, 3, 4, 5]
排序方法 sort() 和 reverse()
- sort() 方法用于對數(shù)組進行升序排序
- reverse() 方法用于對數(shù)組進行降序排序
強大的splice()
splice() 方法應(yīng)該屬于數(shù)組中最強大的方法了,可以接受 2 個及 2 個以上的參數(shù),通常情況下,第 1 個參數(shù)表示要進行操作的開始位置,第 2 個參數(shù)表示要刪除的元素數(shù)量,第 3 個及以后得所有參數(shù),都表示為要新插入的元素。因此,該方法可以根據(jù)參數(shù)的不同,可以實現(xiàn)刪除、插入、替換等操作:
- 刪除:需要傳遞 2 個參數(shù),比如:
arr.splice(0, 2)會刪除前2個元素。 - 插入:需要傳遞 3 個或 3 個以上的參數(shù),且第二個參數(shù)一定為 0,比如:
arr.splice(2, 0, 1, 2, 3)會從第二個位置開始,往后插入1, 2, 3三個元素。 - 替換:同樣需要傳遞 3 個或 3 個以上的參數(shù),表示為從開始位置,刪除多少個元素后,再插入元素數(shù)據(jù)。
const arr = [1, 2, 3, 4, 5]; arr.splice(1, 2); // 從索引1開始刪除2個元素(2, 3),結(jié)果為:[1, 4, 5] arr.splice(2, 0, 'a', 'b'); // 從索引2開始插入'a'和'b',結(jié)果為:[1, 4, 'a', 'b', 5] arr.splice(1, 1, 'c'); // 從索引1開始刪除2個元素(4, 'a'),再插入'c',[1, 'c', 'b', 5]
注:以上所有方法均會改變原數(shù)組。
3. 豐富的數(shù)組方法 - 不會改變原數(shù)組
數(shù)組連接方法concat()
concat() 方法用于將兩個或多個數(shù)組相連接,返回一個新的數(shù)組:
const arr1 = [1, 2, 3]; const arr2 = [4, 5]; const arr3 = arr1.concat(arr2); //[1, 2, 3, 4, 5]
數(shù)組截取方法slice()
slice() 方法可以接收1個或2個參數(shù):當參數(shù)只有1個時,slice() 方法會返回從該索引到數(shù)組末尾的所有元素;當參數(shù)有2個時,slice() 方法會返回從開始索引(第1個參數(shù))到結(jié)束索引(第2個參數(shù))之間的所有元素(不包括結(jié)束索引的值):
const arr = [1, 2, 3, 4, 5]; console.log(arr.slice(3)); // [4, 5] console.log(arr.slice(0, 2)); // [0, 1]
嚴格相等查找indexOf()、lastIndexOf() 和 includes()
這三個方法都是用于在數(shù)組中查找元素的方法,它們都可以接收 1 個或 2 個參數(shù):第 1 個參數(shù)表示要查找的元素;第 2 個參數(shù)可選,表示要從哪個位置開始找(不填默認從位置 0 ,即全數(shù)組查找)。
- indexOf() 和 includes() 都是從數(shù)組開頭進行查找,而 lastIndexOf() 是從數(shù)組末尾開始查找。
- indexOf() 和 lastIndexOf() 會返回要查找的元素在數(shù)組中南的索引位置,不存在該元素則返回 -1 ;includes() 則是返回布爾值,表示是否找到匹配項。
const arr = [1, 2, 3, 4, 5, 1]; arr.indexOf(1); // 0 arr.lastIndexOf(1); // 5 arr.includes(1); // true arr.indexOf(6); // -1 arr.lastIndexOf(6); // -1 arr.includes(6); // false
注:以上三個方法,在進行數(shù)組匹配查找時,會使用全等(===)進行比較,也就是兩項必須嚴格相等,因此一般用于簡單基本數(shù)據(jù)類型的數(shù)組查找;對象數(shù)組通過以上方法無法查找(因為它們在查找比對時,對比的是引用地址)。
斷言查找find() 和 findIndex()
- find():查找并返回數(shù)組中第一個滿足條件的元素值,如果找不到則返回 undefined。
- findIndex():查找并返回數(shù)組中第一個滿足條件的元素值的索引地址,如果找不到則返回 -1 。
const arr = [
{
name: 'zhangsan',
age: 18
},
{
name: 'lisi',
age: 20
}
];
arr.find(item => item.age > 18); // { name: 'lisi', age: 20 }
arr.findIndex(item => item.age > 18); // 1迭代方法forEach()、map()、filter()、every()、some()
JavaScript 中,為數(shù)組定義了 5 個迭代方法:
- forEach():依次遍歷數(shù)組中的每一個元素,沒有返回值。
- map():將數(shù)組的每個元素按照一定規(guī)則轉(zhuǎn)換成新元素,返回一個新數(shù)組。
- filter():按一定規(guī)則過濾數(shù)組中的所有元素,返回滿足規(guī)則的元素。
- every():對數(shù)組中的每一個元素都進行函數(shù)處理判斷,如果數(shù)組中每一個元素的判斷值都為 true,則這個方法的返回結(jié)果是 true;否則返回 false。
- some():對數(shù)組中的每一個元素都進行函數(shù)處理判斷,只要數(shù)組中有一個元素的判斷值為 true,則這個方法的返回結(jié)果是 true;否則返回 false。
const arr = [1, 2, 3, 4, 5]; const mapRes = arr.map((num) => num * 2); // [2, 4, 6, 8, 10] const filtered = arr.filter((num) => num % 2 === 0); // [2, 4] const everyRes = arr.every((num) => num > 0); // true const someRes = arr.some((num) => num > 4); // true
注:以上方法本質(zhì)是不會更改原數(shù)組中,但如果在方法體內(nèi)部對原數(shù)組進行了更改,也是會對原數(shù)組產(chǎn)生影響的,看下面一段代碼示例:
const arr = [1, 2, 3, 4, 5];
array.forEach((item, index) => {
arr[index] = item + 1; // 開發(fā)中,要避免這種寫法
});
console.log(arr); // [2, 3, 4, 5, 6]
歸并方法reduce()
reduce() 方法會迭代數(shù)組的所有項,并在此基礎(chǔ)上構(gòu)建一個最終的返回值:
const arr = [1, 2, 3, 4, 5]; const sum = arr.reduce((acc, cur) => acc + cur, 0); // 15
何時選擇Set?何時選擇Array?
場景1:需要快速成員檢查:使用Set
class PermissionSystem {
constructor() {
// 用戶權(quán)限集合(快速查找)
this.userPermissions = new Set();
}
// 添加權(quán)限
grantPermission(permission) {
this.userPermissions.add(permission);
}
// 檢查權(quán)限 - Set O(1) 時間復(fù)雜度
hasPermission(permission) {
return this.userPermissions.has(permission);
}
// 批量檢查權(quán)限 - O(n) 時間復(fù)雜度
hasAllPermissions(requiredPermissions) {
return requiredPermissions.every(permission =>
this.userPermissions.has(permission)
);
}
}場景2:需要去重:使用Set
const duplicateIds = [1, 2, 2, 3, 4, 4, 4, 5, 5, 5, 5]; // 使用 Set 去重(一行代碼) const uniqueIds = [...new Set(duplicateIds)]; // [1, 2, 3, 4, 5]
場景3:數(shù)學(xué)集合運算:使用Set
class SocialNetwork {
constructor() {
this.users = new Map();
}
// 并集:共同好友 + 各自的好友
getUnionOfFriends(userId1, userId2) {
const friends1 = this.users.get(userId1) || new Set();
const friends2 = this.users.get(userId2) || new Set();
// 并集運算
const union = new Set([...friends1, ...friends2]);
return [...union];
}
// 交集:共同好友
getIntersectionOfFriends(userId1, userId2) {
const friends1 = this.users.get(userId1) || new Set();
const friends2 = this.users.get(userId2) || new Set();
// 交集運算
const intersection = new Set(
[...friends1].filter(friend => friends2.has(friend))
);
return [...intersection];
}
// 差集:A有但B沒有的好友
getDifferenceOfFriends(userId1, userId2) {
const friends1 = this.users.get(userId1) || new Set();
const friends2 = this.users.get(userId2) || new Set();
// 差集運算
const difference = new Set(
[...friends1].filter(friend => !friends2.has(friend))
);
return [...difference];
}
}場景4:有序數(shù)據(jù)、需要索引訪問、豐富數(shù)組操作:使用Array
class ShoppingCart {
constructor() {
this.items = []; // 使用 Array:需要保持順序、索引訪問、各種數(shù)組方法
}
// 添加商品到購物車(保持添加順序)
addItem(product, quantity = 1) {
const existingItemIndex = this.items.findIndex(
item => item.id === product.id
);
if (existingItemIndex !== -1) {
// 更新已有商品數(shù)量
this.items[existingItemIndex].quantity += quantity;
} else {
// 添加新商品
this.items.push({
...product,
quantity,
addedAt: new Date() // 記錄添加時間
});
}
}
// 按索引移除商品
removeItem(index) {
if (index >= 0 && index < this.items.length) {
return this.items.splice(index, 1)[0];
}
return null;
}
// 按商品ID移除
removeItemById(productId) {
const index = this.items.findIndex(item => item.id === productId);
if (index !== -1) {
return this.removeItem(index);
}
return null;
}
// 篩選:按價格范圍
filterByPriceRange(min, max) {
return this.items.filter(item =>
item.price >= min && item.price <= max
);
}
// 分頁獲取商品
getItemsPaginated(page = 1, pageSize = 5) {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
return this.items.slice(startIndex, endIndex);
}
}WeakMap與WeakSet:弱引用的藝術(shù)
WeakMap:弱引用的鍵值對
WeakMap 是 Map 的“兄弟”類型,其 API 也是 Map 的子集。 WeakMap 中的 “weak” 描述的是JavaScript 垃圾回收機制中對待“弱映射”中鍵的方式。
const weakMap = new WeakMap();
WeakMap的基本特性
1. 鍵必須是對象(不能是原始值)
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 3 };
weakMap.set(obj1, 'value1');
weakMap.set(obj2, 'value2');
weakMap.set(obj3, 'value3');2. 不能計算大小
console.log('設(shè)置后weakMap:', weakMap); // <items unknown>
console.log(weakMap.size); // undefined 無法獲取size
3. 鍵是弱引用(不會阻止垃圾回收)
{
let tempObj = { id: 'temp' };
weakMap.set(tempObj, 'temp value');
console.log('臨時對象設(shè)置后:', weakMap.has(tempObj)); // true
}
// tempObj離開作用域,可以被垃圾回收
// weakMap中的對應(yīng)條目會自動被移除4. 沒有遍歷方法(不能迭代)
for (const [key, value] of weakMap) {} // TypeError
console.log(weakMap.keys()); // TypeError
WeakSet:弱引用的集合
WeakSet 是 Set 的“兄弟”類型,其 API 也是 Set 的子集。 WeakSet 中的 “weak” 描述的是JavaScript 垃圾回收機制中對待“弱集合”中鍵的方式。
const weakSet = new WeakSet();
WeakSet的基本特性
1. 值必須是對象
const objA = { name: 'A' };
const objB = { name: 'B' };
const objC = { name: 'C' };
weakSet.add(objA);
weakSet.add(objB);
weakSet.add(objC);2. 弱引用特性(不會阻止垃圾回收)
{
let tempObj = { name: 'temp' };
weakSet.add(tempObj);
console.log('臨時對象添加后:', weakSet.has(tempObj)); // true
}
// tempObj離開作用域,可以被垃圾回收
3. 沒有遍歷方法(不能迭代)
結(jié)語
沒有"最好"的數(shù)據(jù)結(jié)構(gòu),只有"最適合"的數(shù)據(jù)結(jié)構(gòu)。理解每種結(jié)構(gòu)的特點和適用場景,根據(jù)具體需求做出明智選擇!對于文章中錯誤的地方或者有任何問題,歡迎在評論區(qū)留言討論!
到此這篇關(guān)于JavaScript內(nèi)置數(shù)據(jù)結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)js內(nèi)置數(shù)據(jù)結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript中的Set與Map數(shù)據(jù)結(jié)構(gòu)深入對比分析(核心特征)
- js數(shù)據(jù)結(jié)構(gòu)排序示例教程(全網(wǎng)精簡版)
- PostgreSQL使用JSONB存儲和查詢復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
- JS實現(xiàn)鏈表數(shù)據(jù)結(jié)構(gòu)的代碼詳解
- vue3中處理不同數(shù)據(jù)結(jié)構(gòu)JSON的實現(xiàn)
- JavaScript中Set和Map數(shù)據(jù)結(jié)構(gòu)使用場景詳解
- JavaScript Set與Map數(shù)據(jù)結(jié)構(gòu)詳細分析
- JS數(shù)據(jù)結(jié)構(gòu)之隊列結(jié)構(gòu)詳解
- JavaScript數(shù)據(jù)結(jié)構(gòu)之鏈表各種操作詳解
相關(guān)文章
javaScript 關(guān)閉瀏覽器 (不彈出提示框)
如果網(wǎng)頁不是通過腳本程序打開的(window.open()),調(diào)用window.close()腳本關(guān)閉窗口前,必須先將window.opener對象置為null,否則瀏覽器(IE7、IE8)會彈出一個確定關(guān)閉的對話框。2010-01-01

