JavaScript: filter () для об'єктів


185

ECMAScript 5 має filter()прототип для Arrayтипів, але не для Objectтипів, якщо я правильно розумію.

Як я можу реалізувати для filter()для Objects в JavaScript?

Скажімо, у мене є цей об'єкт:

var foo = {
    bar: "Yes"
};

І я хочу написати filter()те, що працює на 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;
};

Це працює, коли я використовую його в наступній демонстраційній версії, але коли я додаю його на свій сайт, який використовує jQuery 1.5 та jQuery UI 1.8.9, у FireBug з’являються помилки JavaScript.


Які помилки ви конкретно отримуєте?
NT3RP

Які помилки ви отримуєте? Опублікуйте їх, якщо можливо :)
Zack The Human

Існує трохи неоднозначної історії wrt 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;
};

61
@patrick: дай людині хліб, і ти будеш його годувати на день, навчиш його пекти, і ти будеш годувати його на все життя (чи щось, я датчанин, я не знаю правильних англійських приказок ;)
Мартін Єсперсен

13
Ви робите це неправильно ...! Предикат (obj [key]) має бути предикатом (obj [key])
pyrotechnick

6
@pyrotechnick: Ні. По-перше, головний пункт відповіді - це не розширювати Object.prototype, а просто просто розміщувати функцію Object. По-друге, це код ОП . Очевидно, що ОП має намір .filter()бути таким, щоб він фільтрував позитивні результати. Іншими словами, це від'ємний фільтр, де додатне значення повернення означає, що воно виключається з результату. Якщо ви подивитесь на приклад jsFiddle, він фільтрує наявні властивості, які є undefined.
користувач113716

4
@patrick dw: Ні. По-перше, я не згадував про розширення / не розширення прототипів. По-друге, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - "Створюється новий масив з усіма елементами, які проходять тест, реалізований наданою функцією." Здійснення навпаки навпаки у світі виглядає досить нерозумно, чи не так?
pyrotechnick

8
@pyrotechnick: Правильно, ви не згадали про розширення / не розширення прототипів, і це моя думка. Ви сказали, що я роблю це неправильно, але єдине "це", яке я роблю, - це сказати ОП не продовжувати Object.prototype. З питання: "Це працює ..., але коли я додаю його на свій сайт ..., я отримую помилки JavaScript" Якщо ОП вирішить реалізувати функцію .filter()з протилежною поведінкою, що стосується Array.prototpe.filter, це залежить від нього. Будь ласка, залиште коментар під запитанням, якщо ви хочете повідомити ОП, що код неправильний, але не кажіть мені, що я роблю це неправильно, коли це не мій код.
користувач113716

282

Перш за все, вважається поганою практикою продовження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повинна бути умова включення (всупереч умові виключення використовуваної ОП), щоб вона відповідала тому, як 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);

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


Чи може бути складнішим запит? Наприклад:x => x.Expression.Filters.Filter
IamStalker

2
@IamStalker, ти спробував? Це не має значення, якщо ви надаєте дійсну функцію у другому аргументі. NB: Я поняття не маю, що .Filterв кінці, але якщо це функція, вам потрібно назвати це ( x => x.Expression.Filters.Filter())
trincot

1
Більш нові функції можуть спричинити менший код, але вони також знижують продуктивність . Код, сумісний з Ed 3, працює у більшості браузерів більш ніж удвічі швидше.
RobG

1
Версія TypeScript останнього варіанту: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
Олівер Джозеф Еш

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 є поганим рішенням, оскільки фільтрування порожніх об'єктів також видалить числа.
міббіт

Я просто використовував лодаш для цього, і це чудове рішення. Дякую @Bogdan D!
Іра Герман

@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у власному коді).
трінкот

7

Як патрік уже заявив, це погана ідея, оскільки вона майже напевно зламає будь-який сторонній код, який ви могли б хотіти використати.

Усі бібліотеки, такі як jquery або прототип, зламаються, якщо розширити 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;
}

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 Спасибі, я оновив відповідь, щоб повернути копію об'єкта, а не повернення на місці.
Містер Polywhirl

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

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

Я використовую це, коли мені це потрібно:

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може взяти об’єкт, але він повертає масив, тому початкові назви властивостей втрачаються. ОП потребує відфільтрованого простого об'єкта.
трінкот
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.