Javascript, эквивалентный C # LINQ Select

137

После этого вопроса здесь:

Использование проверенного связывания в нокауте со списком флажков проверяет все флажки

Я создал несколько флажков, используя нокаут, которые позволяют выбирать из массива. Рабочая скрипка взята сверху постом:

http://jsfiddle.net/NsCXJ/

Есть ли простой способ создать массив только идентификаторов фруктов?

Я больше дома с C #, где я буду делать что-то вроде selectedFruits.select(fruit=>fruit.id);

Есть ли какой-нибудь метод / готовая функция для выполнения чего-то похожего с javascript / jquery? Или самым простым вариантом будет цикл по списку и создание второго массива? Я намерен отправить массив обратно на сервер в формате JSON, поэтому пытаюсь минимизировать отправленные данные.

Крис Невилл
источник

Ответы:

227

Да, 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.

Если вы по какой-то причине предпочитаете другой, вы всегда можете добавить полифилл для поддержки старого браузера.

Вы всегда можете добавить собственные методы в прототип массива:

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массив (что, в первую очередь, неверно), он также будет перебирать перечисляемые свойства. Просто один на один.

Johan
источник
1
@ChrisNevill Я также добавил строковую версию на случай, если вас заинтересует
Йохан,
@Mulferts Хороший улов, обновленный :). В настоящее время я бы предложил использовать lodash для подобных задач. Они предоставляют тот же интерфейс, что и код выше
Йохан
Для поддержки наблюдаемых в нокауте:return typeof item[property] === 'function' ? item[property]() === filter[property] : item[property] === filter[property];
Линус Колдуэлл
@LinusCaldwell Прошло много времени с тех пор, как я использовал нокаут, но как насчет чего-то вроде return ko.unwrap(item[property]) === filter[property]?
Йохан,
Ну, я упомянул нокаут, но, конечно, это будет охватывать все свойства, которые являются функциями без обязательных параметров. Кроме того, зачем нарушать общий стиль вашего прекрасного кода?
Линус Колдуэлл
33

Я знаю, что это поздний ответ, но он был полезен для меня! Просто для завершения, используя $.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;});
Бхарат
15

Поскольку вы используете 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;
});

Если вам нужен интерфейс, похожий на LINQ, в javascript, вы можете использовать библиотеку, такую ​​как linq.js, которая предлагает хороший интерфейс для многих методов LINQ.

var mapped = Enumerable.From(selectedFruits)
    .Select("$.id") // 1 of 3 different ways to specify a selector function
    .ToArray();
Джефф Меркадо
источник
14

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/

July.Tech
источник
Очень признателен. Я только вхожу в ES6, так что это может быть удобно!
Крис Невилл
10

Вы также можете попробовать linq.js

В linq.jsвашем

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

будет

Enumerable.From(selectedFruits).Select(function (fruit) { return fruit.id;  });
Аник Ислам Абхи
источник
4

Я создал библиотеку Linq для TypeScript в TsLinq.codeplex.com, которую вы также можете использовать для простого javascript. Эта библиотека в 2-3 раза быстрее Linq.js и содержит модульные тесты для всех методов Linq. Может быть, вы могли бы рассмотреть это.

Майкл Баарц
источник
2

Взгляните на underscore.js, который предоставляет много linq-подобных функций. В приведенном вами примере вы использовали бы функцию карты.

Грубый кролик
источник
1
Если кто-то хочет узнать, как они сравниваются, я сделал сообщение в блоге, в котором объясняется разница между более чем 15 самыми популярными функциями LINQ / underscore.js: vladopandzic.com/javascript/comparing-underscore-js-with-linq
Владо Панджич,
0

Dinqyjs имеет синтаксис, похожий на linq, и обеспечивает полифилы для таких функций, как map и indexOf, и был специально разработан для работы с массивами в Javascript.

garryp
источник
0

Взгляните на бегло , он поддерживает почти все, что делает LINQ и основан на итерируемости - так что он работает с картами, функциями генератора, массивами, всем итеративным.

kataik
источник
0

Наиболее похожим Selectаналогом C # будет mapфункция. Просто используйте:

var ids = selectedFruits.map(fruit => fruit.id);

выбрать все идентификаторы из selectedFruitsмассива.

Не требует никаких внешних зависимостей, только чистый JavaScript. Вы можете найти mapдокументацию здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Кирилл
источник
-1

Я отвечаю на название вопроса, а не на исходный вопрос, который был более конкретным.

Благодаря новым возможностям Javascript, таким как итераторы, функции и объекты-генераторы, становится возможным что-то вроде LINQ for Javascript. Обратите внимание, что linq.js, например, использует совершенно другой подход, используя регулярные выражения, вероятно, чтобы преодолеть отсутствие поддержки в языке в то время.

Учитывая это, я написал библиотеку LINQ для Javascript, и вы можете найти ее по адресу https://github.com/Siderite/LInQer . Комментарии и обсуждение на https://siderite.dev/blog/linq-in-javascript-linqer .

Из предыдущих ответов только Manipula кажется тем, чего можно ожидать от порта LINQ в Javascript.

Сидерит Zackwehdex
источник