JavaScript: filter() для объектов

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

Как мне реализовать filter() для Object в JavaScript?

Скажем, у меня есть этот объект:

var foo = {
    bar: "Yes"
};

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

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.

Object.prototype.filter = function(predicate) {
  var result = {};
  for (key in this) {
    if (this.hasOwnProperty(key) && !predicate(this[key])) {
      console.log("copying");
      result[key] = this[key];
    }
  }
  return result;
};

var foo = {
  bar: "Yes",
  moo: undefined
};

foo = foo.filter(function(property) {
  return typeof property === "undefined";
});

document.getElementById('disp').innerHTML = JSON.stringify(foo, undefined, '  ');
console.log(foo);
#disp {
  white-space: pre;
  font-family: monospace
}
<div id="disp"></div>
+127
источник поделиться
12 ответов

Никогда не расширяйте 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) {
    var result = {}, key;
    // ---------------^---- as noted by @CMS, 
    //      always declare variables with the "var" keyword

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

    return result;
};
+160
источник

Прежде всего, считалось плохой практикой расширять 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 для реализации желаемого фильтра (с использованием синтаксиса стрелок ESa arrow syntax):

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

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

+204
источник
другие ответы

Связанные вопросы


Похожие вопросы

Если вы хотите использовать подчеркивание или lodash, вы можете использовать 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}
+19
источник

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

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

+5
источник

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"
  }
}
+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>
+3
источник

Как насчет:

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;
}
+3
источник

Дано

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

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

затем:

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

// Helper
function filter(object, ...keys) {
  return keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
  
};

//Example
const person = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};

// Expected to pick only firstname and age keys
console.log(
  filter(person, 'firstname', 'age')
)
+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/khullah/872d5a174108823159d845cc5baba337

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

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

В следующем примере будут удалены все 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);
0
источник

В этих случаях я использую 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
источник

Посмотрите другие вопросы по меткам или Задайте вопрос