JavaScript 的 LINQ






4.96/5 (137投票s)
在 JavaScript 中实现 .NET Enumerable 方法,包括聚合、迭代、谓词和选择器。
在 GitHub 上查看项目,查找一个全面的 JavaScript 数据结构和 LINQ 库。
引言
语言集成查询(LINQ)是 Microsoft .NET Framework 的一个组件,它通过添加查询表达式(类似于 SQL 语句)来扩展语言,可以方便地从数组中提取和处理数据。自 ECMAScript 5th Edition 起,JavaScript 内置 API 提供了一组非常有限的迭代方法:forEach、every、some、filter、map、reduce 和 reduceRight。
这些方法不是跨浏览器的,与 LINQ API 不同,并且不包含 LINQ 的大部分功能。本文介绍了在 JavaScript 中实现的 30 多个 .NET 4.0 Enumerable 方法,包括聚合、迭代、谓词和选择器,为传统的 JavaScript 代码增加了 LINQ 风格查询的强大功能和灵活性。
背景
JavaScript 本身不支持类继承,并通过原型继承笨拙地支持它。可以使用 JavaScript 中的原型来模拟许多类特性。原型提供了面向对象编程语言所特有的面向对象功能。这意味着为了给内置 API 添加更多功能,您可以扩展类的原型。
JavaScript 中的所有数组都继承自Array 对象,并从Array.prototype 继承方法和属性。对 Array 原型对象的更改会传播到所有数组,除非沿原型链进一步覆盖了要更改的属性和方法。
为了给 JavaScript 添加 LINQ 功能,我们需要做的就是将这些方法添加到 Array.prototype
对象中。
例如,JavaScript API 没有 union
方法,但有功能几乎相同的 concat
方法。通过将 Array.prototype.union
设置为内置的 concat
方法,所有 JavaScript 数组也将拥有 union
方法。
(真正的 union 方法使用两个序列并集中的不重复元素)
Array.prototype.union = Array.prototype.concat;
开始之前
大多数 LINQ 方法都需要EqualityComparer(相等比较器)、SortComparer(排序比较器)、Predicate(谓词)或Selector(选择器)函数来应用于数组中的每个元素。在 .Net 中,这是通过将委托传递给方法来完成的。例如,这可能是 C# 中的 Select
方法的样子:
var someArray = new int[] { 1, 2, 3, 4 };
var otherArray = someArray.Select(t => t * 2);
在上面的示例中,t => t * 2
是一个Lambda 表达式,它充当匿名函数(委托),将数组的每个元素乘以 2。但是,由于 JavaScript 不自带 Lambda 表达式,因此 JavaScript 中的匿名函数使用 function(){ ... }
定义。
以下是在 JavaScript 中 LINQ select
方法可能的样子:
var someArray = [1, 2, 3, 4];
var otherArray = someArray.select(function (t) { return t * 2 });
以下是 EqualityComparer
、SortComparer
、Predicate
或 Selector
的默认函数:
function DefaultEqualityComparer(a, b) {
return a === b || a.valueOf() === b.valueOf();
};
function DefaultSortComparer(a, b) {
if (a === b) return 0;
if (a == null) return -1;
if (b == null) return 1;
if (typeof a == "string") return a.toString().localeCompare(b.toString());
return a.valueOf() - b.valueOf();
};
function DefaultPredicate() {
return true;
};
function DefaultSelector(t) {
return t;
};
JavaScript LINQ Selectors (选择器)
Select
将序列中的每个元素投影到新形式。
Array.prototype.select = Array.prototype.map || function (selector, context) {
context = context || window;
var arr = [];
var l = this.length;
for (var i = 0; i < l; i++)
arr.push(selector.call(context, this[i], i, this));
return arr;
};
示例
var arr = [1, 2, 3, 4, 5]; var doubled = arr.select(function(t){ return t * 2 }); // [2, 4, 6, 8, 10]
SelectMany
将序列中的每个元素投影到数组,并将生成的序列展平成一个序列。
Array.prototype.selectMany = function (selector, resSelector) {
resSelector = resSelector || function (i, res) { return res; };
return this.aggregate(function (a, b, i) {
return a.concat(selector(b, i).select(function (res) { return resSelector(b, res) }));
}, []);
};
示例
var arr = [{Name:"A", Values:[1, 2, 3, 4]}, {Name:"B", Values:[5, 6, 7, 8]}]; var res1 = arr.selectMany(function(t){ return t.Values }); // using default result selector var res2 = arr.selectMany(function(t){ return t.Values }, function(t, u){ return {Name:t.Name, Val:u}}); // using custom result selector
Take
从序列的开头返回指定数量的连续元素。
Array.prototype.take = function (c) {
return this.slice(0, c);
};
示例:
var arr = [1, 2, 3, 4, 5]; var res = arr.take(2); // [1, 2]
Skip
跳过序列中指定数量的元素,然后返回剩余元素。
Array.prototype.skip = function (c) {
return this.slice(c);
};
示例:
var arr = [1, 2, 3, 4, 5]; var res = arr.skip(2); // [3, 4, 5]
First
返回序列的第一个元素。
Array.prototype.first = function (predicate, def) {
var l = this.length;
if (!predicate) return l ? this[0] : def == null ? null : def;
for (var i = 0; i < l; i++)
if (predicate(this[i], i, this))
return this[i];
return def == null ? null : def;
};
示例:
var arr = [1, 2, 3, 4, 5]; var t1 = arr.first(); // 1 var t2 = arr.first(function(t){ return t > 2 }); // using comparer: 3 var t3 = arr.first(function(t){ return t > 10 }, 10); // using comparer and default value: 10
Last
返回序列的最后一个元素。
Array.prototype.last = function (predicate, def) {
var l = this.length;
if (!predicate) return l ? this[l - 1] : def == null ? null : def;
while (l-- > 0)
if (predicate(this[l], l, this))
return this[l];
return def == null ? null : def;
};
示例
var arr = [1, 2, 3, 4, 5]; var t1 = arr.last(); // 5 var t2 = arr.last(function(t){ return t > 2 }); // using comparer: 5 var t3 = arr.last(function(t){ return t > 10 }, 10); // using comparer and default value: 10
Union
使用默认的相等比较器生成两个序列的集合并集。
Array.prototype.union = function (arr) {
return this.concat(arr).distinct();
};
示例
var arr1 = [1, 2, 3, 4, 5]; var arr2 = [5, 6, 7, 8, 9]; var res = arr1.union(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Intersect
生成两个序列的集合交集。
Array.prototype.intersect = function (arr, comparer) {
comparer = comparer || DefaultEqualityComparer;
return this.distinct(comparer).where(function (t) {
return arr.contains(t, comparer);
});
};
示例
var arr1 = [1, 2, 3, 4, 5]; var arr2 = [1, 2, 3]; var res = arr1.intersect(arr2); // [1, 2, 3]
Except
生成两个序列的集合差集。
Array.prototype.except = function (arr, comparer) {
if (!(arr instanceof Array)) arr = [arr];
comparer = comparer || DefaultEqualityComparer;
var l = this.length;
var res = [];
for (var i = 0; i < l; i++) {
var k = arr.length;
var t = false;
while (k-- > 0) {
if (comparer(this[i], arr[k]) === true) {
t = true;
break;
}
}
if (!t) res.push(this[i]);
}
return res;
};
示例
var arr1 = [1, 2, 3, 4, 5]; var arr2 = [2, 3, 4]; var res = arr1.except(arr2); // [1, 5]
Distinct
使用默认的相等比较器返回序列中的不重复元素。
Array.prototype.distinct = function (comparer) {
var arr = [];
var l = this.length;
for (var i = 0; i < l; i++) {
if (!arr.contains(this[i], comparer))
arr.push(this[i]);
}
return arr;
};
示例
var arr1 = [1, 2, 2, 3, 3, 4, 5, 5]; var res1 = arr.distinct(); // [1, 2, 3, 4, 5] var arr2 = [{Name:"A", Val:1}, {Name:"B", Val:1}]; var res2 = arr2.distinct(function(a, b){ return a.Val == b.Val }); // [{Name:"A", Val:1}]
Zip
将指定的函数应用于两个序列的相应元素,生成一个结果序列。
Array.prototype.zip = function (arr, selector) { return this .take(Math.min(this.length, arr.length)) .select(function (t, i) { return selector(t, arr[i]); }); };
示例
var arr1 = [1, 2, 3, 4]; var arr2 = ["A", "B", "C", "D"]; var res = arr1.zip(arr2, function(a, b){ return {Num:a, Letter:b} }); // [{Num:1, Letter: "A"},{Num:2, Letter: "B"}, {Num:3, Letter: "C"}, {Num:4, Letter: "D"}]
IndexOf
返回一维数组或数组的一部分中某个值第一次出现的位置。
Array.prototype.indexOf = Array.prototype.indexOf || function (o, index) {
var l = this.length;
for (var i = Math.max(Math.min(index, l), 0) || 0; i < l; i++)
if (this[i] === o) return i;
return -1;
};
示例
var arr = [1, 2, 3, 4, 5]; var index = arr.indexOf(2); // 1
LastIndexOf (最后出现的位置)
返回一维数组或数组的一部分中某个值最后一次出现的位置。
Array.prototype.lastIndexOf = Array.prototype.lastIndexOf || function (o, index) {
var l = Math.max(Math.min(index || this.length, this.length), 0);
while (l-- > 0)
if (this[l] === o) return l;
return -1;
};
示例
var arr = [1, 2, 3, 4, 5, 3, 4, 5]; var index = arr.lastIndexOf(3); // 5
移除
从数组中移除某个对象的第一次出现。
Array.prototype.remove = function (item) {
var i = this.indexOf(item);
if (i != -1)
this.splice(i, 1);
};
示例
var arr = [1, 2, 3, 4, 5]; arr.remove(2); // [1, 3, 4, 5]
RemoveAll
删除所有满足指定谓词定义的元素。
Array.prototype.removeAll = function (predicate) {
var item;
var i = 0;
while (item = this.first(predicate)) {
i++;
this.remove(item);
}
return i;
};
示例
var arr = [1, 2, 3, 4, 5]; arr.removeAll(function(t){ return t % 2 == 0 }); // [1, 3, 5]
OrderBy
根据键值按升序对序列的元素进行排序。
Array.prototype.orderBy = function (selector, comparer) {
comparer = comparer || DefaultSortComparer;
var arr = this.slice(0);
var fn = function (a, b) {
return comparer(selector(a), selector(b));
};
arr.thenBy = function (selector, comparer) {
comparer = comparer || DefaultSortComparer;
return arr.orderBy(DefaultSelector, function (a, b) {
var res = fn(a, b);
return res === 0 ? comparer(selector(a), selector(b)) : res;
});
};
arr.thenByDescending = function (selector, comparer) {
comparer = comparer || DefaultSortComparer;
return arr.orderBy(DefaultSelector, function (a, b) {
var res = fn(a, b);
return res === 0 ? -comparer(selector(a), selector(b)) : res;
});
};
return arr.sort(fn);
};
示例
var arr = [{Name:"A", Val:1}, {Name:"a", Val:2}, {Name:"B", Val:1}, {Name:"C", Val:2}]; var res1 = arr.orderBy(function(t){ return t.Name }); var res2 = arr.orderBy(function(t){ return t.Name }, function(a, b){ if(a.toUpperCase() > b.toUpperCase()) return 1; if(a.toUpperCase() < b.toUpperCase()) return -1; return 0; });
OrderByDescending
按降序对序列的元素进行排序。
Array.prototype.orderByDescending = function (selector, comparer) {
comparer = comparer || DefaultSortComparer;
return this.orderBy(selector, function (a, b) { return -comparer(a, b) });
};
示例
var arr = [{Name:"A", Val:1}, {Name:"a", Val:2}, {Name:"B", Val:1}, {Name:"C", Val:2}]; var res = arr.orderByDescending(function(t){ return t.Name });
ThenBy / ThenByDescending (然后升序/然后降序)
使用指定的比较器对序列中的元素进行后续的升序/降序排序。 ThenBy
和 ThenByDescending
被定义用来扩展 OrderBy
和 OrderByDescending
的输出类型,这也是这些方法的返回类型。这种设计使您可以通过应用任意数量的 ThenBy 或 ThenByDescending 方法来指定多个排序条件。
示例
var arr = [{Name:"A", Val:1}, {Name:"a", Val:2}, {Name:"B", Val:1}, {Name:"C", Val:2}]; var res1 = arr.orderBy(function(t){ return t.Val }) .thenBy(function(t){ return t.Name }); var res2 = arr.orderBy(function(t){ return t.Val }) .thenByDescending(function(t){ return t.Name }); var res3 = arr.orderByDescending(function(t){ return t.Val }) .thenBy(function(t){ return t.Name });
InnerJoin (内连接)
根据匹配的键来关联两个序列的元素。
Array.prototype.innerJoin = function (arr, outer, inner, result, comparer) {
comparer = comparer || DefaultEqualityComparer;
var res = [];
this.forEach(function (t) {
arr.where(function (u) {
return comparer(outer(t), inner(u));
})
.forEach(function (u) {
res.push(result(t, u));
});
});
return res;
};
示例
var arr1 = [{Name:"A", Val:1}, {Name:"B", Val:2}, {Name:"C", Val:3}]; var arr2 = [{Code:"A"}, {Code:"B"}, {Name:"C", Code:"C"}]; var res1 = arr1.innerJoin(arr2, function (t) { return t.Name }, // arr1 selector function (u) { return u.Code }, // arr2 selector function (t, u) { return { Name: t.Name, Val: t.Val, Code: u.Code } }); // result selector // using custom comparer var res2 = arr1.innerJoin(arr2, function (t) { return t.Name }, // arr1 selector function (u) { return u.Code }, // arr2 selector function (t, u) { return { Name: t.Name, Val: t.Val, Code: u.Code } }, // result selector function (a, b) { return a.toUpperCase() == b.toUpperCase() }); // comparer
GroupJoin
根据键的相等性关联两个序列的元素,并对结果进行分组。使用默认的相等比较器来比较键。
Array.prototype.groupJoin = function (arr, outer, inner, result, comparer) {
comparer = comparer || DefaultEqualityComparer;
return this
.select(function (t) {
var key = outer(t);
return {
outer: t,
inner: arr.where(function (u) { return comparer(key, inner(u)); }),
key: key
};
})
.select(function (t) {
t.inner.key = t.key;
return result(t.outer, t.inner);
});
};
示例
var arr1 = [{Name:"A", Val:1}, {Name:"B", Val:2}, {Name:"C", Val:3}]; var arr2 = [{Code:"A"}, {Code:"A"}, {Code:"B"}, {Code:"B"}, {Code:"C"}]; var res1 = arr1.groupJoin(arr2, function(t){ return t.Name }, // arr1 selector function(u){ return u.Code }, // arr2 selector function(t, u){ return {Item:t, Group:u} }) ; // result selector // using custom comparer var res2 = arr1.groupJoin(arr2, function(t){ return t.Name }, // arr1 selector function(u){ return u.Code }, // arr2 selector function(t, u){ return {Item:t, Group:u} }, // result selector function(a, b){ return a.toUpperCase() == b.toUpperCase() }); // comparer
GroupBy
根据指定的键选择器函数对序列的元素进行分组。
Array.prototype.groupBy = function (selector, comparer) {
var grp = [];
var l = this.length;
comparer = comparer || DefaultEqualityComparer;
selector = selector || DefaultSelector;
for (var i = 0; i < l; i++) {
var k = selector(this[i]);
var g = grp.first(function (u) { return comparer(u.key, k); });
if (!g) {
g = [];
g.key = k;
grp.push(g);
}
g.push(this[i]);
}
return grp;
};
示例
var arr = [{Name:"A", Val:1}, {Name:"B", Val:1}, {Name:"C", Val:2}, {Name:"D", Val:2}]; var res = arr.groupBy(function(t){ return t.Val }); // [[{Name:"A", Val:1}, {Name:"B", Val:1}], [{Name:"C", Val:2}, {Name:"D", Val:2}]] res.forEach(function(t){ console.log("Key: " + t.key, "Length: " + t.length); }); // Key: 1 Length: 2 // Key: 2 Length: 2
ToDictionary (转换为字典)
根据指定的键选择器函数从数组创建对象。
Array.prototype.toDictionary = function (keySelector, valueSelector) {
var o = {};
var l = this.length;
while (l-- > 0) {
var key = keySelector(this[l]);
if (key == null || key == "") continue;
o[key] = valueSelector(this[l]);
}
return o;
};
示例
var arr = [1, 2, 3, 4, 5]; var dic = arr.toDictionary(function(t){ return "Num" + t }, function(u){ return u }); // dic = {Num5: 5, Num4: 4, Num3: 3, Num2: 2, Num1: 1}
JavaScript LINQ Aggregations (聚合)
Aggregate
对序列应用累加器函数。
Array.prototype.aggregate = Array.prototype.reduce || function (func, seed) {
var arr = this.slice(0);
var l = this.length;
if (seed == null) seed = arr.shift();
for (var i = 0; i < l; i++)
seed = func(seed, arr[i], i, this);
return seed;
};
示例
var arr = [1, 2, 3, 4, 5]; var sum = arr.aggregate(function(a, b){ return a + b }, 0); // 15
最小值
返回序列中值的最小值。
Array.prototype.min = function (s) {
s = s || DefaultSelector;
var l = this.length;
var min = s(this[0]);
while (l-- > 0)
if (s(this[l]) < min) min = s(this[l]);
return min;
};
示例
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8]; var min1 = arr.min(); // 1 var arr2 = [{Name:"A", Val:1}, {Name:"B", Val:2}]; var min2 = arr2.min(function(t){ return t.Val }); // 1
最大值
返回序列中值的最大值。
Array.prototype.max = function (s) {
s = s || DefaultSelector;
var l = this.length;
var max = s(this[0]);
while (l-- > 0)
if (s(this[l]) > max) max = s(this[l]);
return max;
};
示例
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8]; var max1 = arr.max(); // 8 var arr2 = [{Name:"A", Val:1}, {Name:"B", Val:2}]; var max2 = arr2.max(function(t){ return t.Val }); // 2
Sum
计算数值序列的总和。
Array.prototype.sum = function (s) {
s = s || DefaultSelector;
var l = this.length;
var sum = 0;
while (l-- > 0) sum += s(this[l]);
return sum;
};
示例
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8]; var sum1 = arr.sum(); // 36 var arr2 = [{Name:"A", Val:1}, {Name:"B", Val:2}]; var sum2 = arr2.sum(function(t){ return t.Val }); // 3
JavaScript LINQ Predicates (谓词)
其中
根据谓词过滤值序列。
Array.prototype.where = Array.prototype.filter || function (predicate, context) {
context = context || window;
var arr = [];
var l = this.length;
for (var i = 0; i < l; i++)
if (predicate.call(context, this[i], i, this) === true) arr.push(this[i]);
return arr;
};
示例
var arr = [1, 2, 3, 4, 5]; var res = arr.where(function(t){ return t > 2 }) ; // [3, 4, 5]
任意
确定序列的任何元素是否存在或满足某个条件。
Array.prototype.any = function (predicate, context) {
context = context || window;
var f = this.some || function (p, c) {
var l = this.length;
if (!p) return l > 0;
while (l-- > 0)
if (p.call(c, this[l], l, this) === true) return true;
return false;
};
return f.apply(this, [predicate, context]);
};
示例
var arr = [1, 2, 3, 4, 5]; var res1 = arr.any(); // true var res2 = arr.any(function(t){ return t > 5 }); // false
全部
确定序列的所有元素是否满足某个条件。
Array.prototype.all = function (predicate, context) {
context = context || window;
predicate = predicate || DefaultPredicate;
var f = this.every || function (p, c) {
return this.length == this.where(p, c).length;
};
return f.apply(this, [predicate, context]);
};
示例
var arr = [1, 2, 3, 4, 5]; var res = arr.all(function(t){ return t < 6 }); // true
TakeWhile
返回序列中的元素,只要指定的条件为真,然后跳过剩余的元素。
Array.prototype.takeWhile = function (predicate) { predicate = predicate || DefaultPredicate; var l = this.length; var arr = []; for (var i = 0; i < l && predicate(this[i], i) === true ; i++) arr.push(this[i]); return arr; };
示例
var arr = [1, 2, 3, 4, 5, 6, 7, 8]; var res = arr.takeWhile(function(t){ return t % 4 != 0 }); // [1, 2, 3]
SkipWhile
跳过序列中的元素,只要指定的条件为真,然后返回剩余的元素。
Array.prototype.skipWhile = function (predicate) { predicate = predicate || DefaultPredicate; var l = this.length; var i = 0; for (i = 0; i < l; i++) if (predicate(this[i], i) === false) break; return this.skip(i); };
示例
var arr = [1, 2, 3, 4, 5, 6, 7, 8]; var res = arr.skipWhile(function(t){ return t & 4 != 0 }) ; // [ 4, 5, 6, 7, 8]
Contains
确定序列是否包含指定的元素。
Array.prototype.contains = function (o, comparer) {
comparer = comparer || DefaultEqualityComparer;
var l = this.length;
while (l-- > 0)
if (comparer(this[l], o) === true) return true;
return false;
};
示例
var arr1 = [1, 2, 3, 4, 5]; var res1 = arr.contains(2); // true var arr2 = [{Name:"A", Val:1}, {Name:"B", Val:1}]; var res2 = arr2.contains({Name:"C", Val:1}, function(a, b){ return a.Val == b.Val }) ; // true
JavaScript LINQ Iterations (迭代)
ForEach (遍历)
对数组的每个元素执行指定的操作。
Array.prototype.forEach = Array.prototype.forEach || function (callback, context) {
context = context || window;
var l = this.length;
for (var i = 0; i < l; i++)
callback.call(context, this[i], i, this);
};
示例
var arr = [1, 2, 3, 4, 5]; arr.forEach(function(t){ if(t % 2 ==0) console.log(t); });
DefaultIfEmpty
返回指定序列的元素,或者在序列为空时返回单例集合中的指定值。
Array.prototype.defaultIfEmpty = function (val) { return this.length == 0 ? [val == null ? null : val] : this; };
示例
var arr = [1, 2, 3, 4, 5]; var res = arr.where(function(t){ return t > 5 }).defaultIfEmpty(5); // [5]
历史
- 版本 1.5。