JavaScript: filter () untuk Objects


187

ECMAScript 5 memiliki filter()prototipe untuk Arraytipe, tetapi bukan Objecttipe, jika saya mengerti dengan benar.

Bagaimana cara saya menerapkan s filter()untuk Objectdalam JavaScript?

Katakanlah saya punya objek ini:

var foo = {
    bar: "Yes"
};

Dan saya ingin menulis filter()yang berfungsi di 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;
};

Ini berfungsi ketika saya menggunakannya dalam demo berikut, tetapi ketika saya menambahkannya ke situs saya yang menggunakan jQuery 1.5 dan jQuery UI 1.8.9, saya mendapatkan kesalahan JavaScript di FireBug.


Kesalahan apa yang Anda dapatkan, khususnya?
NT3RP

Apa kesalahan yang Anda dapatkan? Posting mereka jika memungkinkan :)
Zack The Human

Ada sedikit sejarah ambigu wrt jQuery dan skrip yang meluas Object.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh

persis apa yang saya butuhkan, kecuali Anda harus menghapus "!" dalam predikat! (tombol [ini]) untuk memiliki metode filter nyata.
NoxFly

Jawaban:


201

Tidak pernah berkembang Object.prototype.

Hal-hal buruk akan terjadi pada kode Anda. Segalanya akan pecah. Anda memperluas semua jenis objek, termasuk objek literal.

Inilah contoh cepat yang dapat Anda coba:

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

Alih-alih membuat fungsi yang Anda melewati objek.

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: berikan roti untuk seseorang dan kamu akan memberinya makan sehari, ajari dia cara memanggang dan kamu akan memberinya makan seumur hidup (atau sesuatu, aku Denmark, aku tidak tahu ucapan bahasa Inggris yang benar ;)
Martin Jespersen

13
Anda salah melakukannya ...! Predikat (objek [kunci]) harus predikat (objek [kunci])
pyrotechnick

6
@pyrotechnick: Tidak. Pertama, poin utama dari jawabannya adalah untuk tidak memperluas Object.prototype, tetapi hanya untuk menempatkan fungsi Object. Kedua, ini adalah kode OP . Jelas niat OP adalah untuk memiliki .filter()sedemikian rupa sehingga menyaring hasil positif. Dengan kata lain, ini adalah filter negatif, di mana nilai pengembalian positif berarti dikeluarkan dari hasilnya. Jika Anda melihat contoh jsFiddle, ia memfilter properti yang ada undefined.
user113716

4
@ Patrick dw: Tidak. Pertama, saya tidak menyebutkan memperpanjang / tidak memperluas prototipe. Kedua, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - "Membuat array baru dengan semua elemen yang lulus tes diimplementasikan oleh fungsi yang disediakan." Menerapkan kebalikan dari global tampaknya sangat konyol, bukan?
pyrotechnick

8
@pyrotechnick: Itu benar, Anda tidak menyebutkan memperpanjang / tidak memperluas prototipe, dan itu maksud saya. Anda bilang saya salah, tapi satu-satunya "itu" yang saya lakukan adalah mengatakan pada OP untuk tidak memperpanjang Object.prototype. Dari pertanyaan: "Ini berfungsi ..., tetapi ketika saya menambahkannya ke situs saya ..., saya mendapatkan kesalahan JavaScript" Jika OP memutuskan untuk menerapkan .filter()dengan perilaku yang berlawanan dari itu Array.prototpe.filter, itu terserah padanya. Silakan tinggalkan komentar di bawah pertanyaan jika Anda ingin memberi tahu OP bahwa kode itu salah, tetapi jangan katakan bahwa saya salah melakukannya ketika itu bukan kode saya.
user113716

284

Pertama-tama, itu dianggap praktik buruk untuk diperluasObject.prototype . Sebaliknya, menyediakan fitur Anda sebagai fungsi utilitas pada Object, seperti ada sudah berada Object.keys, Object.assign, Object.is, ... dll.

Saya berikan di sini beberapa solusi:

  1. Menggunakan reducedanObject.keys
  2. Seperti (1), dalam kombinasi dengan Object.assign
  3. Menggunakan mapdan menyebar sintaks alih-alihreduce
  4. Menggunakan Object.entriesdanObject.fromEntries

1. Menggunakan reducedanObject.keys

Dengan reducedan Object.keysuntuk menerapkan filter yang diinginkan (menggunakan sintaks panah 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);

Perhatikan bahwa dalam kode di atas predicateharus merupakan kondisi inklusi (bertentangan dengan kondisi pengecualian OP yang digunakan), sehingga sesuai dengan cara Array.prototype.filterkerjanya.

2. As (1), dalam kombinasi dengan Object.assign

Dalam solusi di atas operator koma digunakan di reducebagian untuk mengembalikan resobjek yang bermutasi . Tentu saja ini dapat ditulis sebagai dua pernyataan, bukan satu ungkapan, tetapi yang terakhir lebih ringkas. Untuk melakukannya tanpa operator koma, Anda bisa menggunakan Object.assignsebagai gantinya, yang tidak mengembalikan objek bermutasi:

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. Menggunakan mapdan menyebarkan sintaks sebagai gantireduce

Di sini kita memindahkan Object.assignpanggilan keluar dari loop, sehingga hanya dibuat sekali, dan memberikannya kunci individual sebagai argumen terpisah (menggunakan sintaksis penyebaran ):

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. Menggunakan Object.entriesdanObject.fromEntries

Ketika solusi menerjemahkan objek ke array perantara dan kemudian mengubahnya kembali menjadi objek biasa, akan berguna untuk menggunakan Object.entries(ES2017) dan sebaliknya (yaitu membuat objek dari array pasangan kunci / nilai ) dengan Object.fromEntries( ES2019).

Ini mengarah ke metode "satu garis" pada 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);

Fungsi predikat mendapat pasangan kunci / nilai sebagai argumen di sini, yang sedikit berbeda, tetapi memungkinkan lebih banyak kemungkinan dalam logika fungsi predikat.


Mungkinkah itu permintaan yang lebih kompleks? Sebagai contoh:x => x.Expression.Filters.Filter
IamStalker

2
@IamStalker, sudahkah Anda mencoba? Tidak masalah, selama Anda menyediakan fungsi yang valid dalam argumen kedua. NB: Saya tidak tahu apa yang .Filterada di akhirnya, tetapi jika itu adalah sebuah fungsi, Anda perlu menyebutnya ( x => x.Expression.Filters.Filter())
trincot

1
Fitur yang lebih baru mungkin membuat kode lebih sedikit, tetapi mereka juga membuat untuk kinerja lebih lambat . Kode yang sesuai dengan Ed 3 berjalan lebih dari dua kali lebih cepat di sebagian besar browser.
RobG


1
Kerja bagus! Terima kasih atas penjelasan dan contohnya.
Slavik Meltser

21

Jika Anda ingin menggunakan garis bawah atau tanda lahir , Anda dapat menggunakan pick(atau kebalikannya, omit).

Contoh dari dokumen garis bawah:

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

Atau dengan callback (untuk menginap , gunakan pickBy ):

_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
  return _.isNumber(value);
});
// {age: 50}

1
lodash adalah solusi buruk karena pemfilteran untuk objek kosong juga akan menghapus angka.
mibbit

Saya hanya menggunakan lodash untuk ini dan ini solusi yang bagus. Terima kasih @ Bogdan D!
Ira Herman

@mibbit, bisakah Anda lebih spesifik? Saya percaya ini hanya masalah mengimplementasikan panggilan balik dengan benar
Bogdan D

11

Pendekatan ES6 ...

Bayangkan Anda memiliki objek di bawah ini:

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

Buat fungsi:

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

Dan menyebutnya:

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

dan akan kembali :

{
  1: {
  id: 1,
  name: "Brendan", 
  family: "Eich"
  },
  2: {
   id: 2,
   name: "John", 
   family: "Resig"
  }
}

1
Kelihatan bagus! Tetapi mengapa itu mengembalikan hanya objek lain (dan bukan yang dengan nama / filterValue "Alireza")?
Pille

1
@Pille, OP memintanya seperti itu (perhatikan filter negatif dengan !predicatekode mereka sendiri).
trincot

7

Seperti yang sudah dinyatakan patrick, ini adalah ide yang buruk, karena hampir pasti akan memecahkan kode pihak ketiga yang ingin Anda gunakan.

Semua perpustakaan seperti jquery atau prototipe akan rusak jika Anda memperluas Object.prototype, alasannya adalah bahwa iterasi malas atas objek (tanpa hasOwnPropertypemeriksaan) akan rusak karena fungsi yang Anda tambahkan akan menjadi bagian dari iterasi.


dipilih untuk menjelaskan 'mengapa' ini menjadi ide yang buruk dengan jelas dan singkat.
Michael Liquori

5

Bagaimana tentang:

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

Atau...

function filterObj(keys, obj) {
  const newObj = {};
  Object.keys(obj).forEach(key => {
    if (keys.includes(key)) {
      newObj[key] = obj[key];
    }
  });
  return newObj;
}

4

Diberikan

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

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

kemudian :

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


3
Ini tidak menggunakan fungsi predikat yang diberikan seperti yang dipersyaratkan oleh pertanyaan.
trincot

4

Saya telah membuat Object.filter()yang tidak hanya memfilter menurut fungsi, tetapi juga menerima larik kunci untuk disertakan. Parameter ketiga opsional akan memungkinkan Anda untuk membalikkan filter.

Diberikan:

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

Himpunan:

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

Fungsi:

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

Kode

Penafian : Saya memilih untuk menggunakan inti Ext JS untuk singkatnya. Tidak merasa perlu untuk menulis checker tipe untuk tipe objek karena itu bukan bagian dari pertanyaan.

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


Silakan periksa jawaban vanilla dan intisari saya
Z. Khullah

1
@trincot Terima kasih, saya memperbarui respons untuk mengembalikan salinan objek, bukan pengembalian di tempat.
Tn. Polywhirl

2

Solusi saya:

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
}

Kasus uji:

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

Solusi di Vanilla JS dari tahun 2020.


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

Anda dapat memfilter romNumbersobjek dengan kunci:

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

Atau memfilter romNumbersobjek berdasarkan nilai:

 const filteredByValue = Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => value === 5))
 // filteredByValue = {V: 5} 

1

Jika Anda ingin bermutasi objek yang sama daripada membuat yang baru.

Contoh berikut akan menghapus semua nilai 0 atau kosong:

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

Seperti semua orang katakan, jangan main-main dengan prototipe. Sebagai gantinya, cukup tulis fungsi untuk melakukannya. Ini versi saya dengan 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

Saya menggunakan ini ketika saya membutuhkannya:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}

-1

Dalam kasus ini saya menggunakan jquery $ .map, yang dapat menangani objek. Seperti disebutkan pada jawaban lain, bukan praktik yang baik untuk mengubah prototipe asli ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes )

Di bawah ini adalah contoh pemfilteran hanya dengan memeriksa beberapa properti objek Anda. Ini mengembalikan objek sendiri jika kondisi Anda benar atau mengembalikan undefinedjika tidak. The undefinedproperti akan membuat rekor menghilang dari daftar objek Anda;

$.map(yourObject, (el, index)=>{
    return el.yourProperty ? el : undefined;
});

1
$.mapdapat mengambil objek, tetapi mengembalikan array, sehingga nama properti asli hilang. OP membutuhkan objek polos yang difilter.
trincot
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.