最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript封裝LINQ查詢方法實戰(zhàn)

 更新時間:2025年09月24日 09:41:31   作者:雄哥侃運營  
本文主要介紹了通過js-linq庫在JavaScript中實現(xiàn)類似.NET LINQ的查詢功能,封裝Where、Select、OrderBy等方法,提升數(shù)組和可迭代對象的數(shù)據(jù)處理效率與代碼可讀性

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風(fēng)格的鏈式查詢提供了良好基礎(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)用風(fēng)格,展示了如何通過封裝實現(xiàn)類似 .Where() .Select() 的功能。這種設(shè)計不僅提升了代碼的可讀性,也增強了數(shù)據(jù)處理的邏輯抽象能力。在后續(xù)章節(jié)中,我們將逐步剖析如何封裝和實現(xiàn)這些核心方法。

2. Enumerable.From方法封裝與實現(xiàn)

在構(gòu)建LINQ風(fēng)格的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 風(fēng)格對象,后續(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)用。

可擴展性說明:

  • 當(dāng)需要支持新的數(shù)據(jù)源類型時,只需添加新的 case 分支和對應(yīng)的迭代器函數(shù);
  • 每個迭代器函數(shù)負責(zé)將數(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 :記錄當(dāng)前迭代位置;
  • 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 風(fēng)格查詢中最重要的操作之一,它用于對集合中的元素進行條件篩選,返回滿足條件的子集。在 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 風(fēng)格查詢的核心特性之一。通過返回新的 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 風(fēng)格查詢的核心方法之一。通過封裝謂詞函數(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 風(fēng)格查詢庫,我們需要對 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ù)組映射為字符串?dāng)?shù)組。

在 LINQ 的設(shè)計哲學(xué)中, 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 : 當(dāng)前迭代的數(shù)組元素。
- user.name : 從對象中提取 name 屬性。

4.2 Select方法的實現(xiàn)方式

4.2.1 接收選擇函數(shù)并轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu)

為了實現(xiàn)一個完整的 LINQ 風(fēng)格的 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 風(fēng)格查詢接口,使其在 JavaScript 中具備強大的數(shù)據(jù)處理能力。

5. Enumerable.OrderBy方法封裝與實現(xiàn)

在數(shù)據(jù)處理中,排序是一項基礎(chǔ)且常用的操作。 Enumerable.OrderBy 方法允許開發(fā)者根據(jù)指定的鍵或條件對集合中的元素進行排序。在 LINQ 的設(shè)計哲學(xué)中,排序不僅限于簡單的升序或降序排列,還支持多字段排序、自定義比較器以及嵌套結(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 表示升序,因為當(dāng) a < b 時返回負數(shù), a 被排在前面;
  • b - a 表示降序,當(dāng) 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 風(fēng)格的 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 是當(dāng)前 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)定說明
TimSortO(n log n)V8 引擎默認實現(xiàn)
QuickSortO(n²)不穩(wěn)定,不適合對象排序
MergeSortO(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)定,那么上述兩個對象的順序可能在排序后互換,導(dǎo)致邏輯錯誤。因此,在封裝 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風(fēng)格的 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 : 當(dāng)前分組的鍵值。
  • 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
  }));
}

代碼邏輯逐行分析:

  1. map = new Map() :創(chuàng)建一個空的Map用于存儲分組鍵和對應(yīng)的元素列表。
  2. for (const item of array) :遍歷傳入的原始數(shù)據(jù)集合。
  3. const key = keySelector(item) :通過 keySelector 函數(shù)提取當(dāng)前項的鍵。
  4. if (!map.has(key)) :如果該鍵尚未存在,則初始化一個空數(shù)組。
  5. map.get(key).push(item) :將當(dāng)前元素添加到對應(yīng)鍵的數(shù)組中。
  6. 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)換為字符串?dāng)?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ù)lengthg.elements.length統(tǒng)計組內(nèi)元素數(shù)量
求和reduceg.elements.reduce((s, p) => s + p.age, 0)對數(shù)值字段求和
最大值reduceg.elements.reduce((max, p) => Math.max(max, p.age), -Infinity)找出最大值
最小值reduceg.elements.reduce((min, p) => Math.min(min, p.age), Infinity)找出最小值
平均值reduce + lengthsum / g.elements.length計算平均值
映射轉(zhuǎn)換mapg.elements.map(p => p.name)提取字段形成新數(shù)組

通過本章的講解,我們不僅掌握了 GroupBy 的底層實現(xiàn)邏輯,還了解了如何在JavaScript中模擬LINQ風(fēng)格的分組操作,并通過聚合與映射實現(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ù),直接比較引用地址會導(dǎo)致誤判。因此,我們需要引入自定義比較器(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)文章

最新評論

宕昌县| 东海县| 西藏| 房产| 广州市| 阳山县| 山阴县| 前郭尔| 康平县| 五原县| 通道| 安龙县| 舟曲县| 玉屏| 慈溪市| 宁乡县| 阿拉尔市| 循化| 闸北区| 南城县| 黄梅县| 郴州市| 思茅市| 崇义县| 开化县| 盖州市| 汤原县| 图们市| 和静县| 湖北省| 长泰县| 缙云县| 定边县| 女性| 日土县| 葵青区| 上高县| 江孜县| 潮州市| 壶关县| 云浮市|