JavaScript: filter () dla Objects

185

ECMAScript 5 ma filter()prototyp dla Arraytypów, ale nie Objecttypów, jeśli dobrze rozumiem.

Jak zaimplementować filter()for Objectw JavaScript?

Powiedzmy, że mam ten obiekt:

var foo = {
    bar: "Yes"
};

I chcę napisać, filter()który działa na 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;
};

Działa to, gdy używam go w poniższej wersji demonstracyjnej, ale kiedy dodam go do mojej witryny korzystającej z jQuery 1.5 i jQuery UI 1.8.9, otrzymuję błędy JavaScript w FireBug.

AgileMeansDoAsLittleAsPossible
źródło
Jakie konkretnie błędy się pojawiają?
NT3RP
Jakie masz błędy? Opublikuj je, jeśli to możliwe :)
Zack The Human
Jest trochę niejednoznacznej historii napisanej w jQuery i skryptach Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh
dokładnie to, czego potrzebowałem, z wyjątkiem tego, że musisz usunąć „!” w predykacie! (ten [klawisz]), aby mieć prawdziwą metodę filtrowania.
NoxFly,

Odpowiedzi:

201

Nigdy nie przedłużaj Object.prototype.

W twoim kodzie pojawią się okropne rzeczy. Wszystko się zepsuje. Rozszerzasz wszystkie typy obiektów, w tym literały obiektów.

Oto szybki przykład, który możesz wypróbować:

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

Zamiast tego utwórz funkcję, którą przekazujesz obiekt.

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;
};
użytkownik113716
źródło
61
@patrick: daj mężczyźnie chleb, a będziesz go karmić przez jeden dzień, naucz go, jak piec, a będziesz go karmić przez całe życie (lub coś takiego, jestem duński, nie znam poprawnych angielskich powiedzeń ;)
Martin Jespersen
13
Robisz to źle ...! Predicate (obj [klawisz]) powinien być predykatem (obj [klucz])
pirrotechnick
6
@pyrotechnick: Nie. Po pierwsze, głównym celem odpowiedzi jest nie rozszerzanie Object.prototype, ale po prostu umieszczanie tej funkcji Object. Po drugie, jest to kod PO . Najwyraźniej intencją OP jest .filter()takie filtrowanie pozytywnych wyników. Innymi słowy, jest to filtr ujemny, w którym dodatnia wartość zwracana oznacza, że ​​jest wykluczona z wyniku. Jeśli spojrzysz na przykład jsFiddle, odfiltrowuje on istniejące właściwości undefined.
user113716,
4
@patrick dw: Nie. Po pierwsze, nie wspominałem o rozszerzaniu / nie przedłużaniu prototypów. Po drugie, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - „Tworzy nową tablicę ze wszystkimi elementami, które pomyślnie przeszły test zaimplementowany przez podaną funkcję.” Wdrożenie dokładnego przeciwieństwa w globalnym wydaje się dość głupie, prawda?
pirotechnika
8
@pyrotechnick: Zgadza się, nie wspominałeś o rozszerzaniu / nie przedłużaniu prototypów i o to mi chodzi. Powiedziałeś, że robię to źle, ale jedynym „tym”, co robię, jest mówienie OP, żeby nie przedłużała Object.prototype. Z pytania: „Działa to ..., ale kiedy dodam go do mojej witryny ..., dostaję błędy JavaScript”. Jeśli OP zdecyduje się zastosować .filter()odwrotne zachowanie Array.prototpe.filter, to zależy od niego. Zostaw komentarz pod pytaniem, jeśli chcesz powiadomić OP, że kod jest nieprawidłowy, ale nie mów mi, że robię to źle, jeśli nie jest to mój kod.
user113716,
281

Przede wszystkim przedłużanie uważa się za złą praktykęObject.prototype . Zamiast podać funkcję jako funkcji użyteczności na Object, jak już tam są Object.keys, Object.assign, Object.is, ... etc.

Podaję tutaj kilka rozwiązań:

  1. Korzystanie reduceiObject.keys
  2. Jak (1), w połączeniu z Object.assign
  3. Używanie mapi rozkładanie składni zamiastreduce
  4. Korzystanie Object.entriesiObject.fromEntries

1. Używanie reduceiObject.keys

Za pomocą reducei Object.keysaby zaimplementować żądany filtr (używając składni strzałek 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);

Zauważ, że w powyższym kodzie predicatemusi być warunek włączenia (w przeciwieństwie do warunku wyłączenia zastosowany OP), aby był zgodny z tym, jak Array.prototype.filterdziała.

2. Jak (1) w połączeniu z Object.assign

W powyższym rozwiązaniu operator przecinka jest używany w reduceczęści, aby zwrócić zmutowany resobiekt. Można to oczywiście zapisać jako dwie wypowiedzi zamiast jednego wyrażenia, ale to drugie jest bardziej zwięzłe. Aby to zrobić bez operatora przecinek, można użyć Object.assignzamiast tego, który ma powrócić zmutowaną obiektu:

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. Używanie mapi rozkładanie składni zamiastreduce

Tutaj przenosimy Object.assignwywołanie z pętli, więc jest ono wykonywane tylko raz i przekazujemy poszczególne klucze jako osobne argumenty (przy użyciu składni rozszerzonej ):

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. Używanie Object.entriesiObject.fromEntries

Ponieważ rozwiązanie przekształca obiekt w tablicę pośrednią, a następnie przekształca go z powrotem w zwykły obiekt, przydatne byłoby skorzystanie z Object.entries(ES2017) i odwrotnie (tj. Utworzenie obiektu z tablicy par klucz / wartość ) za pomocą Object.fromEntries( ES2019).

Prowadzi to do tej metody „one-liner” na 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);

Funkcja predykatu otrzymuje tutaj argument klucz / wartość, co jest nieco inne, ale pozwala na więcej możliwości w logice funkcji predykatu.

trincot
źródło
Czy może to być bardziej złożone zapytanie? Na przykład:x => x.Expression.Filters.Filter
IamStalker,
2
@IstStalker, próbowałeś? To nie ma znaczenia, o ile podasz poprawną funkcję w drugim argumencie. Uwaga: Nie mam pojęcia, co .Filterjest na końcu, ale jeśli jest to funkcja, musisz ją wywołać ( x => x.Expression.Filters.Filter())
trincot
1
Nowsze funkcje mogą powodować mniej kodu, ale także zmniejszają wydajność . Kod zgodny z Ed 3 działa ponad dwukrotnie szybciej w większości przeglądarek.
RobG
1
Wersja TypeScript ostatniego wariantu: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
Oliver Joseph Ash
1
Dobra robota! Dziękuję za te wspaniałe wyjaśnienia i przykłady.
Slavik Meltser
21

Jeśli chcesz użyć podkreślenia lub lodash , możesz użyć pick(lub odwrotnie omit).

Przykłady z dokumentów podkreślenia:

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

Lub z oddzwanianiem (dla lodash , użyj pickBy ):

_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
  return _.isNumber(value);
});
// {age: 50}
Bogdan D.
źródło
1
lodash jest złym rozwiązaniem, ponieważ filtrowanie pustych obiektów spowoduje również usunięcie liczb.
mibbit
Właśnie do tego użyłem lodash i jest to świetne rozwiązanie. Dzięki @Bogdan D!
Ira Herman
@mibbit czy możesz być bardziej szczegółowy? Uważam, że to tylko kwestia prawidłowego wdrożenia funkcji oddzwaniania
Bogdan D
11

Podejście ES6 ...

Wyobraź sobie, że masz ten obiekt poniżej:

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

Utwórz funkcję:

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

I nazwij to:

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

i zwróci :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  }
}
Alireza
źródło
1
Wygląda dobrze! Ale dlaczego zwraca tylko drugi obiekt (a nie ten o nazwie / filterValue „Alireza”)?
Pille,
1
@Pille, OP poprosił, aby tak było (zwróć uwagę na filtr ujemny z !predicateich własnym kodem).
trincot
7

Jak już stwierdził Patrick, jest to zły pomysł, ponieważ prawie na pewno złamie dowolny kod innej firmy, którego kiedykolwiek będziesz chciał użyć.

Wszystkie biblioteki, takie jak jquery lub prototyp, ulegną awarii, jeśli się rozszerzysz Object.prototype. Powodem jest to, że leniwa iteracja obiektów (bez hasOwnPropertysprawdzania) ulegnie awarii, ponieważ dodane funkcje będą częścią iteracji.

Martin Jespersen
źródło
gorąco głosował za jasne i zwięzłe wyjaśnienie „dlaczego” tego złego pomysłu.
Michael Liquori,
5

Co powiesz na:

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

Lub...

function filterObj(keys, obj) {
  const newObj = {};
  Object.keys(obj).forEach(key => {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  });
  return newObj;
}
Shaunw
źródło
4

Dany

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

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

następnie :

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

Abdennour TOUMI
źródło
3
Nie korzysta z danej funkcji predykatu, jak wymaga tego pytanie.
trincot
4

Stworzyłem taki, Object.filter()który nie tylko filtruje według funkcji, ale także akceptuje tablicę kluczy do włączenia. Opcjonalny trzeci parametr umożliwia odwrócenie filtru.

Dany:

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

Szyk:

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

Funkcjonować:

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

Kod

Oświadczenie : postanowiłem użyć rdzenia Ext JS dla zwięzłości. Nie czułem, że konieczne jest pisanie kontrolerów typów dla typów obiektów, ponieważ nie było to częścią pytania.

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

Pan Polywhirl
źródło
Proszę sprawdzić moją waniliową odpowiedź i treść
Z. Khullah
1
@trincot Dzięki, zaktualizowałem odpowiedź, aby zwrócić kopię obiektu, a nie zwrot w miejscu.
Pan Polywhirl
2

Moje wymyślone rozwiązanie:

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
}

Przypadki testowe:

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

Z. Khullah
źródło
2

Rozwiązanie w Vanilla JS od roku 2020.


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

Możesz filtrować romNumbersobiekt według klucza:

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

Lub filtruj romNumbersobiekt według wartości:

 const filteredByValue = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => value === 5))
 // filteredByValue = {V: 5} 
Qui-Gon Jinn
źródło
1

Jeśli chcesz zmutować ten sam obiekt, a nie utworzyć nowy.

Poniższy przykład usunie wszystkie 0 lub puste wartości:

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);
yairniz
źródło
0

Jak wszyscy mówili, nie psuj prototypu. Zamiast tego po prostu napisz funkcję, aby to zrobić. Oto moja wersja z 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;
};
saran3h
źródło
0

Używam tego, kiedy go potrzebuję:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Анна
źródło
-1

W takich przypadkach używam jquery $ .map, która może obsługiwać obiekty. Jak wspomniano w innych odpowiedziach, zmiana natywnych prototypów nie jest dobrą praktyką ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes )

Poniżej znajduje się przykład filtrowania tylko poprzez sprawdzenie właściwości obiektu. Zwraca własny obiekt, jeśli warunek jest spełniony, lub zwraca, undefinedjeśli nie. undefinedNieruchomość sprawi, że rekord zniknie z listy obiektów;

$.map(yourObject, (el, index)=>{
    return el.yourProperty ? el : undefined;
});
Marcel Kohls
źródło
1
$.mapmoże przyjąć obiekt, ale zwraca tablicę, więc oryginalne nazwy właściwości zostaną utracone. OP potrzebuje przefiltrowanego zwykłego obiektu.
trincot