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>
Никогда не расширяйте 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;
};
Прежде всего, считалось плохой практикой расширять Object.prototype
. Вместо этого предоставьте свою функцию как служебную функцию в Object
, точно так же, как уже есть Object.keys
, Object.assign
, Object.is
,... и т.д.
Я предлагаю несколько решений:
- Использование
reduce
иObject.keys
- Как (1), в сочетании с
Object.assign
- Использование
map
и распространение синтаксиса вместоreduce
- Использование
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);
Функция предиката получает здесь пару ключ/значение в качестве аргумента, которая немного отличается, но допускает больше возможностей в логике функции предиката.
Если вы хотите использовать подчеркивание или 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}
Как уже сказал Патрик, это плохая идея, так как почти наверняка нарушит любой сторонний код, который вы когда-либо захотите использовать.
Все библиотеки, такие как jquery или prototype, будут разбиты, если вы продолжите Object.prototype
, потому что эта ленивая итерация по объектам (без проверок hasOwnProperty
) сломается, так как функции, которые вы добавляете, будут частью итерации.
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"
}
}
Я создал 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>
Как насчет:
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;
}
Дано
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')
)
Мое самоуверенное решение:
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
Как все говорили, не облажайтесь с прототипом. Вместо этого просто напишите функцию для этого. Вот моя версия с 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 или пустые значения:
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);
В этих случаях я использую 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;
});
Посмотрите другие вопросы по меткам javascript jquery object filtering или Задайте вопрос