JavaScript: filter () für Objekte


185

ECMAScript 5 hat den filter()Prototyp für ArrayTypen, aber keine ObjectTypen, wenn ich das richtig verstehe.

Wie würde ich ein filter()for Objects in JavaScript implementieren ?

Angenommen, ich habe dieses Objekt:

var foo = {
    bar: "Yes"
};

Und ich möchte eine schreiben filter(), die auf Objects funktioniert :

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

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

    return result;
};

Dies funktioniert, wenn ich es in der folgenden Demo verwende. Wenn ich es jedoch zu meiner Site hinzufüge, die jQuery 1.5 und jQuery UI 1.8.9 verwendet, werden in FireBug JavaScript-Fehler angezeigt.


Welche Fehler bekommen Sie speziell?
NT3RP

Was sind die Fehler, die Sie bekommen? Poste sie wenn möglich :)
Zack The Human

Es gibt ein bisschen mehrdeutige Geschichte in Bezug auf jQuery und Skripte, die sich erweitern Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh

genau das, was ich brauchte, außer dass Sie das "!" im! Prädikat (dieser [Schlüssel]), um die echte Filtermethode zu haben.
NoxFly

Antworten:


199

Niemals verlängern Object.prototype.

Schreckliche Dinge werden mit Ihrem Code passieren. Die Dinge werden brechen. Sie erweitern alle Objekttypen, einschließlich Objektliterale.

Hier ist ein kurzes Beispiel, das Sie ausprobieren können:

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

Erstellen Sie stattdessen eine Funktion, mit der Sie das Objekt übergeben.

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

60
@patrick: Gib einem Mann ein Brot und du fütterst ihn einen Tag lang, bringst ihm das Backen bei und du fütterst ihn ein Leben lang (oder so, ich bin dänisch, ich kenne nicht die richtigen englischen Sprüche ;)
Martin Jespersen

13
Du machst es falsch ...! Prädikat (obj [Schlüssel]) sollte Prädikat (obj [Schlüssel]) sein
Pyrotechnik

6
@pyrotechnick: Nein. Erstens besteht der Hauptpunkt der Antwort darin, nicht zu erweitern Object.prototype, sondern nur die Funktion zu platzieren Object. Zweitens ist dies der OP-Code . Offensichtlich OP Absicht ist zu haben .filter()sein , so dass es filtert die positiven Ergebnisse. Mit anderen Worten, es ist ein negativer Filter, bei dem ein positiver Rückgabewert bedeutet, dass er vom Ergebnis ausgeschlossen ist. Wenn Sie sich das Beispiel jsFiddle ansehen, filtert er vorhandene Eigenschaften heraus undefined.
user113716

4
@patrick dw: Nein. Zuerst habe ich nicht erwähnt, Prototypen zu erweitern / nicht zu erweitern. Zweitens: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - "Erstellt ein neues Array mit allen Elementen, die den von der bereitgestellten Funktion implementierten Test bestehen." Das genaue Gegenteil auf einer globalen Ebene zu implementieren, scheint ziemlich dumm zu sein, nicht wahr?
Pyrotechnik

7
@pyrotechnick: Richtig, Sie haben nicht erwähnt, Prototypen zu erweitern / nicht zu erweitern, und das ist mein Punkt. Sie sagten, ich mache es falsch, aber das einzige "es", das ich mache, ist, OP zu sagen, dass es nicht verlängern soll Object.prototype. Aus der Frage: "Das funktioniert ... aber wenn ich es meiner Site hinzufüge ... erhalte ich JavaScript-Fehler" Wenn OP beschließt, ein .filter()mit dem entgegengesetzten Verhalten von zu implementieren Array.prototpe.filter, liegt es an ihm / ihr. Bitte hinterlassen Sie einen Kommentar unter der Frage, wenn Sie OP benachrichtigen möchten, dass der Code falsch ist, aber sagen Sie mir nicht, dass ich es falsch mache, wenn es nicht mein Code ist.
user113716

276

Zuallererst wird es als schlechte Praxis angesehen, zu verlängernObject.prototype . Stattdessen geben Sie Ihre Funktion als Nutzenfunktion auf Object, so wie es bereits sind Object.keys, Object.assign, Object.is, ... etc.

Ich biete hier verschiedene Lösungen an:

  1. Verwenden von reduceundObject.keys
  2. Wie (1) in Kombination mit Object.assign
  3. Verwenden mapund Verbreiten der Syntax anstelle vonreduce
  4. Verwenden von Object.entriesundObject.fromEntries

1. Verwenden von reduceundObject.keys

Mit reduceund Object.keyszum Implementieren des gewünschten Filters (unter Verwendung der ES6- Pfeilsyntax ):

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

Beachten Sie, dass der obige Code predicateeine Einschlussbedingung sein muss (im Gegensatz zu der Ausschlussbedingung , die das OP verwendet), damit sie mit der Funktionsweise Array.prototype.filterübereinstimmt.

2. Wie (1) in Kombination mit Object.assign

In der obigen Lösung wird der Kommaoperator in der verwendetreduce Teil verwendet, um das mutierte resObjekt zurückzugeben. Dies könnte natürlich als zwei Aussagen anstelle eines Ausdrucks geschrieben werden, aber letzterer ist prägnanter. Um es ohne Komma - Operator zu tun, könnten Sie Object.assignstattdessen, die nicht das mutierte Objekt zurückgeben:

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. Verwenden mapund verbreiten Sie die Syntax anstelle vonreduce

Hier verschieben wir den Object.assignAufruf aus der Schleife, sodass er nur einmal ausgeführt wird, und übergeben ihm die einzelnen Schlüssel als separate Argumente (unter Verwendung der Spread-Syntax ):

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. Verwenden von Object.entriesundObject.fromEntries

Da die Lösung das Objekt in ein Zwischenarray übersetzt und dieses dann wieder in ein einfaches Objekt konvertiert, wäre es nützlich, es zu verwenden Object.entries (ES2017) und das Gegenteil (dh ein Objekt aus einem Array von Schlüssel / Wert-Paaren erstellen ) mit Object.fromEntries( ES2019).

Es führt zu dieser "Einzeiler" -Methode auf 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);

Die Prädikatfunktion erhält hier ein Schlüssel / Wert-Paar als Argument, was etwas anders ist, aber mehr Möglichkeiten in der Logik der Prädikatfunktion bietet.


Könnte es eine komplexere Abfrage sein? Zum Beispiel:x => x.Expression.Filters.Filter
IamStalker

2
@IamStalker, hast du es versucht? Es spielt keine Rolle, solange Sie im zweiten Argument eine gültige Funktion angeben. NB: Ich habe keine Ahnung, was .Filteram Ende ist, aber wenn es eine Funktion ist, müssen Sie es ( x => x.Expression.Filters.Filter())
trincot

so lange! kann in 1 Zeile erfolgen. Überprüfen Sie dies
Abdennour TOUMI

1
Neuere Funktionen sorgen möglicherweise für weniger Code, aber auch für eine langsamere Leistung . Ed 3-kompatibler Code wird in den meisten Browsern mehr als doppelt so schnell ausgeführt.
RobG


20

Wenn Sie bereit sind, Unterstrich oder Lodash zu verwenden , können Sie pick(oder das Gegenteil omit) verwenden.

Beispiele aus den Dokumenten des Unterstrichs:

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

Oder mit einem Rückruf (z lodash verwenden Sie pickBy ):

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

1
lodash ist eine schlechte Lösung, da durch das Filtern nach leeren Objekten auch Zahlen entfernt werden.
Mibbit

Ich habe gerade lodash dafür verwendet und es ist eine großartige Lösung. Danke @Bogdan D!
Ira Herman

@mibbit kannst du genauer sein? Ich glaube, es geht nur darum, den Rückruf korrekt zu implementieren
Bogdan D

9

ES6- Ansatz ...

Stellen Sie sich vor, Sie haben dieses Objekt unten:

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

Erstellen Sie eine Funktion:

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

Und nenne es:

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

und wird zurückkehren :

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

1
Sieht gut aus! Aber warum gibt es nur das andere Objekt zurück (und nicht das mit dem Namen / Filterwert von "Alireza")?
Pille

1
@Pille, das OP hat darum gebeten, dass es so ist (beachten Sie den Negativfilter mit !predicatein ihrem eigenen Code).
Trincot

7

Wie Patrick bereits sagte, ist dies eine schlechte Idee, da dadurch mit ziemlicher Sicherheit jeder Code von Drittanbietern beschädigt wird, den Sie jemals verwenden möchten.

Alle Bibliotheken wie jquery oder prototype werden beim Erweitern unterbrochen. Object.prototypeDer Grund dafür ist, dass die verzögerte Iteration über Objekte (ohne hasOwnPropertyÜberprüfungen) unterbrochen wird, da die von Ihnen hinzugefügten Funktionen Teil der Iteration sind.


Es wurde dafür gestimmt, das „Warum“ einer schlechten Idee klar und präzise zu erklären.
Michael Liquori

5

Wie wäre es mit:

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

Oder...

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

4

Gegeben

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

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

dann :

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


2
Dies verwendet keine gegebene Prädikatfunktion, wie es die Frage erfordert.
Trincot

4

Ich habe eine erstellt Object.filter() die nicht nur nach einer Funktion filtert, sondern auch ein Array von Schlüsseln akzeptiert, die eingeschlossen werden sollen. Mit dem optionalen dritten Parameter können Sie den Filter invertieren.

Gegeben:

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

Array:

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

Funktion:

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

Code

Haftungsausschluss : Der Kürze halber habe ich mich für Ext JS Core entschieden. Ich hatte nicht das Gefühl, dass es notwendig war, Typprüfungen für Objekttypen zu schreiben, da dies nicht Teil der Frage war.

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


Bitte überprüfen Sie meine Vanille Antwort und Kern
Z. Khullah

1
@trincot Vielen Dank, ich habe die Antwort aktualisiert, um eine Kopie des Objekts anstelle einer direkten Rückgabe zurückzugeben.
Mr. Polywhirl

2

Meine meinungsgebundene Lösung:

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
}

Testfälle:

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


1

Wenn Sie dasselbe Objekt mutieren möchten, anstatt ein neues zu erstellen.

Im folgenden Beispiel werden alle 0 oder leeren Werte gelöscht:

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

1

Lösung in Vanilla JS ab dem Jahr 2020.


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

Sie können das romNumbersObjekt nach Schlüssel filtern :

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

Oder filtern Sie das romNumbersObjekt nach Wert:

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

0

Wie alle sagten, schrauben Sie nicht mit Prototypen herum. Schreiben Sie stattdessen einfach eine Funktion, um dies zu tun. Hier ist meine Version mit 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

Ich benutze dies, wenn ich es brauche:

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

-1

In diesen Fällen verwende ich die jquery $ .map, die Objekte verarbeiten kann. Wie in anderen Antworten erwähnt, ist es keine gute Vorgehensweise, native Prototypen zu ändern ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes) ).

Im Folgenden finden Sie ein Beispiel für das Filtern, indem Sie einige Eigenschaften Ihres Objekts überprüfen. Es gibt das eigene Objekt zurück, wenn Ihre Bedingung erfüllt ist, oder gibt es zurück, undefinedwenn nicht. Durch die undefinedEigenschaft wird dieser Datensatz aus Ihrer Objektliste entfernt.

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

1
$.mapkann ein Objekt aufnehmen, gibt jedoch ein Array zurück, sodass die ursprünglichen Eigenschaftsnamen verloren gehen. Das OP benötigt ein gefiltertes einfaches Objekt.
Trincot
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.