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>

Ответ 1

Никогда не расширяйте 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;
};

Ответ 2

Прежде всего, считалось плохой практикой расширять 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);

Ответ 3

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

Ответ 4

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

Все библиотеки, такие как 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"
  }
}

Ответ 6

Я создал 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>

Ответ 7

Как насчет:

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

Ответ 8

Дано

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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