Javascript对应于C# LINQ中的Select操作

183

在这个问题之后:

使用Knockout的checked绑定和一组复选框,勾选其中一个会勾选所有的复选框

我用 Knockout 创建了一些复选框,可以从一个数组中进行选择。 从上面的帖子中获取的工作 jsfiddle:

http://jsfiddle.net/NsCXJ/

有没有一种简单的方法创建一个仅包含水果ID的数组?

我更喜欢 C#,所以我会做一些类似于 selectedFruits.select(fruit=>fruit.id); 的操作。

有没有类似于 JavaScript/jQuery 的某种方法或现成的函数来实现类似的操作?或者最简单的选项是循环遍历列表并创建第二个数组? 我打算将数组以 JSON 格式发送回服务器,所以尽量减少发送的数据。

8个回答

287

是的,Array.map() 或者 $.map() 做的是同样的事情。

//array.map:
var ids = this.fruits.map(function(v){
    return v.Id;
});

//jQuery.map:
var ids2 = $.map(this.fruits, function (v){
    return v.Id;
});

console.log(ids, ids2);

http://jsfiddle.net/NsCXJ/1/

由于旧浏览器不支持 array.map 方法,建议您使用 jQuery 方法。

如果您出于某些原因喜欢另一种方法,可以为旧浏览器添加 polyfill 支持。

您也可以向数组原型添加自定义方法:

Array.prototype.select = function(expr){
    var arr = this;
    //do custom stuff
    return arr.map(expr); //or $.map(expr);
};

var ids = this.fruits.select(function(v){
    return v.Id;
});

如果您传递一个字符串,可以使用函数构造器的扩展版本。也许可以玩一下:


Array.prototype.select = function(expr){
    var arr = this;

    switch(typeof expr){

        case 'function':
            return $.map(arr, expr);
            break;

        case 'string':

            try{

                var func = new Function(expr.split('.')[0], 
                                       'return ' + expr + ';');
                return $.map(arr, func);

            }catch(e){

                return null;
            }

            break;

        default:
            throw new ReferenceError('expr not defined or not supported');
            break;
    }

};

console.log(fruits.select('x.Id'));

http://jsfiddle.net/aL85j/

更新:

由于此回答变得如此流行,我添加了类似的 where() + firstOrDefault()。这些也可以与基于字符串的函数构造器方法一起使用(这是最快的方法),但这里是使用对象字面量作为过滤器的另一种方法:

Array.prototype.where = function (filter) {

    var collection = this;

    switch(typeof filter) { 

        case 'function': 
            return $.grep(collection, filter); 

        case 'object':
            for(var property in filter) {
              if(!filter.hasOwnProperty(property)) 
                  continue; // ignore inherited properties

              collection = $.grep(collection, function (item) {
                  return item[property] === filter[property];
              });
            }
            return collection.slice(0); // copy the array 
                                      // (in case of empty object filter)

        default: 
            throw new TypeError('func must be either a' +
                'function or an object of properties and values to filter by'); 
    }
};


Array.prototype.firstOrDefault = function(func){
    return this.where(func)[0] || null;
};

用法:

var persons = [{ name: 'foo', age: 1 }, { name: 'bar', age: 2 }];

// returns an array with one element:
var result1 = persons.where({ age: 1, name: 'foo' });

// returns the first matching item in the array, or null if no match
var result2 = persons.firstOrDefault({ age: 1, name: 'foo' }); 

这里有一个jsperf测试,用于比较函数构造器和对象字面量的速度。如果您决定使用前者,请记住正确引用字符串。

我个人偏好于在筛选1-2个属性时使用基于对象字面量的解决方案,并为更复杂的过滤传递回调函数。

我将以两个通用提示结束,在向本地对象原型添加方法时请注意:

  1. 在覆盖现有方法之前检查其是否存在,例如:

    if(!Array.prototype.where) { Array.prototype.where = ...

  2. 如果您不需要支持IE8及以下版本,请使用Object.defineProperty定义方法,使它们成为不可枚举的。如果有人在数组上使用了for..in(这本来就是错误的),他们也将迭代可枚举属性。只是提醒一下。


2
@ChrisNevill 如果您有兴趣,我还添加了一个字符串版本。 - Johan
@MUlferts,好发现,已更新:)。现在我建议使用lodash来完成这些任务。它们公开了与上面代码相同的接口。 - Johan
为了支持Knockout observables:return typeof item[property] === 'function' ? item[property]() === filter[property] : item[property] === filter[property]; - Linus Caldwell
@LinusCaldwell,我已经很久没有使用knockout了,但是你觉得这样行不行呢:return ko.unwrap(item[property]) === filter[property] - Johan
我提到了 knockout,但当然这将涵盖所有没有必需参数的函数属性。此外,为什么要破坏您美丽代码的通用风格呢? - Linus Caldwell
“map”在C#中与“select”不同。Map返回一个新数组,而Select返回一个可枚举对象(延迟到迭代时执行)。 - David Kiff

34

我知道这是一个晚回答,但对我很有用!只需补充说明,使用$.grep函数即可模拟linq中的where()

Linq:

var maleNames = people
.Where(p => p.Sex == "M")
.Select(p => p.Name)

Javascript:

// replace where  with $.grep
//         select with $.map
var maleNames = $.grep(people, function (p) { return p.Sex == 'M'; })
            .map(function (p) { return p.Name; });

这是我想要的...但在您的回答和Enumerable.From(selectedFruits).Select(function (fruit) { return fruit.id; });之间,哪个更好? - Bharat

20

ES6的方式:

let people = [{firstName:'Alice',lastName:'Cooper'},{firstName:'Bob',age:'Dylan'}];
let names = Array.from(people, p => p.firstName);
for (let name of names) {
  console.log(name);
}

也可以在此链接查看: https://jsfiddle.net/52dpucey/


非常感谢。我刚开始学习ES6,这会很有用! - Chris Nevill

18

因为你正在使用knockout,你应该考虑使用knockout实用函数arrayMap()和其他数组实用函数。

这里是各种数组实用函数及其等效的LINQ方法列表:

arrayFilter() -> Where()
arrayFirst() -> First()
arrayForEach() -> (no direct equivalent)
arrayGetDistictValues() -> Distinct()
arrayIndexOf() -> IndexOf()
arrayMap() -> Select()
arrayPushAll() -> (no direct equivalent)
arrayRemoveItem() -> (no direct equivalent)
compareArrays() -> (no direct equivalent)

所以,在你的例子中,你可以这样做:

var mapped = ko.utils.arrayMap(selectedFruits, function (fruit) {
    return fruit.id;
});

如果您想在JavaScript中使用类似于LINQ的接口,可以使用诸如linq.js之类的库,该库提供了对许多LINQ方法的良好接口。

var mapped = Enumerable.from(selectedFruits)
    .select("$.id") // shorthand for `x => x.id`
    .toArray();

11

您还可以尝试使用 linq.js

linq.js 中,您的

selectedFruits.select(fruit=>fruit.id);

将会是

Enumerable.From(selectedFruits).Select(function (fruit) { return fruit.id;  });

4

3
我已经创建了一个适用于 TypeScript 的 Linq 库,可以在 TsLinq.codeplex.com 上使用,也可以用于纯 JavaScript。该库比 Linq.js 快2-3倍,并且包含所有 Linq 方法的单元测试。也许您可以对其进行审核。

此链接需要更新。 - Chris Nevill
1
我自从第一个版本以来就没有进行维护,并且已经很长时间没有再使用它了。所以如果它消失了,那就消失了 :) - Michael Baarz
这很公平。 - Chris Nevill

0

看一下underscore.js,它提供了许多类似于linq的函数。在你给出的例子中,你可以使用map函数。


3
如果有人想知道如何比较,我写了一篇博客文章,解释了15个最流行的LINQ/underscore.js函数之间的区别:https://www.vladopandzic.com/javascript/comparing-underscore-js-with-linq/ - Vlado Pandžić

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接