JavaScript:对象的filter()


185

如果我理解正确的话,ECMAScript 5具有类型的filter()原型Array,但没有Object类型的原型。

如何在JavaScript中实现filter()for 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

您遇到什么错误?如果可能的话,将其发布:)
Zack The Human

jQuery和扩展了脚本的历史有些模糊Object.prototypebugs.jquery.com/ticket/2721
Crescent Fresh

正是我需要的,除了必须删除“!” 在!predicate(this [key])中具有真正的过滤方法。
NoxFly

Answers:


200

永远不要扩展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;
};

60
@patrick:给男人一个面包,你会给他喂一天,教他如何烘烤,然后你将给他喂一辈子(或者,我是丹麦人,我不知道正确的英语谚语;)
Martin Jespersen

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 /…-“创建一个新数组,其中所有元素都通过由提供的功能实现的测试。” 在全局上实施完全相反的操作似乎很愚蠢,不是吗?
pyrotechnick

7
@pyrotechnick:是的,您没有提到扩展/不扩展原型,这就是我的观点。您说我做错了,但是我唯一要做的是告诉OP不要扩展Object.prototype。从问题开始:“这可行...但是,当我将其添加到我的网站...时,我遇到JavaScript错误”。如果OP决定实施.filter()与的行为相反的行为,则Array.prototpe.filter取决于他/她。如果您想通知OP代码错误,请在问题下发表评论,但不要告诉我,如果不是我的代码,做错了。
user113716 2011年

278

首先,扩展认为是不好的做法Object.prototype。相反,提供功能的实用功能Object,就像目前已经有Object.keysObject.assignObject.is,...等等。

我在这里提供几种解决方案:

  1. 使用reduceObject.keys
  2. 与(1)结合 Object.assign
  3. 使用map和扩展语法而不是reduce
  4. 使用Object.entriesObject.fromEntries

1.使用reduceObject.keys

使用reduceObject.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调用移出循环,因此只进行一次,然后将各个键作为单独的参数传递给它(使用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 '16

2
@IamStalker,您尝试过吗?没关系,只要您在第二个参数中提供有效的函数即可。注意:我不知道.Filter结尾是什么,但是如果它是一个函数,则需要将其称为(x => x.Expression.Filters.Filter()
trincot 16-10-19

太长!可以在1行上完成。检查一下
Abdennour TOUMI

1
较新的功能可能会减少代码量,但也会降低性能。符合Ed 3的代码在大多数浏览器中的运行速度是其两倍。
RobG '17


20

如果您愿意使用下划线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!
艾拉·赫尔曼

@mibbit可以更具体吗?我认为这只是正确实现回调的问题
Bogdan D

9

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”的对象)?
皮尔

1
@ Pille,OP要求它像这样(请注意!predicate在自己的代码中带有否定过滤器)。
特里科特

7

正如patrick所说的,这是一个坏主意,因为它几乎肯定会破坏您可能希望使用的任何第三方代码。

如果您扩展Object.prototype,所有类似jquery或prototype的库都将中断,原因是对象上的惰性迭代(不带hasOwnProperty检查)将中断,因为您添加的功能将成为迭代的一部分。


赞成简洁明了地解释这是一个坏主意的“原因”。
Michael Liquori'5

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(),不仅可以按功能过滤,还可以接受要包含的键数组。可选的第三个参数将允许您反转滤波器。

鉴于:

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>


请检查我的
原始

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


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);

1

从2020年开始使用Vanilla 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} 

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需要过滤的普通对象。
特里科特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.