JavaScript:オブジェクトのfilter()


187

ECMAScript 5にはfilter()型のプロトタイプArrayがありObjectますが、私が正しく理解していれば、型はありません。

JavaScriptでfilter()for をどのように実装Objectしますか?

私がこのオブジェクトを持っているとしましょう:

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、2011

発生しているエラーは何ですか?可能であれば投稿してください:)
ザックザヒューマン

延長jQueryとスクリプトWRTあいまいな歴史のビットがありますObject.prototypebugs.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
@patrick:男にパンを与えれば、彼に1日餌を与え、彼に焼く方法を教えて、生涯彼に餌を与えるでしょう(または、私はデンマーク語ですが、正しい英語のことわからないのです) ;)
マーティンイェスパーセン、2011

13
あなたはそれを間違っています...!predicate(obj [key])はpredicate(obj [key])でなければなりません
pyrotechnick

6
@pyrotechnick:いいえ。まず、答えの要点は拡張しないでObject.prototype、関数をに配置することObjectです。次に、これはOPのコードです。明らかにOPの意図は持っている.filter()、それはようなものでフィルタリングする肯定的な結果を。つまり、これは負のフィルターであり、正の戻り値は結果から除外されることを意味します。jsFiddleの例を見ると、彼は既存のプロパティであるを除外していますundefined
user113716 2011

4
@patrick dw:いいえ。最初に、プロトタイプの拡張については触れませんでした。次に、developer.mozilla.org / en / JavaScript / Reference / Global_Objects /…-「提供された関数によって実装されたテストに合格するすべての要素を含む新しい配列を作成します。」まったく反対のことをグローバルに実装するのは、ばかげているように思えませんか?
パイロテクニック

8
@pyrotechnick:そうです、あなたはプロトタイプを拡張する/拡張しないことに言及していませんでした、そしてそれが私のポイントです。あなたは私がそれを間違っていると言ったが、私がしている唯一の「それ」はOPに拡張しないように言うことObject.prototypeです。質問から:「これは機能しますが、自分のサイトに追加すると、JavaScriptエラーが発生します」 OPが.filter()とは逆の動作でを実装することを決定した場合、それはArray.prototpe.filter彼または彼女次第です。コードが間違っていることをOPに通知したい場合は、質問の下にコメントを残してください。ただし、自分のコードではないときに間違っいること知らせないでください。
user113716 2011

284

まず、拡張するのは悪い習慣だと考えられていObject.prototypeます。代わりに、上の効用関数として、あなたの機能を提供しObject、すでに存在しているだけのようにObject.keysObject.assignObject.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

上記のソリューションでは、変更されたオブジェクトを返すためにパートでカンマ演算子が使用されています。もちろん、これは1つの式ではなく2つのステートメントとして記述できますが、後者の方が簡潔です。コンマ演算子なしでそれを行うには、代わりにを使用できます。これ、変化したオブジェクトを返します。reduceresObject.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呼び出しをループの外に移動するため、呼び出しは1回だけ行われ、個別のキーとして個別のキーを渡します(スプレッド構文を使用)。

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 '19年

2
@IamStalker、やってみましたか?2番目の引数に有効な関数を指定する限り、問題はありません。注意:何.Filterが最後にあるのかはx => x.Expression.Filters.Filter()
わかり

1
新しい機能はコードが少なくなる可能性がありますが、パフォーマンスの低下にもつながります。Ed 3準拠のコードは、ほとんどのブラウザーで2倍以上の速度で実行されます。
RobG 2017

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

私はこれにロダッシュを使用しただけで、素晴らしい解決策です。ありがとう@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
いいね!しかし、なぜそれが他のオブジェクトだけを返すのですか(name / filterValueが "Alireza"であるオブジェクトではない)?
ピレ

1
@ Pille、OPはそれをそのようにするように求めました(!predicate独自のコードにある否定フィルターに注意してください)。
トリンコット

7

パトリックがすでに述べたように、これは悪い考えです、それはあなたがこれまでに使いたいサードパーティのコードをほぼ確実に壊してしまうからです。

拡張するとjqueryやプロトタイプなどのすべてのライブラリが機能し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
これは、質問で必要とされる特定の述語関数を使用しません。
トリンコット

4

Object.filter()関数でフィルタリングするだけでなく、含めるキーの配列も受け入れるを作成しました。オプションの3番目のパラメーターを使用すると、フィルターを反転できます。

与えられた:

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

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にはフィルター処理されたプレーンオブジェクトが必要です。
トリンコット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.