JavaScript: фильтр () для объектов

187

ECMAScript 5 имеет filter()прототип для Arrayтипов, но не для Objectтипов, если я правильно понимаю.

Как бы я реализовать filter()для Objects в JavaScript?

Допустим, у меня есть этот объект:

var foo = {
    bar: "Yes"
};

И я хочу написать, filter()что работает на Objectс:

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

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

    return result;
};

Это работает, когда я использую его в следующей демонстрации, но когда я добавляю его на свой сайт, который использует jQuery 1.5 и jQuery UI 1.8.9, я получаю ошибки JavaScript в FireBug.

AgileMeansDoAsLittleAsPossible
источник
Какие ошибки вы получаете, в частности?
NT3RP
Какие ошибки вы получаете? Опубликуйте их, если это возможно :)
Зак Человек
Существует немного неоднозначной истории относительно jQuery и расширенных сценариев Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh
именно то, что мне нужно, кроме того, что вы должны удалить "!" в предикате! (этот [ключ]) иметь реальный метод фильтра.
NoxFly

Ответы:

201

Никогда не расширяйся Object.prototype.

Ужасные вещи будут происходить с вашим кодом. Вещи сломаются. Вы расширяете все типы объектов, включая литералы объектов.

Вот быстрый пример, который вы можете попробовать:

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

Вместо этого создайте функцию, которую вы передаете объекту.

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;
};
user113716
источник
61
@patrick: дай человеку хлеб, и ты накормишь его на день, научишь его выпекать и накормишь его на всю жизнь (или что-то, я датчанин, я не знаю правильных английских высказываний) ;)
Мартин Йесперсен
13
Вы делаете это неправильно ...! Предикат (obj [ключ]) должен быть предикатом (obj [ключ])
пиротехник
6
@pyrotechnick: Нет. Во-первых, суть ответа не в том, чтобы расширить Object.prototype, а просто вложить функцию Object. Во-вторых, это код ОП . Ясно, что намерение ОП состоит в том, чтобы иметь .filter()возможность отфильтровывать положительные результаты. Другими словами, это отрицательный фильтр, где положительное возвращаемое значение означает, что оно исключено из результата. Если вы посмотрите на пример jsFiddle, он отфильтровывает существующие свойства undefined.
user113716
4
@patrick dw: Нет. Во-первых, я не упомянул о расширении / не расширении прототипов. Во-вторых, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - «Создает новый массив со всеми элементами, которые проходят тест, реализованный предоставленной функцией». Реализация полной противоположности на глобальном уровне кажется довольно глупой, не так ли?
пиротехник
8
@pyrotechnick: Правильно, вы не упоминали о расширении / не расширении прототипов, и это моя точка зрения. Вы сказали, что я делаю это неправильно, но единственное "это", которое я делаю, это говорит ОП не расширяться Object.prototype. Из вопроса: «Это работает ..., но когда я добавляю его на свой сайт ..., я получаю ошибки JavaScript». Если OP решит реализовать a .filter()с поведением, противоположным таковому Array.prototpe.filter, это его / ее. Пожалуйста, оставьте комментарий под вопросом, если вы хотите уведомить OP, что код неправильный, но не говорите мне, что я делаю это неправильно, если это не мой код.
user113716
284

Прежде всего, расширение считается плохой практикойObject.prototype . Вместо этого, обеспечить вашу функцию в качестве функции полезности на Object, так же , как там уже есть Object.keys, Object.assign, Object.is... и т.д..

Я приведу здесь несколько решений:

  1. Использование reduceиObject.keys
  2. Как (1), в сочетании с Object.assign
  3. Использование mapи распространение синтаксиса вместоreduce
  4. Использование Object.entriesиObject.fromEntries

1. Использование reduceиObject.keys

С помощью reduceи Object.keysреализовать желаемый фильтр (используя синтаксис стрелки 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);

Обратите внимание, что в приведенном выше коде predicateдолжно быть условие включения (в отличие от условия исключения используемого OP), чтобы оно соответствовало тому, как Array.prototype.filterработает.

2. Как (1), в сочетании с Object.assign

В приведенном выше решении оператор запятой используется в reduceчасти для возврата мутированного resобъекта. Конечно, это можно записать в виде двух утверждений вместо одного выражения, но последнее является более кратким. Для того, чтобы сделать это без оператора запятая, вы можете использовать Object.assignвместо этого, который действительно возвращает мутантный объект:

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. Использование mapи распространение синтаксиса вместоreduce

Здесь мы перемещаем Object.assignвызов из цикла, чтобы он выполнялся только один раз, и передаем отдельные ключи в качестве отдельных аргументов (используя синтаксис распространения ):

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. Использование Object.entriesиObject.fromEntries

Поскольку решение переводит объект в промежуточный массив, а затем преобразует его обратно в простой объект, было бы полезно использовать Object.entries(ES2017) и наоборот (т.е. создать объект из массива пар ключ / значение ) с помощью Object.fromEntries( ES2019).

Это приводит к этому «однострочному» методу 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);

Функция предиката получает здесь пару ключ / значение в качестве аргумента, которая немного отличается, но допускает больше возможностей в логике функции предиката.

trincot
источник
Это может быть более сложный запрос? Например:x => x.Expression.Filters.Filter
IamStalker
2
@IamStalker, ты пробовал? Это не имеет значения, если вы предоставляете действительную функцию во втором аргументе. NB: я понятия не имею, что .Filterнаходится в конце, но если это функция, вам нужно вызвать ее ( x => x.Expression.Filters.Filter())
trincot
1
Новые функции могут привести к уменьшению кода, но они также снижают производительность . Код, совместимый с Ed 3, работает в два раза быстрее в большинстве браузеров.
RobG
1
Версия последнего варианта на машинописном языке
Оливер Джозеф Эш,
1
Прекрасная работа! Спасибо за эти отличные объяснения и примеры.
Славик Мельцер
21

Если вы хотите использовать подчеркивание или штрих , вы можете использовать pick(или наоборот omit).

Примеры из документов подчеркивания:

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

Или с обратным вызовом (для lodash используйте pickBy ):

_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
  return _.isNumber(value);
});
// {age: 50}
Богдан Д
источник
1
lodash - плохое решение, потому что фильтрация пустых объектов также удалит числа.
Миббит
Я просто использовал lodash для этого, и это отличное решение. Спасибо @ Богдан Д!
Ира Герман
@mibbit Вы можете быть более конкретным? Я считаю, что это просто вопрос правильной реализации обратного вызова
Богдан Д.
11

ES6 подход ...

Представьте, что у вас есть этот объект ниже:

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

Создать функцию:

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

И назовите это:

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

и вернется :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  }
}
Алиреза
источник
1
Выглядит хорошо! Но почему он возвращает только другой объект (а не тот, с именем / filterValue из «Alireza»)?
Пилле
1
@Pille, ОП попросил, чтобы это было так (обратите внимание на отрицательный фильтр !predicateв их собственном коде).
Trincot
7

Как уже сказал Патрик, это плохая идея, поскольку она почти наверняка сломает любой сторонний код, который вы когда-либо захотите использовать.

Все библиотеки, такие как jquery или prototype, прервутся, если вы расширите Object.prototype, причина в том, что ленивая итерация над объектами (без hasOwnPropertyпроверок) прервется, поскольку добавленные вами функции будут частью итерации.

Мартин Йесперсен
источник
голосовали за то, чтобы объяснить «почему», что это плохая идея, ясно и кратко.
Майкл Ликори
5

Как насчет:

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

Или...

function filterObj(keys, obj) {
  const newObj = {};
  Object.keys(obj).forEach(key => {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  });
  return newObj;
}
shaunw
источник
4

Дано

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

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

затем :

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

Абденнур ТУМИ
источник
3
Это не использует данную функцию предиката, как того требует вопрос.
Трино
4

Я создал, Object.filter()который не только фильтрует по функции, но также принимает массив ключей для включения. Необязательный третий параметр позволит вам инвертировать фильтр.

Дано:

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

Массив:

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

Функция:

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

Код

отказ : я решил использовать ядро ​​Ext JS для краткости. Не чувствовал, что было необходимо писать контролеры типов для типов объектов, поскольку это не было частью вопроса.

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

Мистер Поливирл
источник
Пожалуйста, проверьте мой ванильный ответ и суть
З. Хулла
1
@trincot Спасибо, я обновил ответ, чтобы вернуть копию объекта, а не возврат на месте.
Мистер Поливирл
2

Мое самоуверенное решение:

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
}

Тестовые случаи:

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

Решение в Vanilla JS с 2020 года.


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

Вы можете фильтровать romNumbersобъект по ключу:

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

Или отфильтруйте romNumbersобъект по значению:

 const filteredByValue = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => value === 5))
 // filteredByValue = {V: 5} 
Куай-Гон Джинн
источник
1

Если вы хотите изменить тот же объект, а не создавать новый.

В следующем примере будут удалены все 0 или пустые значения:

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
источник
0

Как все говорили, не облажайтесь с прототипом. Вместо этого просто напишите функцию для этого. Вот моя версия с 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
источник
0

Я использую это, когда мне это нужно:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Анна
источник
-1

В этих случаях я использую jquery $ .map, который может обрабатывать объекты. Как уже упоминалось в других ответах, не рекомендуется менять родные прототипы ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes )

Ниже приведен пример фильтрации, просто проверяя некоторые свойства вашего объекта. Он возвращает собственный объект, если ваше условие истинно, или возвращает, undefinedесли нет. undefinedСвойство будет делать , что запись исчезнет из списка объектов;

$.map(yourObject, (el, index)=>{
    return el.yourProperty ? el : undefined;
});
Марсель Колс
источник
1
$.mapможет взять объект, но он возвращает массив, поэтому исходные имена свойств теряются. ОП требует отфильтрованный простой объект.
трино