JavaScript封裝LINQ查詢方法實戰(zhàn)
JavaScript中通過“js-linq”庫實現(xiàn)了類似.NET中LINQ的數(shù)據(jù)查詢功能,提供如Where、Select、OrderBy等常用操作,支持前端對數(shù)組或可迭代對象進行高效數(shù)據(jù)處理。壓縮包中包含核心庫文件、壓縮版本、文檔和示例代碼,適用于數(shù)據(jù)過濾、轉(zhuǎn)換、排序與分組等場景。本資源幫助開發(fā)者提升JavaScript項目的數(shù)據(jù)操作能力與代碼可讀性。

1. LINQ查詢簡介與JavaScript實現(xiàn)原理
LINQ(Language Integrated Query)是一種將查詢能力直接集成到編程語言中的機制,最初由C#引入,廣泛應(yīng)用于.NET平臺的數(shù)據(jù)處理中。它提供了一種統(tǒng)一的語法來查詢和操作數(shù)組、集合、XML、數(shù)據(jù)庫等多種數(shù)據(jù)源,極大提升了代碼的可讀性和開發(fā)效率。
雖然JavaScript語言本身并未原生支持LINQ,但其函數(shù)式編程特性(如 map 、 filter 、 reduce 等方法)以及原型鏈機制,為模擬LINQ風格的鏈式查詢提供了良好基礎(chǔ)。
通過封裝類似LINQ的查詢方法,開發(fā)者可以在JavaScript中實現(xiàn)優(yōu)雅的數(shù)據(jù)操作流程,例如:
const result = Enumerable.From([1, 2, 3, 4])
.Where(x => x % 2 === 0)
.Select(x => x * 2)
.ToArray();
上述代碼模擬了LINQ的鏈式調(diào)用風格,展示了如何通過封裝實現(xiàn)類似 .Where() 和 .Select() 的功能。這種設(shè)計不僅提升了代碼的可讀性,也增強了數(shù)據(jù)處理的邏輯抽象能力。在后續(xù)章節(jié)中,我們將逐步剖析如何封裝和實現(xiàn)這些核心方法。
2. Enumerable.From方法封裝與實現(xiàn)
在構(gòu)建LINQ風格的JavaScript查詢庫時,第一步就是將原始數(shù)據(jù)封裝成一個可查詢的對象。這正是 Enumerable.From 方法的核心作用。通過該方法,開發(fā)者可以將任意類型的數(shù)據(jù)源(如數(shù)組、對象、字符串等)轉(zhuǎn)換為統(tǒng)一的可迭代對象,從而為后續(xù)的查詢操作(如 .Where() 、 .Select() 、 .OrderBy() 等)提供一致的接口。
2.1 Enumerable.From的基本作用
2.1.1 從數(shù)組、對象、字符串等數(shù)據(jù)源創(chuàng)建可查詢對象
在JavaScript中,不同數(shù)據(jù)結(jié)構(gòu)具有不同的訪問方式。例如:
- 數(shù)組可以通過索引遍歷;
- 對象需要通過
for...in或Object.keys()遍歷; - 字符串可以被視為字符數(shù)組處理;
- 類數(shù)組對象(如
arguments、NodeList)則需特殊處理。
為了統(tǒng)一這些數(shù)據(jù)結(jié)構(gòu)的訪問方式, Enumerable.From 方法需要能夠識別這些輸入類型,并將其封裝為統(tǒng)一的可查詢對象。這個對象通常包含一個迭代器(iterator),允許使用統(tǒng)一的方式進行遍歷和鏈式操作。
const query = Enumerable.From([1, 2, 3, 4, 5]);
此時, query 是一個可鏈式調(diào)用的 LINQ 風格對象,后續(xù)可以調(diào)用 .Where() 、 .Select() 等方法。
2.1.2 數(shù)據(jù)源的類型識別與適配處理
為了處理不同類型的數(shù)據(jù)源, Enumerable.From 需要具備類型識別能力。常見的數(shù)據(jù)源類型包括:
| 數(shù)據(jù)源類型 | 示例 | 適配方式說明 |
|---|---|---|
| 數(shù)組 | [1,2,3] | 直接使用 for 循環(huán)遍歷 |
| 類數(shù)組對象 | document.querySelectorAll() | 轉(zhuǎn)換為數(shù)組處理 |
| 對象 | {a:1, b:2} | 遍歷鍵值對 |
| 字符串 | "hello" | 拆分為字符數(shù)組 |
| 可迭代對象(ES6) | Map , Set | 使用內(nèi)置 Symbol.iterator |
我們可以通過 typeof 和 Object.prototype.toString.call() 來識別不同類型的輸入。
示例代碼:類型識別函數(shù)
function getType(source) {
if (Array.isArray(source)) return 'array';
if (typeof source === 'string') return 'string';
if (source && typeof source === 'object') {
if (typeof source[Symbol.iterator] === 'function') {
return 'iterable';
}
return 'object';
}
return 'unknown';
}
代碼邏輯分析:
- Array.isArray(source) 判斷是否為數(shù)組;
- typeof source === 'string' 判斷是否為字符串;
- source && typeof source === 'object' 判斷是否為對象;
- Symbol.iterator 存在則為可迭代對象;
- 否則為未知類型。
代碼參數(shù)說明:
source:傳入的數(shù)據(jù)源,可以是任意類型;- 返回值為字符串,表示類型名稱,如
'array'、'object'、'string'等。
2.2 Enumerable.From的實現(xiàn)思路
2.2.1 使用工廠函數(shù)封裝初始化邏輯
為了將 Enumerable.From 的邏輯模塊化并提升可擴展性,我們可以使用工廠函數(shù)來封裝初始化邏輯。這樣可以將不同的數(shù)據(jù)源處理邏輯封裝到不同的處理函數(shù)中,便于后續(xù)維護和擴展。
示例代碼:工廠函數(shù)實現(xiàn)
function Enumerable() {}
Enumerable.From = function(source) {
const type = getType(source);
let iterator;
switch (type) {
case 'array':
iterator = arrayIterator(source);
break;
case 'string':
iterator = stringIterator(source);
break;
case 'object':
iterator = objectIterator(source);
break;
case 'iterable':
iterator = iterableIterator(source);
break;
default:
throw new Error('Unsupported data source type');
}
return new Queryable(iterator);
};
代碼邏輯分析:
getType(source):識別數(shù)據(jù)源類型;- 根據(jù)類型選擇對應(yīng)的迭代器生成函數(shù);
- 構(gòu)造
Queryable實例并返回。
參數(shù)說明:
source:任意類型的數(shù)據(jù)源;- 返回值為一個
Queryable實例,用于后續(xù)鏈式調(diào)用。
可擴展性說明:
- 當需要支持新的數(shù)據(jù)源類型時,只需添加新的 case 分支和對應(yīng)的迭代器函數(shù);
- 每個迭代器函數(shù)負責將數(shù)據(jù)源轉(zhuǎn)換為統(tǒng)一的迭代器接口。
2.2.2 借助迭代器模式統(tǒng)一數(shù)據(jù)訪問接口
迭代器模式是一種設(shè)計模式,用于提供統(tǒng)一的方式來訪問聚合對象中的各個元素。JavaScript 中的 Symbol.iterator 已經(jīng)提供了這種能力,但我們可以通過自定義迭代器來增強功能,例如支持延遲執(zhí)行、鏈式調(diào)用等。
示例代碼:自定義迭代器
function arrayIterator(arr) {
let index = 0;
return {
next: () => {
if (index < arr.length) {
return { value: arr[index++], done: false };
} else {
return { done: true };
}
},
[Symbol.iterator]: function () { return this; }
};
}
代碼邏輯分析:
- index :記錄當前迭代位置;
- next() :返回下一個元素;
- done :表示是否迭代完成;
- [Symbol.iterator] :保證該迭代器本身也是可迭代的,支持 for...of 等語法。
參數(shù)說明:
arr:傳入的數(shù)組數(shù)據(jù)源;- 返回值為一個符合迭代器協(xié)議的對象。
示例調(diào)用:
const iterator = arrayIterator([1, 2, 3]);
for (const item of iterator) {
console.log(item); // 輸出 1, 2, 3
}
2.3 Enumerable.From的測試與驗證
2.3.1 不同類型數(shù)據(jù)源的封裝效果驗證
為了確保 Enumerable.From 能正確處理各種數(shù)據(jù)源,我們需要編寫測試用例來驗證其行為。
示例測試代碼:
function testFrom() {
const arr = [1, 2, 3];
const str = "hello";
const obj = { a: 1, b: 2 };
const map = new Map([['a', 1], ['b', 2]]);
const q1 = Enumerable.From(arr);
const q2 = Enumerable.From(str);
const q3 = Enumerable.From(obj);
const q4 = Enumerable.From(map);
// 模擬執(zhí)行查詢
console.log([...q1]); // [1,2,3]
console.log([...q2]); // ['h','e','l','l','o']
console.log([...q3]); // [{key: 'a', value:1}, {key: 'b', value:2}]
console.log([...q4]); // [['a',1], ['b',2]]
}
代碼邏輯分析:
- 構(gòu)造不同類型的輸入;
- 調(diào)用
Enumerable.From(); - 使用擴展運算符
...獲取結(jié)果; - 驗證輸出是否符合預(yù)期。
測試結(jié)果說明:
q1:數(shù)組轉(zhuǎn)換為可迭代對象;q2:字符串轉(zhuǎn)換為字符數(shù)組;q3:對象轉(zhuǎn)換為鍵值對數(shù)組;q4:Map 轉(zhuǎn)換為鍵值對數(shù)組。
2.3.2 異常輸入處理與容錯機制設(shè)計
除了處理正常輸入外, Enumerable.From 還應(yīng)具備處理異常輸入的能力,例如:
null或undefined- 非對象類型如
number、boolean - 無法識別的自定義類型
示例代碼:容錯處理
function getType(source) {
if (source === null || source === undefined) {
return 'null';
}
if (Array.isArray(source)) return 'array';
if (typeof source === 'string') return 'string';
if (source && typeof source === 'object') {
if (typeof source[Symbol.iterator] === 'function') {
return 'iterable';
}
return 'object';
}
return 'unknown';
}
異常處理邏輯:
switch (type) {
case 'array':
case 'string':
case 'object':
case 'iterable':
// 正常處理
break;
case 'null':
throw new Error('Cannot create Enumerable from null or undefined');
default:
throw new Error(`Unsupported data type: ${typeof source}`);
}
異常處理流程圖(Mermaid):
graph TD
A[開始] --> B{輸入是否為 null/undefined?}
B -- 是 --> C[拋出錯誤]
B -- 否 --> D{是否為數(shù)組/字符串/對象/可迭代對象?}
D -- 是 --> E[創(chuàng)建迭代器]
D -- 否 --> F[拋出不支持類型錯誤]
代碼邏輯說明:
- 先判斷是否為 null 或 undefined;
- 再判斷是否為支持的類型;
- 否則拋出錯誤。
通過上述章節(jié)內(nèi)容的詳細分析與代碼實現(xiàn),我們可以清晰地理解 Enumerable.From 方法的設(shè)計與實現(xiàn)過程。它不僅實現(xiàn)了對多種數(shù)據(jù)源的支持,還通過迭代器模式和工廠函數(shù)封裝,提升了代碼的可讀性與可維護性。下一章將深入講解 .Where() 方法的實現(xiàn)與優(yōu)化策略。
3. Enumerable.Where方法封裝與實現(xiàn)
Enumerable.Where 是 LINQ 風格查詢中最重要的操作之一,它用于對集合中的元素進行條件篩選,返回滿足條件的子集。在 JavaScript 中模擬實現(xiàn)這一功能不僅可以提升代碼的可讀性和表達力,還能增強函數(shù)式編程的體驗。本章將從 Where 方法的核心功能出發(fā),逐步深入其實現(xiàn)細節(jié),并探討其優(yōu)化與擴展方向。
3.1 Where方法的核心功能與應(yīng)用場景
3.1.1 條件篩選的基本原理
Where 方法本質(zhì)上是一個高階函數(shù),它接收一個 謂詞函數(shù)(predicate) ,該函數(shù)用于判斷集合中的每個元素是否符合條件。在 JavaScript 中,數(shù)組的 filter 方法已經(jīng)具備類似功能,但通過封裝 Enumerable.Where 可以實現(xiàn)更靈活、可鏈式調(diào)用的查詢結(jié)構(gòu)。
示例:JavaScript 原生 filter 的使用
const numbers = [1, 2, 3, 4, 5]; const evenNumbers = numbers.filter(n => n % 2 === 0); console.log(evenNumbers); // [2, 4]
邏輯分析:
numbers.filter(...):調(diào)用數(shù)組的filter方法。n => n % 2 === 0:這是一個謂詞函數(shù),判斷數(shù)值是否為偶數(shù)。- 返回一個新數(shù)組,僅包含滿足條件的元素。
雖然 filter 功能強大,但它不具備鏈式調(diào)用的結(jié)構(gòu)。而我們希望構(gòu)建的 Enumerable.Where 能夠作為整個查詢鏈的一部分,支持如 .Where(...).Select(...).ToArray() 這樣的操作。
3.1.2 在數(shù)據(jù)過濾、權(quán)限控制中的實際應(yīng)用
Where 方法在前端開發(fā)中用途廣泛,特別是在以下場景中:
| 應(yīng)用場景 | 示例說明 |
|---|---|
| 數(shù)據(jù)過濾 | 對用戶列表按角色、狀態(tài)等字段進行篩選 |
| 權(quán)限控制 | 根據(jù)用戶權(quán)限過濾菜單項或操作按鈕 |
| 日志處理 | 篩選特定類型的日志條目 |
| 表單驗證 | 過濾未通過校驗的表單項 |
示例:用戶權(quán)限過濾菜單項
const menus = [
{ name: "Dashboard", role: "admin" },
{ name: "Profile", role: "user" },
{ name: "Settings", role: "admin" },
];
const userRole = "user";
const filteredMenus = menus.filter(menu => menu.role === userRole);
console.log(filteredMenus); // [ { name: "Profile", role: "user" } ]
這個例子展示了 Where 的典型用途:根據(jù)用戶角色過濾菜單。若將此封裝為 Enumerable.Where ,可以更優(yōu)雅地嵌入整個查詢鏈。
3.2 Where方法的實現(xiàn)細節(jié)
3.2.1 接收謂詞函數(shù)并執(zhí)行過濾邏輯
在實現(xiàn) Enumerable.Where 之前,我們需要一個基礎(chǔ)類 Enumerable ,并確保它支持鏈式調(diào)用。下面是一個簡化版的實現(xiàn):
基礎(chǔ) Enumerable 類定義
class Enumerable {
constructor(source) {
this.source = source;
}
toArray() {
return Array.from(this.source);
}
}
Where 方法實現(xiàn)
Enumerable.prototype.Where = function(predicate) {
const filtered = this.source.filter(predicate);
return new Enumerable(filtered);
};
使用示例
const numbers = new Enumerable([1, 2, 3, 4, 5]); const result = numbers .Where(n => n > 2) .Where(n => n < 5) .toArray(); console.log(result); // [3, 4]
邏輯分析:
Enumerable.prototype.Where:擴展Enumerable實例的方法。this.source.filter(predicate):對內(nèi)部數(shù)據(jù)源進行過濾。return new Enumerable(filtered):返回新的Enumerable實例,支持鏈式調(diào)用。
3.2.2 支持多條件鏈式過濾的設(shè)計
鏈式調(diào)用是 LINQ 風格查詢的核心特性之一。通過返回新的 Enumerable 實例,可以不斷追加 Where 條件或其他操作,形成一個清晰的查詢流程。
示例:多條件鏈式過濾
const users = new Enumerable([
{ name: "Alice", age: 25, role: "admin" },
{ name: "Bob", age: 30, role: "user" },
{ name: "Charlie", age: 22, role: "user" },
]);
const result = users
.Where(u => u.age > 20)
.Where(u => u.role === "user")
.toArray();
console.log(result);
// [
// { name: "Bob", age: 30, role: "user" },
// { name: "Charlie", age: 22, role: "user" }
// ]
優(yōu)化建議:
- 可以在
Where中添加參數(shù)校驗,防止無效謂詞。 - 支持傳入多個謂詞,自動組合為 AND 條件。
3.3 Where方法的優(yōu)化與擴展
3.3.1 異步過濾邏輯的引入
在處理異步數(shù)據(jù)源(如 API 請求返回的數(shù)組)時,我們可能需要在 Where 中引入異步邏輯。JavaScript 支持 Promise 和 async/await ,我們可以通過異步謂詞函數(shù)實現(xiàn)這一點。
示例:異步 Where 方法
Enumerable.prototype.WhereAsync = async function(predicate) {
const filtered = await Promise.all(
this.source.map(async item => await predicate(item))
);
const result = this.source.filter((_, index) => filtered[index]);
return new Enumerable(result);
};
使用示例:
const data = new Enumerable([1, 2, 3, 4, 5]);
const result = await data
.WhereAsync(async n => {
await new Promise(r => setTimeout(r, 100)); // 模擬異步延遲
return n % 2 === 0;
})
.toArray();
console.log(result); // [2, 4]
邏輯分析:
WhereAsync方法接收一個異步謂詞函數(shù)。- 使用
Promise.all處理每個元素的異步判斷。 - 最終根據(jù)判斷結(jié)果過濾原始數(shù)組。
3.3.2 支持對象屬性路徑(dot路徑)的過濾
在處理對象數(shù)組時,有時需要根據(jù)對象的嵌套屬性進行過濾。例如,過濾 user.address.city === 'Beijing' 。我們可以封裝一個輔助函數(shù)來解析屬性路徑。
示例:dot路徑解析函數(shù)
function getPropertyValue(obj, path) {
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}
擴展 Where 方法支持 dot 路徑
Enumerable.prototype.WhereByPath = function(path, value) {
const filtered = this.source.filter(item => getPropertyValue(item, path) === value);
return new Enumerable(filtered);
};
使用示例:
const data = new Enumerable([
{ name: "Alice", address: { city: "Beijing" } },
{ name: "Bob", address: { city: "Shanghai" } },
]);
const result = data.WhereByPath("address.city", "Beijing").toArray();
console.log(result); // [ { name: "Alice", ... } ]
邏輯分析:
getPropertyValue:根據(jù)路徑字符串獲取對象屬性值。WhereByPath:接受路徑和目標值,進行屬性匹配。- 支持嵌套對象的深層查詢。
3.3.3 性能優(yōu)化與鏈式調(diào)用效率
為了提升 Where 方法的性能,我們可以:
- 避免不必要的對象創(chuàng)建 :在鏈式調(diào)用中緩存中間結(jié)果。
- 延遲執(zhí)行機制 :類似于 LINQ 的 deferred execution,只有在調(diào)用
toArray()時才真正執(zhí)行過濾。 - 減少嵌套函數(shù)調(diào)用層級 :合并多個
Where條件為一個謂詞函數(shù)。
示例:合并多個 Where 條件
Enumerable.prototype.And = function(predicate) {
const combined = item => this.predicate(item) && predicate(item);
return new Enumerable(this.source.filter(combined));
};
總結(jié)
Enumerable.Where 是構(gòu)建 LINQ 風格查詢的核心方法之一。通過封裝謂詞函數(shù)、支持鏈式調(diào)用、引入異步邏輯、處理嵌套屬性等方式,我們可以在 JavaScript 中實現(xiàn)一個強大而靈活的查詢接口。本章通過代碼示例詳細講解了其實現(xiàn)邏輯與優(yōu)化方向,為后續(xù)章節(jié)中 Select 、 OrderBy 等方法的封裝打下了基礎(chǔ)。
下一章將探討
Enumerable.Select方法的封裝與實現(xiàn),繼續(xù)構(gòu)建完整的 LINQ 查詢鏈。
4. Enumerable.Select方法封裝與實現(xiàn)
Select 方法是 LINQ 中最常用的操作之一,它用于對集合中的每個元素進行投影轉(zhuǎn)換,從而生成一個新的集合。在 JavaScript 中,雖然沒有原生的 LINQ 支持,但通過數(shù)組的 map 方法,我們可以實現(xiàn)類似的功能。然而,為了構(gòu)建一個完整的 LINQ 風格查詢庫,我們需要對 Select 方法進行封裝,使其支持鏈式調(diào)用、嵌套映射、類型安全處理等功能。本章將從基礎(chǔ)概念出發(fā),逐步深入到實現(xiàn)細節(jié),并最終探討其與其他查詢操作的協(xié)同機制。
4.1 Select方法的作用與數(shù)據(jù)投影
4.1.1 數(shù)據(jù)映射的基本概念
Select 方法本質(zhì)上是一種投影操作,它允許我們對集合中的每個元素應(yīng)用一個轉(zhuǎn)換函數(shù),從而將原始數(shù)據(jù)映射為新的結(jié)構(gòu)或類型。例如,可以將一個包含對象的數(shù)組映射為僅包含某些屬性的數(shù)組,或者將數(shù)字數(shù)組映射為字符串數(shù)組。
在 LINQ 的設(shè)計哲學中, Select 是一種惰性求值操作,這意味著它不會立即執(zhí)行,而是等到最終需要結(jié)果時才進行處理。這種設(shè)計在 JavaScript 中雖然不完全適用(因為 JavaScript 是單線程語言),但我們?nèi)匀豢梢酝ㄟ^返回一個封裝了映射函數(shù)的對象,來模擬這種行為。
4.1.2 投影到新對象、數(shù)組或特定結(jié)構(gòu)
在實際開發(fā)中, Select 的應(yīng)用場景非常廣泛。例如:
- 從對象數(shù)組中提取特定屬性 :如從用戶對象數(shù)組中提取所有用戶的姓名。
- 轉(zhuǎn)換數(shù)據(jù)格式 :如將數(shù)字轉(zhuǎn)換為百分比字符串。
- 創(chuàng)建新的復(fù)合結(jié)構(gòu) :如將多個字段組合成一個新的對象結(jié)構(gòu)。
下面是一個簡單的 JavaScript 示例,演示了 Select 的基本用途:
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
];
// 使用 map 實現(xiàn) Select 功能
const names = users.map(user => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]
在這個例子中,我們使用了數(shù)組的 map 方法來實現(xiàn) Select 的功能,即將每個用戶對象映射為對應(yīng)的 name 字段。
參數(shù)說明 :
- user : 當前迭代的數(shù)組元素。
- user.name : 從對象中提取 name 屬性。
4.2 Select方法的實現(xiàn)方式
4.2.1 接收選擇函數(shù)并轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu)
為了實現(xiàn)一個完整的 LINQ 風格的 Select 方法,我們需要封裝一個類或函數(shù),使其支持鏈式調(diào)用,并接受一個映射函數(shù)作為參數(shù)。
下面是一個簡化的 Enumerable.Select 方法實現(xiàn):
class Enumerable {
constructor(source) {
this.source = source;
}
select(selector) {
const result = this.source.map(item => selector(item));
return new Enumerable(result);
}
toArray() {
return this.source;
}
}
// 工廠函數(shù)
function from(source) {
return new Enumerable(source);
}
// 使用示例
const numbers = [1, 2, 3, 4, 5];
const squared = from(numbers)
.select(n => n * n)
.toArray();
console.log(squared); // [1, 4, 9, 16, 25]
代碼邏輯分析 :
- Enumerable 類封裝了一個數(shù)據(jù)源(數(shù)組)。
- select(selector) 方法接收一個映射函數(shù) selector ,并使用 map 對數(shù)據(jù)源進行轉(zhuǎn)換。
- 返回一個新的 Enumerable 實例,以支持鏈式調(diào)用。
- toArray() 方法用于獲取最終結(jié)果。參數(shù)說明 :
- source : 原始數(shù)據(jù)源,通常是數(shù)組。
- selector : 用于映射的函數(shù),接受一個元素作為參數(shù)并返回轉(zhuǎn)換后的值。
4.2.2 支持嵌套數(shù)據(jù)的映射處理
在現(xiàn)實場景中,數(shù)據(jù)往往具有嵌套結(jié)構(gòu)。例如,一個用戶可能包含地址信息,而地址又包含城市、省份等字段。我們需要確保 Select 能夠處理這種嵌套結(jié)構(gòu),并正確地進行投影。
下面是一個處理嵌套結(jié)構(gòu)的示例:
const users = [
{
name: 'Alice',
address: { city: 'Shanghai', country: 'China' }
},
{
name: 'Bob',
address: { city: 'Beijing', country: 'China' }
}
];
const result = from(users)
.select(user => ({
name: user.name,
city: user.address.city
}))
.toArray();
console.log(result);
// 輸出:
// [
// { name: 'Alice', city: 'Shanghai' },
// { name: 'Bob', city: 'Beijing' }
// ]
參數(shù)說明 :
- user.address.city : 訪問嵌套對象的屬性。
- 匿名對象 { name: ..., city: ... } :用于創(chuàng)建新的投影結(jié)構(gòu)。邏輯分析 :
- 使用 select 方法將每個用戶對象映射為一個新的對象,只保留 name 和 city 。
- 返回的新結(jié)構(gòu)更適合后續(xù)的展示或處理。
支持深度嵌套的映射策略
為了更通用地處理嵌套路徑,我們可以引入一個輔助函數(shù),允許通過字符串路徑訪問對象屬性:
function getProperty(obj, path) {
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}
// 修改 select 方法
select(selectorOrPath) {
const result = typeof selectorOrPath === 'function'
? this.source.map(item => selectorOrPath(item))
: this.source.map(item => getProperty(item, selectorOrPath));
return new Enumerable(result);
}
這樣,我們可以傳入字符串路徑來訪問嵌套屬性:
const cities = from(users)
.select('address.city')
.toArray();
console.log(cities); // ['Shanghai', 'Beijing']
參數(shù)說明 :
- 'address.city' : 表示對象屬性的點路徑。
- getProperty(obj, path) : 遞歸訪問對象屬性。
4.3 Select與其他操作的協(xié)同
4.3.1 與Where、OrderBy等方法的鏈式組合
在實際開發(fā)中, Select 通常不會單獨使用,而是與其他查詢操作(如 Where 、 OrderBy )結(jié)合使用,形成一個完整的查詢流程。例如,先篩選滿足條件的用戶,再對其進行投影。
下面是一個鏈式調(diào)用的完整示例:
class Enumerable {
constructor(source) {
this.source = source;
}
where(predicate) {
const result = this.source.filter(item => predicate(item));
return new Enumerable(result);
}
select(selector) {
const result = this.source.map(item => selector(item));
return new Enumerable(result);
}
orderBy(keySelector) {
const result = [...this.source].sort((a, b) => {
const keyA = keySelector(a);
const keyB = keySelector(b);
return keyA > keyB ? 1 : -1;
});
return new Enumerable(result);
}
toArray() {
return this.source;
}
}
// 使用示例
const filteredUsers = from(users)
.where(user => user.age > 28)
.select(user => ({
name: user.name,
city: user.address.city
}))
.orderBy(user => user.name)
.toArray();
console.log(filteredUsers);
流程圖展示 (mermaid 格式):
graph TD A[原始數(shù)據(jù)] --> B[Where過濾] B --> C[Select投影] C --> D[OrderBy排序] D --> E[最終結(jié)果]
邏輯分析 :
- where :過濾年齡大于 28 的用戶。
- select :映射出用戶的 name 和 city 。
- orderBy :按 name 排序。
- toArray :獲取最終結(jié)果。
4.3.2 高階函數(shù)與閉包的結(jié)合應(yīng)用
JavaScript 的函數(shù)式特性使得 Select 可以與閉包、高階函數(shù)結(jié)合,實現(xiàn)更靈活的數(shù)據(jù)處理邏輯。
例如,我們可以定義一個返回函數(shù)的函數(shù),用于動態(tài)生成映射邏輯:
function createMapper(keys) {
return item => {
const result = {};
keys.forEach(key => {
result[key] = item[key];
});
return result;
};
}
const userMapper = createMapper(['name', 'age']);
const mapped = from(users)
.select(userMapper)
.toArray();
console.log(mapped);
參數(shù)說明 :
- keys : 需要映射的字段列表。
- createMapper(keys) : 返回一個映射函數(shù),用于提取指定字段。邏輯分析 :
- 利用閉包, createMapper 可以根據(jù)傳入的字段動態(tài)生成映射邏輯。
- 這種方式非常適合構(gòu)建可配置的投影操作。
綜上所述, Select 方法不僅是數(shù)據(jù)轉(zhuǎn)換的核心工具,更是構(gòu)建復(fù)雜查詢邏輯的重要組成部分。通過合理封裝,我們可以實現(xiàn)一個靈活、可擴展的 LINQ 風格查詢接口,使其在 JavaScript 中具備強大的數(shù)據(jù)處理能力。
5. Enumerable.OrderBy方法封裝與實現(xiàn)
在數(shù)據(jù)處理中,排序是一項基礎(chǔ)且常用的操作。 Enumerable.OrderBy 方法允許開發(fā)者根據(jù)指定的鍵或條件對集合中的元素進行排序。在 LINQ 的設(shè)計哲學中,排序不僅限于簡單的升序或降序排列,還支持多字段排序、自定義比較器以及嵌套結(jié)構(gòu)的排序邏輯。本章將從排序的基本原理出發(fā),深入探討 OrderBy 的實現(xiàn)機制、封裝策略及其性能優(yōu)化方式。
5.1 OrderBy方法的排序原理
排序操作在編程中幾乎無處不在,而 OrderBy 方法正是實現(xiàn)這一功能的核心手段之一。理解其背后的原理,有助于我們更好地設(shè)計和使用排序邏輯。
5.1.1 升序與降序排序的實現(xiàn)機制
JavaScript 中的數(shù)組排序默認是升序的,其內(nèi)部調(diào)用 Array.prototype.sort() 方法,該方法接受一個比較函數(shù)作為參數(shù)。比較函數(shù)的返回值決定了排序順序:
- 若返回值 < 0,則
a排在b前面; - 若返回值 > 0,則
b排在a前面; - 若返回值 == 0,則保持原順序。
[3, 1, 2].sort((a, b) => a - b); // 升序:[1, 2, 3] [3, 1, 2].sort((a, b) => b - a); // 降序:[3, 2, 1]
代碼邏輯分析:
a - b表示升序,因為當a < b時返回負數(shù),a被排在前面;b - a表示降序,當b > a時返回正值,b被排在前面。
在封裝 OrderBy 方法時,我們需要允許用戶指定排序方向(ascending 或 descending),并通過比較函數(shù)動態(tài)生成對應(yīng)的排序邏輯。
5.1.2 多字段排序的策略與實現(xiàn)
在實際應(yīng)用中,經(jīng)常需要根據(jù)多個字段進行排序,例如先按部門排序,再按工資排序。這可以通過鏈式比較函數(shù)來實現(xiàn)。
const data = [
{ name: 'Alice', dept: 'HR', salary: 5000 },
{ name: 'Bob', dept: 'IT', salary: 6000 },
{ name: 'Charlie', dept: 'IT', salary: 5500 },
];
data.sort((a, b) => {
if (a.dept !== b.dept) {
return a.dept.localeCompare(b.dept); // 先按部門排序
}
return b.salary - a.salary; // 再按薪資降序
});
代碼邏輯分析:
- 首先判斷部門是否不同,若不同則使用
localeCompare對字符串進行排序; - 若部門相同,則按薪資降序排列。
在 Enumerable.OrderBy 中,我們可以支持鏈式調(diào)用,例如:
Enumerable.From(data) .OrderBy(x => x.dept) .ThenByDescending(x => x.salary) .ToArray();
這種設(shè)計不僅提升了可讀性,也增強了功能的靈活性。
5.2 OrderBy方法的封裝設(shè)計
為了實現(xiàn) LINQ 風格的 OrderBy 方法,我們需要在 JavaScript 中構(gòu)建一個可鏈式調(diào)用的查詢接口。本節(jié)將介紹其封裝設(shè)計的核心思路。
5.2.1 自定義排序函數(shù)的傳入與調(diào)用
在 LINQ 中, OrderBy 支持傳入一個函數(shù)用于提取排序鍵。例如:
.OrderBy(x => x.salary)
在 JavaScript 中,我們可以通過函數(shù)調(diào)用提取每個元素的排序鍵,并將其用于比較函數(shù)中。
function orderBy(keySelector, direction = 'asc') {
const compare = (a, b) => {
const keyA = keySelector(a);
const keyB = keySelector(b);
if (keyA < keyB) return direction === 'asc' ? -1 : 1;
if (keyA > keyB) return direction === 'asc' ? 1 : -1;
return 0;
};
this.items = [...this.items].sort(compare);
return this;
}
代碼邏輯分析:
keySelector是用戶傳入的函數(shù),用于提取排序鍵;direction控制排序方向,默認為升序;compare函數(shù)根據(jù)提取的鍵值進行比較,并返回相應(yīng)的排序結(jié)果;this.items是當前 Enumerable 對象維護的數(shù)據(jù)集合;- 返回
this實現(xiàn)鏈式調(diào)用。
5.2.2 對對象屬性排序的支持
在處理對象數(shù)組時,往往需要根據(jù)對象的屬性進行排序。為了支持點路徑(dot-path)的屬性訪問,我們可以實現(xiàn)一個輔助函數(shù)來解析嵌套屬性。
function getProperty(obj, path) {
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}
代碼邏輯分析:
path.split('.')將路徑如"user.address.city"拆分為數(shù)組;- 使用
reduce遍歷路徑,逐步獲取嵌套屬性值; - 如果某一級屬性不存在,則返回
undefined。
結(jié)合該函數(shù),我們可以實現(xiàn)對嵌套屬性的排序:
.OrderBy(x => x.user.address.city)
或者更靈活地傳入字符串路徑:
.OrderBy("user.address.city")
這提升了 API 的易用性與可讀性。
5.3 OrderBy的性能優(yōu)化與穩(wěn)定性
排序操作在大數(shù)據(jù)量下容易成為性能瓶頸,因此在實現(xiàn) OrderBy 時,必須考慮算法選擇、穩(wěn)定性以及時間復(fù)雜度等問題。
5.3.1 排序算法的選擇與時間復(fù)雜度分析
JavaScript 引擎內(nèi)部實現(xiàn)的排序算法通常是高效的。例如:
- V8 引擎(Chrome、Node.js)使用 TimSort 算法;
- SpiderMonkey(Firefox)使用 MergeSort;
- JavaScriptCore(Safari)也使用 TimSort。
TimSort 是一種混合排序算法,結(jié)合了歸并排序和插入排序的優(yōu)點,具有良好的最壞情況時間復(fù)雜度 O(n log n),并且是穩(wěn)定排序。
| 排序算法 | 最壞時間復(fù)雜度 | 是否穩(wěn)定 | 說明 |
|---|---|---|---|
| TimSort | O(n log n) | 是 | V8 引擎默認實現(xiàn) |
| QuickSort | O(n²) | 否 | 不穩(wěn)定,不適合對象排序 |
| MergeSort | O(n log n) | 是 | 穩(wěn)定,適合對象排序 |
因此,在封裝 OrderBy 時,我們應(yīng)盡量復(fù)用原生的 .sort() 方法,以獲得最佳性能和穩(wěn)定性。
5.3.2 穩(wěn)定排序與非穩(wěn)定排序的考量
穩(wěn)定排序指的是在排序過程中,相同鍵值的元素保持原有順序。例如:
const data = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Charlie' }
];
data.sort((a, b) => a.id - b.id);
// 穩(wěn)定排序下,{id:1, name:'Alice'} 應(yīng)該排在 {id:1, name:'Charlie'} 前面
如果排序算法不穩(wěn)定,那么上述兩個對象的順序可能在排序后互換,導致邏輯錯誤。因此,在封裝 OrderBy 時,應(yīng)確保排序過程是穩(wěn)定的。
示例:穩(wěn)定排序的實現(xiàn)驗證
function testStableSort() {
const arr = [
{ key: 1, value: 'A' },
{ key: 2, value: 'B' },
{ key: 1, value: 'C' },
{ key: 2, value: 'D' }
];
const sorted = [...arr].sort((a, b) => a.key - b.key);
console.log(sorted.map(x => x.value)); // 輸出: ["A", "C", "B", "D"]
}
代碼邏輯分析:
- 原始數(shù)組中兩個
key: 1的元素順序為 A → C; - 排序后仍保持 A 在前,C 在后,說明排序是穩(wěn)定的。
流程圖:排序封裝與執(zhí)行流程
graph TD
A[開始排序] --> B[提取排序鍵]
B --> C{是否為對象路徑?}
C -->|是| D[調(diào)用getProperty解析路徑]
C -->|否| E[直接調(diào)用keySelector]
D --> F[生成比較函數(shù)]
E --> F
F --> G[調(diào)用Array.sort()]
G --> H[返回排序后Enumerable]
該流程圖清晰地展示了 OrderBy 方法在封裝和執(zhí)行時的邏輯路徑,幫助開發(fā)者理解其內(nèi)部工作原理。
通過本章的詳細解析,我們不僅掌握了 OrderBy 的基本原理和實現(xiàn)機制,還深入探討了其性能優(yōu)化和穩(wěn)定性設(shè)計。在實際開發(fā)中,合理使用排序功能,不僅能提升代碼可讀性,還能顯著優(yōu)化程序性能。下一章我們將深入講解 GroupBy 方法的封裝與實現(xiàn),進一步拓展 LINQ 在數(shù)據(jù)處理中的應(yīng)用場景。
6. Enumerable.GroupBy方法封裝與實現(xiàn)
分組操作是數(shù)據(jù)處理中的重要環(huán)節(jié),它允許我們將具有相同特征的數(shù)據(jù)歸類在一起,從而便于后續(xù)的統(tǒng)計、分析與展示。 GroupBy 方法正是實現(xiàn)這一功能的核心手段。在本章中,我們將深入探討 Enumerable.GroupBy 的實現(xiàn)原理、分組邏輯、數(shù)據(jù)結(jié)構(gòu)設(shè)計,以及如何在JavaScript中模擬LINQ風格的 GroupBy 行為。此外,我們還將通過示例展示其在實際開發(fā)中的高級用法,如分組后的聚合操作和結(jié)果映射。
6.1 GroupBy方法的分組邏輯
6.1.1 根據(jù)鍵值進行數(shù)據(jù)分組
GroupBy 的核心在于“分組鍵(Key)”的提取。開發(fā)者可以通過一個函數(shù)或?qū)傩月窂絹碇付ǚ纸M的依據(jù),系統(tǒng)會根據(jù)每個元素的鍵值將其歸入對應(yīng)的組中。
示例:
const people = [
{ name: 'Alice', age: 25, city: 'Beijing' },
{ name: 'Bob', age: 30, city: 'Shanghai' },
{ name: 'Charlie', age: 25, city: 'Beijing' },
{ name: 'David', age: 30, city: 'Shanghai' }
];
const groupedByCity = Enumerable.From(people).GroupBy(p => p.city);
console.log(groupedByCity.toArray());
輸出結(jié)構(gòu):
[
{
key: 'Beijing',
elements: [
{ name: 'Alice', age: 25, city: 'Beijing' },
{ name: 'Charlie', age: 25, city: 'Beijing' }
]
},
{
key: 'Shanghai',
elements: [
{ name: 'Bob', age: 30, city: 'Shanghai' },
{ name: 'David', age: 30, city: 'Shanghai' }
]
}
]
6.1.2 分組結(jié)果的結(jié)構(gòu)定義
分組結(jié)果通常是一個數(shù)組,其中每個元素是一個對象,包含兩個關(guān)鍵字段:
key: 當前分組的鍵值。elements: 屬于該鍵值的所有原始數(shù)據(jù)項。
這種結(jié)構(gòu)清晰地表達了每個組的含義,并便于后續(xù)操作如聚合、映射等。
分組結(jié)果結(jié)構(gòu)的定義方式(偽代碼):
class Group {
constructor(key, elements = []) {
this.key = key;
this.elements = elements;
}
}
6.2 GroupBy的實現(xiàn)策略
6.2.1 鍵的提取與分組字典的構(gòu)建
實現(xiàn) GroupBy 的核心在于構(gòu)建一個“鍵到元素列表”的映射字典。我們可以使用JavaScript中的 Map 對象來高效地實現(xiàn)這一結(jié)構(gòu)。
實現(xiàn)代碼:
function groupBy(array, keySelector) {
const map = new Map();
for (const item of array) {
const key = keySelector(item);
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(item);
}
// 轉(zhuǎn)換為Group對象數(shù)組
return Array.from(map.entries()).map(([key, elements]) => ({
key,
elements
}));
}
代碼邏輯逐行分析:
- map = new Map() :創(chuàng)建一個空的Map用于存儲分組鍵和對應(yīng)的元素列表。
- for (const item of array) :遍歷傳入的原始數(shù)據(jù)集合。
- const key = keySelector(item) :通過 keySelector 函數(shù)提取當前項的鍵。
- if (!map.has(key)) :如果該鍵尚未存在,則初始化一個空數(shù)組。
- map.get(key).push(item) :將當前元素添加到對應(yīng)鍵的數(shù)組中。
- Array.from(map.entries())... :將Map轉(zhuǎn)換為數(shù)組形式,并構(gòu)建 Group 對象。
參數(shù)說明:
array: 待分組的數(shù)據(jù)源。keySelector: 接收一個元素并返回其分組鍵的函數(shù)。
6.2.2 支持多個分組鍵的鏈式處理
有時我們需要根據(jù)多個字段進行分組,例如先按城市分組,再按年齡細分。這可以通過鏈式調(diào)用 GroupBy 來實現(xiàn)。
示例代碼:
const groupedByCityAndAge = Enumerable.From(people)
.GroupBy(p => p.city)
.Select(g => ({
key: g.key,
groups: Enumerable.From(g.elements)
.GroupBy(p => p.age)
.toArray()
}));
console.log(groupedByCityAndAge.toArray());
輸出結(jié)構(gòu):
[
{
key: 'Beijing',
groups: [
{
key: 25,
elements: [
{ name: 'Alice', age: 25, city: 'Beijing' },
{ name: 'Charlie', age: 25, city: 'Beijing' }
]
}
]
},
{
key: 'Shanghai',
groups: [
{
key: 30,
elements: [
{ name: 'Bob', age: 30, city: 'Shanghai' },
{ name: 'David', age: 30, city: 'Shanghai' }
]
}
]
}
]
實現(xiàn)方式分析:
- 每個外層
GroupBy生成的elements再次作為內(nèi)層GroupBy的數(shù)據(jù)源。 - 使用
Select對分組結(jié)果進行投影,實現(xiàn)嵌套分組結(jié)構(gòu)。 - 該方式支持任意層級的分組嵌套,靈活性極高。
mermaid 流程圖:
graph TD
A[開始] --> B[遍歷數(shù)據(jù)源]
B --> C[提取鍵值]
C --> D{鍵是否已存在?}
D -- 是 --> E[將元素加入現(xiàn)有組]
D -- 否 --> F[創(chuàng)建新組]
F --> G[添加鍵-組映射]
E --> H[繼續(xù)遍歷]
G --> H
H --> I{是否遍歷完成?}
I -- 否 --> B
I -- 是 --> J[返回分組結(jié)果數(shù)組]
6.3 GroupBy的高級用法
6.3.1 分組后的聚合操作(如Count、Sum)
分組后通常需要進行聚合統(tǒng)計,如計算每組的元素數(shù)量、總和、平均值等。這些操作可以通過 Select 與 Aggregate 方法結(jié)合實現(xiàn)。
示例:計算每組人數(shù)
const groupedWithCount = Enumerable.From(people)
.GroupBy(p => p.city)
.Select(g => ({
city: g.key,
count: g.elements.length
}));
console.log(groupedWithCount.toArray());
輸出:
[
{ city: 'Beijing', count: 2 },
{ city: 'Shanghai', count: 2 }
]
示例:計算每組年齡總和
const groupedWithSum = Enumerable.From(people)
.GroupBy(p => p.city)
.Select(g => ({
city: g.key,
totalAge: g.elements.reduce((sum, p) => sum + p.age, 0)
}));
console.log(groupedWithSum.toArray());
輸出:
[
{ city: 'Beijing', totalAge: 50 },
{ city: 'Shanghai', totalAge: 60 }
]
6.3.2 分組結(jié)果的轉(zhuǎn)換與映射
除了簡單的統(tǒng)計,我們還可以對分組結(jié)果進行更復(fù)雜的結(jié)構(gòu)映射。例如將每組中的名字提取出來,形成一個字符串列表。
示例:將每組的名字轉(zhuǎn)換為字符串數(shù)組
const groupedWithNameList = Enumerable.From(people)
.GroupBy(p => p.city)
.Select(g => ({
city: g.key,
names: g.elements.map(p => p.name)
}));
console.log(groupedWithNameList.toArray());
輸出:
[
{ city: 'Beijing', names: ['Alice', 'Charlie'] },
{ city: 'Shanghai', names: ['Bob', 'David'] }
]
表格:GroupBy常用聚合操作示例
| 聚合類型 | 方法 | 示例代碼 | 說明 |
|---|---|---|---|
| 計數(shù) | length | g.elements.length | 統(tǒng)計組內(nèi)元素數(shù)量 |
| 求和 | reduce | g.elements.reduce((s, p) => s + p.age, 0) | 對數(shù)值字段求和 |
| 最大值 | reduce | g.elements.reduce((max, p) => Math.max(max, p.age), -Infinity) | 找出最大值 |
| 最小值 | reduce | g.elements.reduce((min, p) => Math.min(min, p.age), Infinity) | 找出最小值 |
| 平均值 | reduce + length | sum / g.elements.length | 計算平均值 |
| 映射轉(zhuǎn)換 | map | g.elements.map(p => p.name) | 提取字段形成新數(shù)組 |
通過本章的講解,我們不僅掌握了 GroupBy 的底層實現(xiàn)邏輯,還了解了如何在JavaScript中模擬LINQ風格的分組操作,并通過聚合與映射實現(xiàn)更復(fù)雜的數(shù)據(jù)處理需求。這些技術(shù)在實際開發(fā)中具有廣泛的應(yīng)用價值,尤其適用于數(shù)據(jù)統(tǒng)計、報表展示、權(quán)限控制等場景。在下一章中,我們將進一步探討 Enumerable.Distinct 方法的實現(xiàn)原理與優(yōu)化策略。
7. Enumerable.Distinct方法封裝與實現(xiàn)
7.1 Distinct方法去重的核心機制
Distinct 是 LINQ 中非常關(guān)鍵的一個操作,用于從集合中去除重復(fù)項,保留唯一的元素。在 JavaScript 中,數(shù)組默認沒有 Distinct 方法,但可以通過擴展原型鏈或封裝函數(shù)來實現(xiàn)類似功能。
7.1.1 基于值比較的去重策略
JavaScript 中的原始類型(如 number、string、boolean)可以直接通過 === 進行比較。對于這類數(shù)據(jù),我們可以通過 Set 或 Map 結(jié)構(gòu)來緩存已出現(xiàn)的值,從而實現(xiàn)去重:
function distinct(arr) {
const seen = new Set();
return arr.filter(item => {
if (!seen.has(item)) {
seen.add(item);
return true;
}
return false;
});
}
// 示例
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(distinct(numbers)); // [1, 2, 3, 4, 5]
說明:
- Set 自動確保值的唯一性。
- filter 遍歷數(shù)組,只有未出現(xiàn)的元素才會被保留。
7.1.2 自定義比較器的引入
對于對象類型的數(shù)據(jù),直接比較引用地址會導致誤判。因此,我們需要引入自定義比較器(comparer)來定義“唯一”的標準:
function distinctWithComparer(arr, comparer) {
const seen = [];
return arr.filter(item => {
const exists = seen.some(seenItem => comparer(item, seenItem));
if (!exists) {
seen.push(item);
return true;
}
return false;
});
}
// 示例
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }
];
const result = distinctWithComparer(users, (a, b) => a.id === b.id);
console.log(result); // [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]
說明:
- comparer 函數(shù)用于判斷兩個對象是否“相等”。
- some 遍歷已緩存對象,判斷是否已有匹配項。
7.2 Distinct方法的實現(xiàn)細節(jié)
7.2.1 利用Set或Map進行去重緩存
對于原始值的去重, Set 是最高效的結(jié)構(gòu),時間復(fù)雜度為 O(1)。而對于對象,如果僅需要基于某個唯一字段(如 id )去重,可以使用 Map 來緩存字段值:
function distinctById(arr) {
const map = new Map();
return arr.filter(item => {
if (!map.has(item.id)) {
map.set(item.id, true);
return true;
}
return false;
});
}
// 示例
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }
];
console.log(distinctById(users)); // [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]
說明:
- 使用 Map 緩存唯一標識符 id ,避免對象比較的復(fù)雜性。
- 性能優(yōu)于自定義比較器。
7.2.2 對對象去重的特殊處理
對于復(fù)雜對象,若沒有唯一字段,可以使用 JSON.stringify() 將對象序列化后進行比較,但需注意對象鍵的順序問題:
function deepDistinct(arr) {
const seen = new Set();
return arr.filter(item => {
const key = JSON.stringify(item);
if (!seen.has(key)) {
seen.add(key);
return true;
}
return false;
});
}
// 示例
const data = [
{ name: 'John', age: 25 },
{ age: 25, name: 'John' },
{ name: 'Jane', age: 30 }
];
console.log(deepDistinct(data)); // [ { name: 'John', age: 25 }, { name: 'Jane', age: 30 } ]
注意:
- JSON.stringify() 的鍵順序會影響字符串結(jié)果。
- 不適用于包含函數(shù)、undefined、循環(huán)引用等復(fù)雜結(jié)構(gòu)。
7.3 Distinct與其他操作的組合應(yīng)用
7.3.1 與Select、Where等方法的配合使用
Distinct 通常與其他 LINQ 操作組合使用,以實現(xiàn)更復(fù)雜的數(shù)據(jù)處理流程。例如:先投影再去重:
const result = users
.map(user => user.name) // Select
.filter(name => name.startsWith('A')) // Where
.reduce((acc, name) => {
if (!acc.includes(name)) acc.push(name);
return acc;
}, []); // Distinct
console.log(result); // 去重后的名字列表
說明:
- 通過 .map() 實現(xiàn) Select 功能。
- 通過 .filter() 實現(xiàn) Where 功能。
- 通過 .reduce() 實現(xiàn) Distinct 功能。
7.3.2 去重性能與內(nèi)存占用的優(yōu)化策略
- 避免重復(fù)序列化 :如使用
JSON.stringify()去重,建議在初始化時緩存字符串。 - 優(yōu)先使用字段比較 :如對象有唯一標識符,優(yōu)先使用字段去重而非對象深度比較。
- 使用生成器函數(shù) :處理大數(shù)據(jù)集時,可使用生成器函數(shù)(Generator)逐條處理,避免一次性加載全部數(shù)據(jù)。
graph TD
A[開始] --> B[讀取數(shù)據(jù)源]
B --> C{是否為對象類型?}
C -->|是| D[使用Map或自定義比較器]
C -->|否| E[使用Set直接比較]
D --> F[遍歷并去重]
E --> F
F --> G[輸出去重結(jié)果]
上圖展示了 Distinct 方法在不同數(shù)據(jù)類型下的執(zhí)行流程,通過判斷類型選擇最優(yōu)去重策略,提升性能與穩(wěn)定性
到此這篇關(guān)于JavaScript封裝LINQ查詢方法實戰(zhàn)的文章就介紹到這了,更多相關(guān)JavaScript LINQ查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
網(wǎng)頁前臺通過js非法字符過濾代碼(罵人的話等等)
項目中如果需要過濾非法字符(你不想用戶輸入的任何字符)的話在前臺可以使用js過濾,同樣可以提高用戶體驗,以前都是后臺過濾,這樣雙重過濾用戶體驗更好。2010-05-05
在TypeScript中迭代對象鍵Object.keys不起作用的原因和解決方案
在TypeScript中迭代對象鍵object?keys可能是一場噩夢,以下是我所知道的所有解決方案,文中有詳細的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下2023-10-10
JavaScript中return返回多個值的三個方法實現(xiàn)
本文主要介紹了JavaScript中return返回多個值的三個方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08
JS Excel讀取和寫入操作(模板操作)實現(xiàn)代碼
前一段時間一直在做報表,所以肯定會用到Excel的操作,但是在網(wǎng)上查閱資料有關(guān)JS操作excel較少,有的話,也都是老生常談或很零碎的一些東西。2010-04-04

