JavaScript : 객체 용 filter ()


185

ECMAScript 5에는 유형에 대한 filter()프로토 타입이 Array있지만 Object올바르게 이해하면 유형이 아닙니다 .

JavaScript에서 filter()for를 어떻게 구현 Object합니까?

이 객체가 있다고 가정 해 봅시다.

var foo = {
    bar: "Yes"
};

그리고 s에서 filter()작동 하는을 작성하고 싶습니다 Object.

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

당신이 얻는 오류는 무엇입니까? :) 가능하면 그들을 게시
잭 인간

확장 jQuery를 스크립트 WRT 모호한 역사의 비트가있다 Object.prototype: bugs.jquery.com/ticket/2721은
초승달 신선한

"!"를 제거해야한다는 점을 제외하고는 내가 필요한 것입니다. ! predicate (this [key])에서 실제 필터 메소드를 갖습니다.
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
@ 패트릭 : 남자에게 빵을 주면 하루 동안 먹이를주고 빵을 만드는 법을 가르쳐 평생 동안 먹이를 줄 것입니다. ;)
Martin Jespersen 님이

13
당신이 잘못하고 있습니다 ...! predicate (obj [key]) predicate (obj [key])
pyrotechnick

6
@pyrotechnick : 아니요. 첫째, 대답의 주요 요점은 확장하는 Object.prototype것이 아니라 함수를에 배치하는 것입니다 Object. 둘째, 이것은 OP의 코드 입니다. 분명 OP의 의도는 긍정적 인 결과를 걸러내는.filter() 것이 어야 한다 . 즉, 음수 필터입니다. 양수의 반환 값 은 결과에서 제외됨 을 의미합니다 . jsFiddle 예제를 보면 그는 기존 속성을 필터링합니다 . undefined
user113716

4
@ Patrick dw : 아니요. 첫째, 프로토 타입 확장 / 확장에 대해서는 언급하지 않았습니다. 둘째, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…- "제공된 함수에 의해 구현 된 테스트를 통과하는 모든 요소로 새로운 배열을 만듭니다." 세계에서 정반대를 구현하는 것은 어리석은 것처럼 보이지 않습니까?
pyrotechnick

8
@ pyrotechnick : 맞습니다. 프로토 타입 확장 / 확장에 대해서는 언급하지 않았습니다. 이것이 바로 제 요점입니다. 당신은 내가 잘못하고 있다고 말했지만, 내가하고있는 유일한 것은 그것이 연장하지 말라고 OP에게 알리는 것입니다 Object.prototype. 질문에서 : "이것은 작동하지만 ... 내 사이트에 추가하면 ... JavaScript 오류가 발생합니다." OP가의 .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. 사용 reduceObject.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);

위 코드 에서 OP가 사용한 제외 조건 과 달리 포함 조건 이 작동 방식 과 일치하도록 포함 조건 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는 루프 밖으로 호출 을 이동 하므로 한 번만 만들어 개별 키를 별도의 인수로 ( spread 구문 사용 ) 전달합니다.

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.entriesObject.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
잘 했어! 이 훌륭한 설명과 예에 감사드립니다.
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

@mibbit 좀 더 구체적으로 할 수 있습니까? 콜백을 올바르게 구현하는 것이 문제라고 생각합니다.
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
좋아 보인다! 그러나 왜 다른 객체 만 반환합니까 (이름 / filterValue가 "Alireza"인 객체는 아님)?
Pille

1
@Pille, OP는 그런 식으로 요청했습니다 ( !predicate자체 필터에 네거티브 필터 사용 ).
trincot 2014 년

7

패트릭은 이미 언급했듯이 사용하기를 원하는 타사 코드를 거의 확실하게 깨뜨릴 수 있으므로 나쁜 생각입니다.

확장하면 jquery 또는 prototype과 같은 모든 라이브러리가 중단됩니다. 추가하는 함수가 반복의 일부이기 때문에 검사 Object.prototype하지 않고 객체에 대한 지연 반복 hasOwnProperty이 중단되기 때문입니다.


이것이 나쁜 생각 인 이유를 명확하고 간결하게 설명하는 것에 대한 공감대.
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 감사합니다. 내부 반환 대신 객체의 복사본을 반환하도록 응답을 업데이트했습니다.
Mr. Polywhirl 2016 년

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

2020 년의 바닐라 JS 솔루션


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 )

다음은 객체의 일부 속성을 확인하여 필터링하는 예입니다. 조건이 true이면 자체 객체를 반환하거나 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.