JavaScript: filter () cho các đối tượng


187

ECMAScript 5 có filter()nguyên mẫu cho Arraycác loại, nhưng không phải Objectloại, nếu tôi hiểu chính xác.

Làm cách nào để triển khai một filter()for cho ObjectJavaScript?

Hãy nói rằng tôi có đối tượng này:

var foo = {
    bar: "Yes"
};

Và tôi muốn viết một filter()tác phẩm trên 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;
};

Điều này hoạt động khi tôi sử dụng nó trong bản demo sau, nhưng khi tôi thêm nó vào trang web của mình sử dụng jQuery 1.5 và jQuery UI 1.8.9, tôi gặp lỗi JavaScript trong FireBug.


Những lỗi nào bạn nhận được, cụ thể?
NT3RP

Các lỗi bạn nhận được là gì? Đăng chúng nếu có thể :)
Zack The Human

Có một chút lịch sử mơ hồ khi viết jQuery và các tập lệnh mở rộng Object.prototype: bug.jquery.com/ticket/2721
Crescent Fresh

chính xác những gì tôi cần, ngoại trừ việc bạn phải xóa dấu "!" trong vị ngữ! ([khóa]) này để có phương thức lọc thực.
NoxFly

Câu trả lời:


201

Không bao giờ mở rộng Object.prototype.

Những điều khủng khiếp sẽ xảy ra với mã của bạn. Mọi thứ sẽ vỡ. Bạn đang mở rộng tất cả các loại đối tượng, bao gồm cả các đối tượng bằng chữ.

Đây là một ví dụ nhanh bạn có thể thử:

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

Thay vào đó hãy tạo một hàm mà bạn truyền đối tượng.

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: cho một người đàn ông một chiếc bánh mì và bạn sẽ cho anh ta ăn một ngày, dạy anh ta cách nướng bánh và bạn sẽ nuôi anh ta cả đời (hoặc một cái gì đó, tôi là Đan Mạch, tôi không biết những câu nói tiếng Anh chính xác ;)
Martin Jespersen

13
Bạn đang làm sai ...! Vị ngữ (obj [key]) nên là vị ngữ (obj [key])
pyrotechnick

6
@pyrotechnick: Không. Đầu tiên, điểm chính của câu trả lời là không mở rộng Object.prototype, mà chỉ đặt chức năng trên Object. Thứ hai, đây là mã của OP . Rõ ràng ý định của OP là để .filter()lọc ra kết quả tích cực. Nói cách khác, nó là một bộ lọc âm, trong đó giá trị trả về dương có nghĩa là nó bị loại khỏi kết quả. Nếu bạn nhìn vào ví dụ jsFiddle, anh ta sẽ lọc ra các thuộc tính hiện có undefined.
dùng113716

4
@patrick dw: Không. Đầu tiên, tôi không đề cập đến việc mở rộng / không mở rộng nguyên mẫu. Thứ hai, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/ mẹo - "Tạo một mảng mới với tất cả các yếu tố vượt qua bài kiểm tra được thực hiện bởi hàm được cung cấp." Thực hiện ngược lại chính xác trên toàn cầu có vẻ khá ngớ ngẩn, phải không?
pháo hoa

8
@pyrotechnick: Đúng vậy, bạn đã không đề cập đến việc mở rộng / không mở rộng các nguyên mẫu và đó là quan điểm của tôi. Bạn nói tôi đang làm sai, nhưng "nó" duy nhất tôi đang làm là bảo OP không gia hạn Object.prototype. Từ câu hỏi: "Điều này hoạt động ..., nhưng khi tôi thêm nó vào trang web của mình ..., tôi gặp lỗi JavaScript" Nếu OP quyết định thực hiện một .filter()hành vi ngược lại với điều đó Array.prototpe.filter, điều đó tùy thuộc vào anh ấy / cô ấy. Vui lòng để lại nhận xét dưới câu hỏi nếu bạn muốn thông báo cho OP rằng mã bị sai, nhưng đừng nói với tôi rằng tôi đang làm sai khi đó không phải là mã của tôi.
dùng113716

284

Trước hết, nó được coi là thực hành xấu để mở rộngObject.prototype . Thay vào đó, cung cấp tính năng của bạn như chức năng tiện ích trên Object, giống như đã có Object.keys, Object.assign, Object.is, ... vv.

Tôi cung cấp ở đây một số giải pháp:

  1. Sử dụng reduceObject.keys
  2. Như (1), kết hợp với Object.assign
  3. Sử dụng mapvà truyền bá cú pháp thay vìreduce
  4. Sử dụng Object.entriesObject.fromEntries

1. Sử dụng reduceObject.keys

Với reduceObject.keysđể thực hiện bộ lọc mong muốn (sử dụng cú pháp mũi tên 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);

Lưu ý rằng trong đoạn mã trên predicatephải là điều kiện đưa vào (trái với điều kiện loại trừ mà OP đã sử dụng), để nó phù hợp với cách thức Array.prototype.filterhoạt động.

2. Như (1), kết hợp với Object.assign

Trong giải pháp trên, toán tử dấu phẩy được sử dụng trong reducephần để trả về resđối tượng bị đột biến . Điều này tất nhiên có thể được viết thành hai câu thay vì một biểu thức, nhưng câu sau ngắn gọn hơn. Để làm điều đó mà không cần các nhà điều hành dấu phẩy, bạn có thể sử dụng Object.assignthay vào đó, mà không trả lại đối tượng đột biến:

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. Sử dụng mapvà truyền bá cú pháp thay vìreduce

Ở đây chúng tôi di chuyển Object.assigncuộc gọi ra khỏi vòng lặp, do đó, nó chỉ được thực hiện một lần và chuyển cho nó các khóa riêng lẻ dưới dạng đối số riêng biệt (sử dụng cú pháp lây lan ):

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. Sử dụng Object.entriesObject.fromEntries

Khi giải pháp chuyển đối tượng thành một mảng trung gian và sau đó chuyển đổi trở lại thành một đối tượng đơn giản, sẽ rất hữu ích khi sử dụng Object.entries(ES2017) và ngược lại (nghĩa là tạo một đối tượng từ một mảng các cặp khóa / giá trị ) với Object.fromEntries( ES2019).

Nó dẫn đến phương pháp "một lớp" này trên 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);

Hàm vị ngữ lấy một cặp khóa / giá trị làm đối số ở đây, khác một chút, nhưng cho phép nhiều khả năng hơn trong logic của hàm vị ngữ.


Nó có thể là truy vấn phức tạp hơn? Ví dụ:x => x.Expression.Filters.Filter
IamStalker

2
@IamStalker, bạn đã thử chưa? Nó không quan trọng, miễn là bạn cung cấp một hàm hợp lệ trong đối số thứ hai. Lưu ý: Tôi không biết .Filtercuối cùng là gì , nhưng nếu đó là một hàm, bạn cần gọi nó ( x => x.Expression.Filters.Filter())
trincot

1
Các tính năng mới hơn có thể làm cho ít mã hơn, nhưng chúng cũng làm cho hiệu suất chậm hơn . Mã tuân thủ Ed 3 chạy nhanh hơn gấp đôi trong hầu hết các trình duyệt.
RobG

1
Phiên bản TypeScript của biến thể cuối cùng: gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
Oliver Joseph Ash

1
Bạn đã làm rất tốt! Cảm ơn bạn cho những giải thích và ví dụ tuyệt vời.
Slasta Meltser

21

Nếu bạn sẵn sàng sử dụng dấu gạch dưới hoặc dấu gạch ngang , bạn có thể sử dụng pick(hoặc ngược lại, omit).

Ví dụ từ các tài liệu gạch dưới:

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

Hoặc với một cuộc gọi lại (đối với lodash , hãy sử dụng pickBy ):

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

1
lodash là một giải pháp tồi vì lọc cho các đối tượng trống cũng sẽ loại bỏ số.
mibbit

Tôi chỉ sử dụng lodash cho việc này và đó là một giải pháp tuyệt vời. Cảm ơn @Bogdan D!
Ira Herman

@mibbit bạn có thể cụ thể hơn? Tôi tin rằng đó chỉ là vấn đề thực hiện cuộc gọi lại một cách chính xác
Bogdan D

11

Cách tiếp cận ES6 ...

Hãy tưởng tượng bạn có đối tượng này dưới đây:

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

Tạo một chức năng:

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

Và gọi nó là:

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

và sẽ trở lại :

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

1
Có vẻ tốt! Nhưng tại sao nó chỉ trả về đối tượng khác (mà không phải là đối tượng có tên / filterValue của "Alireza")?
Pille

1
@Pille, OP yêu cầu nó phải như thế (lưu ý bộ lọc âm với !predicatemã riêng của họ).
trincot

7

Như patrick đã tuyên bố đây là một ý tưởng tồi, vì nó gần như chắc chắn sẽ phá vỡ bất kỳ mã bên thứ 3 nào bạn muốn sử dụng.

Tất cả các thư viện như jquery hoặc nguyên mẫu sẽ bị hỏng nếu bạn mở rộng Object.prototype, lý do là việc lặp lại lười biếng trên các đối tượng (không có hasOwnPropertykiểm tra) sẽ bị hỏng do các chức năng bạn thêm sẽ là một phần của lần lặp.


được khuyến khích để giải thích "tại sao" đây là một ý tưởng tồi rõ ràng và chính xác.
Michael Liquori

5

Làm thế nào về:

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

Hoặc là...

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

4

Được

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

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

sau đó :

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


3
Điều này không sử dụng một chức năng vị ngữ nhất định theo yêu cầu của câu hỏi.
trincot

4

Tôi đã tạo ra một hàm Object.filter()không chỉ lọc theo hàm mà còn chấp nhận một loạt các khóa để đưa vào. Tham số thứ ba tùy chọn sẽ cho phép bạn đảo ngược bộ lọc.

Được:

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

Mảng:

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

Chức năng:

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

Tuyên bố miễn trừ trách nhiệm : Tôi đã chọn sử dụng lõi Ext JS cho ngắn gọn. Không cảm thấy cần phải viết trình kiểm tra loại cho các loại đối tượng vì nó không phải là một phần của câu hỏi.

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


Vui lòng kiểm tra câu trả lờiý chính
Z. Khullah

1
@trincot Cảm ơn, tôi đã cập nhật phản hồi để trả về một bản sao của đối tượng, thay vì trả lại tại chỗ.
Ông Polywhirl

2

Giải pháp quan điểm của tôi:

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
}

Các trường hợp thử nghiệm:

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

Giải pháp trong Vanilla JS từ năm 2020.


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

Bạn có thể lọc romNumbersđối tượng theo khóa:

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

Hoặc lọc romNumbersđối tượng theo giá trị:

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

1

Nếu bạn muốn đột biến cùng một đối tượng hơn là tạo một đối tượng mới.

Ví dụ sau sẽ xóa tất cả 0 hoặc giá trị trống:

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

Giống như mọi người đã nói, đừng bắt vít với nguyên mẫu. Thay vào đó, chỉ cần viết một hàm để làm như vậy. Đây là phiên bản của tôi với 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

Tôi sử dụng nó khi tôi cần:

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

-1

Trong những trường hợp này, tôi sử dụng jquery $ .map, có thể xử lý các đối tượng. Như đã đề cập trên các câu trả lời khác, việc thay đổi các nguyên mẫu gốc ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inherribution_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes ) không phải là một cách tốt .

Dưới đây là một ví dụ về việc lọc chỉ bằng cách kiểm tra một số thuộc tính của đối tượng của bạn. Nó trả về đối tượng riêng nếu điều kiện của bạn là đúng hoặc trả về undefinednếu không. Các undefinedbất động sản sẽ làm cho điều đó kỷ lục biến mất khỏi danh sách đối tượng của bạn;

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

1
$.mapcó thể lấy một đối tượng, nhưng nó trả về một mảng, vì vậy tên thuộc tính ban đầu bị mất. OP cần một đối tượng đơn giản được lọc.
trincot
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.