JavaScript: filter () per gli oggetti


187

ECMAScript 5 ha il filter()prototipo per i Arraytipi, ma non per i Objecttipi, se ho capito bene.

Come implementerei a filter()for Objects in JavaScript?

Diciamo che ho questo oggetto:

var foo = {
    bar: "Yes"
};

E voglio scrivere un filter()che funziona su Objects:

Object.prototype.filter = function(predicate) {
    var result = {};

    for (key in this) {
        if (this.hasOwnProperty(key) && !predicate(this[key])) {
            result[key] = this[key];
        }
    }

    return result;
};

Funziona quando lo uso nella seguente demo, ma quando lo aggiungo al mio sito che utilizza jQuery 1.5 e jQuery UI 1.8.9, ottengo errori JavaScript in FireBug.


Quali errori ricevi, in particolare?
NT3RP,

Quali sono gli errori che stai riscontrando? Pubblicali se possibile :)
Zack The Human,

C'è un po 'di storia ambigua tra jQuery e script che si estendono Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh

esattamente ciò di cui avevo bisogno, tranne per il fatto che è necessario rimuovere il "!" nel predicato! (questo [tasto]) per avere il metodo di filtro reale.
NoxFly

Risposte:


201

Mai e poi mai estendere Object.prototype.

Accadranno cose orribili al tuo codice. Le cose si romperanno. Stai estendendo tutti i tipi di oggetto, compresi i letterali degli oggetti.

Ecco un breve esempio che puoi provare:

    // Extend Object.prototype
Object.prototype.extended = "I'm everywhere!";

    // See the result
alert( {}.extended );          // "I'm everywhere!"
alert( [].extended );          // "I'm everywhere!"
alert( new Date().extended );  // "I'm everywhere!"
alert( 3..extended );          // "I'm everywhere!"
alert( true.extended );        // "I'm everywhere!"
alert( "here?".extended );     // "I'm everywhere!"

Invece crea una funzione che passi l'oggetto.

Object.filter = function( obj, predicate) {
    let result = {}, key;

    for (key in obj) {
        if (obj.hasOwnProperty(key) && !predicate(obj[key])) {
            result[key] = obj[key];
        }
    }

    return result;
};

61
@patrick: dai un pane a un uomo e gli darai da mangiare per un giorno, gli insegnerai come cuocere e gli darai da mangiare per tutta la vita (o qualcosa del genere, sono danese, non conosco i corretti detti inglesi ;)
Martin Jespersen,

13
Stai sbagliando ...! Predicato (obj [chiave]) dovrebbe essere predicato (obj [chiave])
pyrotechnick

6
@pyrotechnick: No. Innanzitutto, il punto principale della risposta è non estendere Object.prototype, ma piuttosto semplicemente posizionare la funzione Object. Secondo, questo è il codice del PO . Chiaramente l'intenzione di OP è quella di .filter()essere tale da filtrare i risultati positivi. In altre parole, è un filtro negativo, in cui un valore di ritorno positivo significa che è escluso dal risultato. Se guardi l'esempio jsFiddle, sta filtrando le proprietà esistenti che lo sono undefined.
user113716

4
@patrick dw: No. Innanzitutto, non ho menzionato l'estensione / non estensione dei prototipi. In secondo luogo, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - "Crea un nuovo array con tutti gli elementi che superano il test implementato dalla funzione fornita." L'implementazione dell'esatto contrario su un globale sembra piuttosto sciocca, no?
pyrotechnick,

8
@pyrotechnick: Esatto, non hai menzionato l'estensione o la non estensione dei prototipi, e questo è il mio punto. Hai detto che lo sto facendo male, ma l'unico "cosa" che sto facendo è dire a OP di non estenderlo Object.prototype. Dalla domanda: "Funziona ..., ma quando lo aggiungo al mio sito ..., ottengo errori JavaScript" Se OP decide di implementare un .filter()comportamento opposto rispetto a quello di Array.prototpe.filter, dipende da lui / lei. Si prega di lasciare un commento sotto la domanda se si desidera notificare a OP che il codice è errato, ma non dirmi che sto sbagliando quando non è il mio codice.
user113716

284

Prima di tutto, è considerata una cattiva pratica da estendereObject.prototype . Invece, fornire il proprio caratteristica come funzione di utilità su Object, proprio come ci sono già Object.keys, Object.assign, Object.is, ... ecc.

Fornisco qui diverse soluzioni:

  1. Utilizzando reduceeObject.keys
  2. Come (1), in combinazione con Object.assign
  3. Utilizzo mape diffusione della sintassi anzichéreduce
  4. Utilizzando Object.entrieseObject.fromEntries

1. Utilizzo di reduceeObject.keys

Con reducee Object.keysper implementare il filtro desiderato (usando la sintassi della freccia ES6 ):

Object.filter = (obj, predicate) => 
    Object.keys(obj)
          .filter( key => predicate(obj[key]) )
          .reduce( (res, key) => (res[key] = obj[key], res), {} );

// Example use:
var scores = {
    John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1); 
console.log(filtered);

Si noti che nel codice sopra riportato predicatedeve essere una condizione di inclusione (contrariamente alla condizione di esclusione utilizzata dall'OP), in modo che sia in linea con il modo in cui Array.prototype.filterfunziona.

2. As (1), in combinazione con Object.assign

Nella soluzione precedente l' operatore virgola viene utilizzato nella reduceparte per restituire l' resoggetto mutato . Questo potrebbe ovviamente essere scritto come due affermazioni anziché come un'espressione, ma quest'ultima è più concisa. Per farlo senza l'operatore virgola, è possibile utilizzare Object.assign, invece, che non restituisce l'oggetto mutato:

Object.filter = (obj, predicate) => 
    Object.keys(obj)
          .filter( key => predicate(obj[key]) )
          .reduce( (res, key) => Object.assign(res, { [key]: obj[key] }), {} );

// Example use:
var scores = {
    John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1); 
console.log(filtered);

3. Utilizzo mape diffusione della sintassi anzichéreduce

Qui spostiamo la Object.assignchiamata fuori dal ciclo, quindi viene fatta una sola volta e passiamo i singoli tasti come argomenti separati (usando la sintassi di diffusione ):

Object.filter = (obj, predicate) => 
    Object.assign(...Object.keys(obj)
                    .filter( key => predicate(obj[key]) )
                    .map( key => ({ [key]: obj[key] }) ) );

// Example use:
var scores = {
    John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1); 
console.log(filtered);

4. Utilizzo di Object.entrieseObject.fromEntries

Poiché la soluzione traduce l'oggetto in un array intermedio e poi lo converte in un oggetto semplice, sarebbe utile utilizzare Object.entries(ES2017) e l'opposto (ovvero creare un oggetto da un array di coppie chiave / valore ) con Object.fromEntries( ES2019).

Conduce a questo metodo "one-liner" su Object:

Object.filter = (obj, predicate) => 
                  Object.fromEntries(Object.entries(obj).filter(predicate));

// Example use:
var scores = {
    John: 2, Sarah: 3, Janet: 1
};

var filtered = Object.filter(scores, ([name, score]) => score > 1); 
console.log(filtered);

La funzione predicato ottiene qui una coppia chiave / valore come argomento, che è un po 'diverso, ma consente maggiori possibilità nella logica della funzione predicato.


Potrebbe essere una query più complessa? Ad esempio:x => x.Expression.Filters.Filter
IamStalker,

2
@IamStalker, hai provato? Non importa, purché si fornisca una funzione valida nel secondo argomento. NB: Non ho idea di cosa ci .Filtersia alla fine, ma se è una funzione, devi chiamarla ( x => x.Expression.Filters.Filter())
trincot

1
Le funzionalità più recenti possono ridurre il codice, ma anche rallentare le prestazioni . Il codice conforme a Ed 3 viene eseguito più del doppio della velocità nella maggior parte dei browser.
RobG


1
Ottimo lavoro! Grazie per queste grandi spiegazioni ed esempi.
Slavik Meltser,

21

Se sei disposto a usare il carattere di sottolineatura o lodash , puoi usare pick(o il suo contrario omit).

Esempi dai documenti di underscore:

_.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
// {name: 'moe', age: 50}

O con un callback (per lodash , usa pickBy ):

_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
  return _.isNumber(value);
});
// {age: 50}

1
lodash è una cattiva soluzione perché il filtro per oggetti vuoti rimuoverà anche i numeri.
mibbit

Ho appena usato Lodash per questo ed è un'ottima soluzione. Grazie @Bogdan D!
Ira Herman,

@mibbit puoi essere più specifico? Credo che sia solo una questione di implementare correttamente il callback
Bogdan D

11

Approccio ES6 ...

Immagina di avere questo oggetto qui sotto:

const developers = {
  1: {
   id: 1,
   name: "Brendan", 
   family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  },  
  3: {
   id: 3,
   name: "Alireza", 
   family: "Dezfoolian"
 }
};

Crea una funzione:

const filterObject = (obj, filter, filterValue) => 
   Object.keys(obj).reduce((acc, val) => 
   (obj[val][filter] === filterValue ? acc : {
       ...acc,
       [val]: obj[val]
   }                                        
), {});

E chiamalo:

filterObject(developers, "name", "Alireza");

e restituirà :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  }
}

1
Sembra buono! Ma perché restituisce solo l'altro oggetto (e non quello con il nome / filtroValore di "Alireza")?
Pille,

1
@Pille, l'OP ha chiesto che fosse così (notare il filtro negativo con !predicateil proprio codice).
trincot

7

Come già affermato da Patrick, questa è una cattiva idea, poiché quasi sicuramente romperà qualsiasi codice di terze parti che potresti mai desiderare di utilizzare.

Tutte le librerie come jquery o prototipo si romperanno se si estende Object.prototype, il motivo è che l'iterazione pigra sugli oggetti (senza hasOwnPropertycontrolli) si interromperà poiché le funzioni aggiunte faranno parte dell'iterazione.


votato per aver spiegato il "perché" di questa cattiva idea in modo chiaro e conciso.
Michael Liquori,

5

Che ne dite di:

function filterObj(keys, obj) {
  const newObj = {};
  for (let key in obj) {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

O...

function filterObj(keys, obj) {
  const newObj = {};
  Object.keys(obj).forEach(key => {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  });
  return newObj;
}

4

Dato

object = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};

keys = ['firstname', 'age'];

poi :

keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
// {firstname:'abd', age: 16}


3
Questo non utilizza una determinata funzione predicato come richiesto dalla domanda.
trincot

4

Ho creato un oggetto Object.filter()che non solo filtra per funzione, ma accetta anche una serie di chiavi da includere. Il terzo parametro opzionale ti permetterà di invertire il filtro.

Dato:

var foo = {
    x: 1,
    y: 0,
    z: -1,
    a: 'Hello',
    b: 'World'
}

Vettore:

Object.filter(foo, ['z', 'a', 'b'], true);

Funzione:

Object.filter(foo, function (key, value) {
    return Ext.isString(value);
});

Codice

Disclaimer : ho scelto di utilizzare Ext JS core per brevità. Non pensavo fosse necessario scrivere i controlli di tipo per i tipi di oggetto in quanto non faceva parte della domanda.

// Helper function
function print(obj) {
    document.getElementById('disp').innerHTML += JSON.stringify(obj, undefined, '  ') + '<br />';
    console.log(obj);
}

Object.filter = function (obj, ignore, invert) {
    let result = {}; // Returns a filtered copy of the original list
    if (ignore === undefined) {
        return obj;   
    }
    invert = invert || false;
    let not = function(condition, yes) { return yes ? !condition : condition; };
    let isArray = Ext.isArray(ignore);
    for (var key in obj) {
        if (obj.hasOwnProperty(key) &&
                !(isArray && not(!Ext.Array.contains(ignore, key), invert)) &&
                !(!isArray && not(!ignore.call(undefined, key, obj[key]), invert))) {
            result[key] = obj[key];
        }
    }
    return result;
};

let foo = {
    x: 1,
    y: 0,
    z: -1,
    a: 'Hello',
    b: 'World'
};

print(Object.filter(foo, ['z', 'a', 'b'], true));
print(Object.filter(foo, (key, value) => Ext.isString(value)));
#disp {
    white-space: pre;
    font-family: monospace
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/extjs/4.2.1/builds/ext-core.min.js"></script>
<div id="disp"></div>


Per favore, controlla la mia risposta alla vaniglia e l' essenza
Z. Khullah,

1
@trincot Grazie, ho aggiornato la risposta per restituire una copia dell'oggetto, anziché un ritorno sul posto.
Mr. Polywhirl

2

La mia soluzione supponente:

function objFilter(obj, filter, nonstrict){
  r = {}
  if (!filter) return {}
  if (typeof filter == 'string') return {[filter]: obj[filter]}
  for (p in obj) {
    if (typeof filter == 'object' &&  nonstrict && obj[p] ==  filter[p]) r[p] = obj[p]
    else if (typeof filter == 'object' && !nonstrict && obj[p] === filter[p]) r[p] = obj[p]
    else if (typeof filter == 'function'){ if (filter(obj[p],p,obj)) r[p] = obj[p]}
    else if (filter.length && filter.includes(p)) r[p] = obj[p]
  }
  return r
}

Casi test:

obj = {a:1, b:2, c:3}

objFilter(obj, 'a') // returns: {a: 1}
objFilter(obj, ['a','b']) // returns: {a: 1, b: 2}
objFilter(obj, {a:1}) // returns: {a: 1}
objFilter(obj, {'a':'1'}, true) // returns: {a: 1}
objFilter(obj, (v,k,o) => v%2===1) // returns: {a: 1, c: 3}

https://gist.github.com/bernardoadc/872d5a174108823159d845cc5baba337


2

Soluzione in Vanilla JS dall'anno 2020.


let romNumbers={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}

Puoi filtrare l' romNumbersoggetto per chiave:

const filteredByKey = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => key === 'I'))
// filteredByKey = {I: 1} 

O filtra l' romNumbersoggetto per valore:

 const filteredByValue = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => value === 5))
 // filteredByValue = {V: 5} 

1

Se si desidera mutare lo stesso oggetto anziché crearne uno nuovo.

L'esempio seguente eliminerà tutti 0 o valori vuoti:

const sev = { a: 1, b: 0, c: 3 };
const deleteKeysBy = (obj, predicate) =>
  Object.keys(obj)
    .forEach( (key) => {
      if (predicate(obj[key])) {
        delete(obj[key]);
      }
    });

deleteKeysBy(sev, val => !val);

0

Come tutti hanno detto, non scherzare con il prototipo. Invece, basta scrivere una funzione per farlo. Ecco la mia versione con lodash:

import each from 'lodash/each';
import get from 'lodash/get';

const myFilteredResults = results => {
  const filteredResults = [];

  each(results, obj => {
    // filter by whatever logic you want.

    // sample example
    const someBoolean = get(obj, 'some_boolean', '');

    if (someBoolean) {
      filteredResults.push(obj);
    }
  });

  return filteredResults;
};

0

Lo uso quando ne ho bisogno:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}

-1

In questi casi uso jquery $ .map, che può gestire oggetti. Come menzionato in altre risposte, non è una buona pratica cambiare i prototipi nativi ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes )

Di seguito è riportato un esempio di filtro semplicemente controllando alcune proprietà dell'oggetto. Restituisce il proprio oggetto se la tua condizione è vera o restituisce in undefinedcaso contrario. La undefinedproprietà farà scomparire quel record dal tuo elenco di oggetti;

$.map(yourObject, (el, index)=>{
    return el.yourProperty ? el : undefined;
});

1
$.mappuò prendere un oggetto, ma restituisce un array, quindi i nomi delle proprietà originali vanno persi. L'OP necessita di un oggetto semplice filtrato.
trincot
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.