JavaScript: filter () สำหรับ Objects


185

ECMAScript 5 มีfilter()ต้นแบบสำหรับArrayประเภท แต่ไม่ใช่Objectประเภทหากฉันเข้าใจอย่างถูกต้อง

ฉันจะใช้filter()สำหรับObjectใน 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 ฉันได้รับข้อผิดพลาด JavaScript ใน FireBug


คุณได้รับข้อผิดพลาดอะไรโดยเฉพาะ?
NT3RP

คุณได้รับข้อผิดพลาดอะไร โพสต์ถ้าเป็นไปได้ :)
Zack The Human

มีประวัติที่คลุมเครือนิดหน่อย wrt jQuery และสคริปต์ที่ขยายออกไปObject.prototype: bugs.jquery.com/ticket/2721
Crescent Fresh

สิ่งที่ฉันต้องการยกเว้นว่าคุณจะต้องลบ "!" ใน! predicate (ปุ่ม [นี้]) เพื่อให้มีวิธีการกรองที่แท้จริง
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: มอบขนมปังให้ผู้ชายและคุณจะให้อาหารเขาสักวันสอนให้เขารู้วิธีการอบและคุณจะให้อาหารเขาตลอดชีวิต (หรือบางอย่างฉันก็ไม่รู้ฉันไม่รู้คำพูดภาษาอังกฤษที่ถูกต้อง ;)
Martin Jespersen

13
คุณกำลังทำผิด ... ! เพรดิเคต (obj [กุญแจ]) ควรจะเป็นการระบุ (obj [คีย์])
pyrotechnick

6
@pyrotechnick: ไม่ก่อนอื่นประเด็นหลักของคำตอบคือไม่ขยายออกObject.prototypeแต่ให้วางฟังก์ชั่นObjectไว้ ประการที่สองนี้เป็นรหัสของ OP เห็นได้ชัด.filter()ว่าความตั้งใจของ OP คือการเป็นเช่นนั้นเพื่อกรองผลลัพธ์เชิงบวก กล่าวอีกนัยหนึ่งมันเป็นตัวกรองเชิงลบโดยที่ค่าส่งคืนที่เป็นบวกหมายความว่ามันถูกแยกออกจากผลลัพธ์ ถ้าคุณดูที่ตัวอย่าง jsFiddle undefinedเขากรองคุณสมบัติที่มีอยู่ที่มี
user113716

4
@patrick dw: ไม่ก่อนอื่นฉันไม่ได้พูดถึงการขยาย / ไม่ขยายต้นแบบ ประการที่สองdeveloper.mozilla.org/en/JavaScript/Reference/Global_Objects/… - "สร้างอาร์เรย์ใหม่พร้อมองค์ประกอบทั้งหมดที่ผ่านการทดสอบที่ใช้งานโดยฟังก์ชันที่มีให้" การใช้สิ่งที่ตรงกันข้ามกับสิ่งที่เกิดขึ้นทั่วโลกนั้นดูโง่ ๆ ใช่ไหม?
pyrotechnick

8
@pyrotechnick: ถูกต้องคุณไม่ได้พูดถึงการขยาย / ไม่ขยายต้นแบบและนั่นคือจุดของฉัน คุณบอกว่าฉันทำมันผิด แต่เพียง "มัน" ที่ฉันทำจะบอก OP Object.prototypeจะไม่ขยาย จากคำถาม: "ใช้งานได้ ... แต่เมื่อฉันเพิ่มไปยังเว็บไซต์ของฉัน ... ฉันได้รับข้อผิดพลาด JavaScript"ถ้า OP ตัดสินใจที่จะใช้ a ที่.filter()มีพฤติกรรมตรงข้ามกับของArray.prototpe.filterนั่นก็ขึ้นอยู่กับเขา / เธอ โปรดแสดงความคิดเห็นภายใต้คำถามหากคุณต้องการแจ้งให้ OP ทราบว่ารหัสนั้นผิด แต่อย่าบอกฉันว่าฉันทำผิดเมื่อไม่ใช่รหัสของฉัน
user113716

283

ครั้งแรกของทั้งหมดก็ถือว่าปฏิบัติไม่ดีที่จะขยาย 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จะต้องเป็นเงื่อนไขการรวม (ตรงกันข้ามกับเงื่อนไขการยกเว้น 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);

ฟังก์ชันเพรดิเคตรับคู่คีย์ / ค่าเป็นอาร์กิวเมนต์ที่นี่ซึ่งแตกต่างกันเล็กน้อย แต่ช่วยให้มีความเป็นไปได้มากขึ้นในตรรกะของฟังก์ชันเพรดิเคต


มันอาจเป็นแบบสอบถามที่ซับซ้อนมากขึ้น ตัวอย่างเช่น:x => x.Expression.Filters.Filter
IamStalker

2
@IamStalker คุณลองหรือยัง มันไม่สำคัญตราบใดที่คุณให้ฟังก์ชันที่ถูกต้องในอาร์กิวเมนต์ที่สอง หมายเหตุ: ฉันไม่รู้ว่าอะไร.Filterจะเกิดขึ้นในตอนท้าย แต่ถ้าเป็นฟังก์ชั่นคุณต้องเรียกมันว่า ( x => x.Expression.Filters.Filter())
trincot

1
คุณลักษณะใหม่ ๆ อาจทำให้โค้ดน้อย แต่พวกเขายังทำให้ประสิทธิภาพการทำงานช้าลง รหัสที่สอดคล้องกับ Ed 3 จะทำงานได้เร็วกว่าเบราว์เซอร์ส่วนใหญ่มากกว่าสองเท่า
RobG

1
TypeScript เวอร์ชันล่าสุดของตัวแปร: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
Oliver Joseph Ash

1
เยี่ยมมาก! ขอบคุณสำหรับคำอธิบายและตัวอย่างที่ยอดเยี่ยมเหล่านี้
Slavik Meltser

21

หากคุณยินดีที่จะใช้ขีดล่างหรือ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}

1
lodash เป็นวิธีการแก้ปัญหาที่ไม่ดีเพราะการกรองสำหรับวัตถุที่ว่างเปล่าจะลบตัวเลขด้วย
mibbit

ฉันเพิ่งใช้ lodash สำหรับเรื่องนี้และมันเป็นทางออกที่ดี ขอบคุณ @Bogdan D!
Ira Herman

@ micbit คุณจะเจาะจงมากขึ้นได้ไหม? ผมเชื่อว่ามันเป็นเพียงเรื่องของการดำเนินการเรียกกลับได้อย่างถูกต้อง
Bogdan D

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
ดูดี! แต่ทำไมมันกลับเป็นเพียงวัตถุอื่น (ไม่ใช่วัตถุที่มีชื่อ / ตัวกรองมูลค่าของ "Alireza")
Pille

1
@Pille OP ขอให้มันเป็นอย่างนั้น (สังเกตตัวกรองเชิงลบด้วย!predicateในรหัสของตัวเอง)
trincot

7

ตามที่แพทริคระบุไว้แล้วนี่เป็นความคิดที่ไม่ดีเนื่องจากมันจะทำลายรหัสบุคคลที่สามใด ๆ ที่คุณอาจต้องการใช้อย่างแน่นอน

ไลบรารีทั้งหมดเช่น jquery หรือต้นแบบจะแตกถ้าคุณขยายObject.prototypeสาเหตุที่การทำซ้ำอย่างเกียจคร้านเหนือวัตถุ (ไม่มีhasOwnPropertyเช็ค) จะแตกเนื่องจากฟังก์ชันที่คุณเพิ่มจะเป็นส่วนหนึ่งของการทำซ้ำ


upvoted สำหรับการอธิบาย 'ทำไม' ของการนี้เป็นความคิดที่ไม่ดีอย่างชัดเจนและรัดกุม
Michael Liquori

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
สิ่งนี้ไม่ได้ใช้ฟังก์ชันเพรดิเคตที่กำหนดตามที่คำถามต้องการ
trincot

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>


โปรดตรวจสอบคำตอบและส่วนสำคัญ
Z. Khullah

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สามารถนำวัตถุ แต่กลับอาร์เรย์ดังนั้นชื่อคุณสมบัติเดิมจะหายไป OP ต้องการวัตถุธรรมดาที่ถูกกรอง
trincot
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.