Javascript equivalente a C # LINQ Select

137

Seguindo esta pergunta aqui:

Usar a encadernação marcada no nocaute com uma lista de caixas de seleção marca todas as caixas de seleção

Eu criei algumas caixas de seleção usando knockout que permitem a seleção de uma matriz. violino de trabalho retirado do post acima:

http://jsfiddle.net/NsCXJ/

Existe uma maneira simples de criar uma matriz apenas dos IDs da fruta?

Estou mais à vontade com o C #, onde faria algo ao longo das linhas de selectedFruits.select(fruit=>fruit.id);

Existe algum método / função pronta para fazer algo semelhante com javascript / jquery? Ou a opção mais simples seria percorrer a lista e criar uma segunda matriz? Pretendo postar a matriz de volta no servidor em JSON, por isso estou tentando minimizar os dados enviados.

Chris Nevill
fonte

Respostas:

227

Sim, Array.map () ou $ .map () fazem a mesma coisa.

//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/

Como o array.map não é suportado em navegadores antigos, sugiro que você siga o método jQuery.

Se você preferir o outro por algum motivo, sempre poderá adicionar um polyfill para suporte ao navegador antigo.

Você também pode adicionar métodos personalizados ao protótipo da matriz:

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;
});

Uma versão estendida que usa o construtor da função se você passar uma string. Algo para brincar, talvez:

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/

Atualizar:

Como essa se tornou uma resposta tão popular, estou adicionando meu where()+ semelhante firstOrDefault(). Eles também podem ser usados ​​com a abordagem do construtor de função baseada em string (que é a mais rápida), mas aqui está outra abordagem usando um literal de objeto como filtro:

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;
};

Uso:

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' }); 

Aqui está um teste jsperf para comparar a velocidade literal do construtor de funções versus a velocidade do objeto. Se você decidir usar o primeiro, lembre-se de citar as strings corretamente.

Minha preferência pessoal é usar as soluções baseadas em objetos literais ao filtrar 1-2 propriedades e passar uma função de retorno de chamada para uma filtragem mais complexa.

Terminarei com duas dicas gerais ao adicionar métodos a protótipos de objetos nativos:

  1. Verifique a ocorrência de métodos existentes antes de substituir, por exemplo:

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

  2. Se você não precisar oferecer suporte ao IE8 e abaixo, defina os métodos usando Object.defineProperty para torná-los não enumeráveis. Se alguém usado for..inem uma matriz (o que está errado em primeiro lugar), ele também iterará inúmeras propriedades. Apenas um aviso.

Johan
fonte
1
@ChrisNevill Adicionei uma versão em string também no caso de você estar interessado
Johan
@MUlferts Boa captura, atualizada :). Atualmente, eu sugeriria o uso do lodash para esse tipo de tarefa. Eles expõem a mesma interface que o código acima
Johan
Para apoiar observáveis ​​knockout:return typeof item[property] === 'function' ? item[property]() === filter[property] : item[property] === filter[property];
Linus Caldwell
@LinusCaldwell Faz um longo tempo desde que usei o nocaute, mas e quanto a algo assim return ko.unwrap(item[property]) === filter[property]?
187 Johan Johan
Mencionei o nocaute, mas é claro que isso abrangeria todas as propriedades que são funções sem os parâmetros necessários. Além disso, por que alguém quebraria o estilo genérico do seu código bonito?
Linus Caldwell
33

Eu sei que é uma resposta tardia, mas foi útil para mim! Apenas para concluir, usando a $.grepfunção, você pode emular o 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; });
Stefano Altieri
fonte
é isso que eu quero ... mas o que é mais bom entre sua resposta e Enumerable.From (selectedFruits) .Select (function (fruit) {return fruit.id;});
Bharat
15

Como você está usando knockout, considere usar a função do utilitário knockout arrayMap()e outras funções do array array.

Aqui está uma lista de funções utilitárias de matriz e seus métodos LINQ equivalentes:

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)

Então, o que você poderia fazer no seu exemplo é o seguinte:

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

Se você deseja uma interface semelhante ao LINQ em javascript, pode usar uma biblioteca como linq.js, que oferece uma interface agradável para muitos dos métodos LINQ.

var mapped = Enumerable.From(selectedFruits)
    .Select("$.id") // 1 of 3 different ways to specify a selector function
    .ToArray();
Jeff Mercado
fonte
14

A maneira 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);
}

também em: https://jsfiddle.net/52dpucey/

July.Tech
fonte
Muito apreciado. Estou entrando no ES6, então isso pode ser útil!
precisa
10

Você também pode tentar linq.js

No linq.jsseu

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

será

Enumerable.From(selectedFruits).Select(function (fruit) { return fruit.id;  });
Anik Islam Abhi
fonte
4

Criei uma biblioteca Linq para TypeScript em TsLinq.codeplex.com que você também pode usar para javascript simples. Essa biblioteca é 2-3 vezes mais rápida que o Linq.js e contém testes de unidade para todos os métodos do Linq. Talvez você possa revisar essa.

Michael Baarz
fonte
0

O Dinqyjs possui uma sintaxe do tipo linq e fornece polyfills para funções como map e indexOf, e foi projetado especificamente para trabalhar com matrizes em Javascript.

garryp
fonte
0

Dê uma olhada no fluente , ele suporta quase tudo o que o LINQ faz e com base em iteráveis ​​- para que ele funcione com mapas, funções de gerador, matrizes, tudo que é iterável.

kataik
fonte
-1

Estou respondendo ao título da pergunta, e não à pergunta original, que era mais específica.

Com os novos recursos do Javascript, como iteradores e funções e objetos do gerador, algo como o LINQ for Javascript se torna possível. Observe que o linq.js, por exemplo, usa uma abordagem completamente diferente, usando expressões regulares, provavelmente para superar a falta de suporte no idioma da época.

Com isso dito, escrevi uma biblioteca LINQ para Javascript e você pode encontrá-la em https://github.com/Siderite/LInQer . Comentários e discussão em https://siderite.dev/blog/linq-in-javascript-linqer .

Das respostas anteriores, apenas Manipula parece ser o que seria de esperar de uma porta LINQ em Javascript.

Siderite Zackwehdex
fonte