JavaScript:对象的filter()方法

355

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

在JavaScript中,我该如何为Object实现一个filter()函数呢?

假设我有以下这个对象:

var foo = {
    bar: "Yes"
};

我想编写一个适用于 Objectfilter() 函数:

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错误。

Object.prototype.filter = function(predicate) {
  var result = {};
  for (key in this) {
    if (this.hasOwnProperty(key) && !predicate(this[key])) {
      console.log("copying");
      result[key] = this[key];
    }
  }
  return result;
};

var foo = {
  bar: "Yes",
  moo: undefined
};

foo = foo.filter(function(property) {
  return typeof property === "undefined";
});

document.getElementById('disp').innerHTML = JSON.stringify(foo, undefined, '  ');
console.log(foo);
#disp {
  white-space: pre;
  font-family: monospace
}
<div id="disp"></div>


1
你具体遇到了哪些错误? - NT3RP
你遇到了哪些错误?如果可能的话,请将它们发布出来 :) - Zack The Human
关于jQuery和扩展Object.prototype的脚本,存在一些不明确的历史记录:http://bugs.jquery.com/ticket/2721 - Crescent Fresh
正是我需要的,只是你必须从!predicate(this[key])中删除"!"才能得到真正的过滤器方法。 - NoxFly
那些提到扩展Object原型的人,如果不是实习生,我建议立即解雇他们。没有任何使用情况需要这样做。你基本上正在重新编写这个语言,因为你拿走了JS中的一切并说:“让我稍微改变一下”。是的,语言允许你干扰它的最高原型,但你真的应该更好地了解这些知识。为了给你一个类比 - 想象一下,如果你把你的车变得有点不同,当速度在59和60之间时,刹车不再起作用。 - DanteTheSmith
19个回答

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

我的个人解决方案:

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


3
如果你的对象中有Symbol属性需要过滤,那么你不能使用Object.keys Object.entries Object.fromEntries等方法,因为:

Symbol键不是可枚举的

你可以使用 Reflect.ownKeys 方法,并在 reduce 中过滤键。
Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && {...a, [k]: o[k]} || a, {});

打开开发者工具以输出日志 - 符号不会在 Stackoverflow UI 上记录

const bKey = Symbol('b_k');
const o = {
    a:                 1,
    [bKey]:            'b',
    c:                 [1, 3],
    [Symbol.for('d')]: 'd'
};

const allow = ['a', bKey, Symbol.for('d')];

const z1 = Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && {...a, [k]: o[k]} || a, {});

console.log(z1);                   // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
console.log(bKey in z1)            // true
console.log(Symbol.for('d') in z1) // true

这等同于这个

const z2 = Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && Object.assign(a, {[k]: o[k]}) || a, {});
const z3 = Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && Object.defineProperty(a, k, {value: o[k]}) || a, {});

console.log(z2); // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
console.log(z3); // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}

在一个filter()函数中,可以传递一个可选的target对象

const filter = (o, allow, t = {}) => Reflect.ownKeys(o).reduce(
    (a, k) => allow.includes(k) && {...a, [k]: o[k]} || a, 
    t
);

console.log(filter(o, allow));           // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
console.log(filter(o, allow, {e: 'e'})); // {a: 1, e: "e", Symbol(b_k): "b", Symbol(d): "d"}

2
你也可以像这样做,通过过滤条目以查找提供的键并返回该值。
   let func = function(items){
      let val
      Object.entries(this.items).map(k => {
        if(k[0]===kind){
         val = k[1]
        }
      })
      return val
   }

1

我只是想分享一下我的做法,因为这样可以避免创建额外的函数,我认为这样更加简洁,而且我没有看到过这个答案:

let object = {a: 1, b: 2, c: 3};
[object].map(({a,c}) => ({a,c}))[0]; // {a:1, c:2}

很酷的是,它也适用于对象数组:

let object2 = {a: 4, b: 5, c: 6, d: 7};
[object, object2].map(({a,b,c,d}) => ({a,c})); //[{"a":1,"c":3},{"a":4,"c":6}]
[object, object2].map(({a,d}) => ({a,d})); //[{"a":1,"d":undefined},{"a":4,"d":7}]

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

如果您不需要原始对象,则可以使用以下简单且非常无聊的答案,这不会浪费内存:

const obj = {'a': 'want this', 'b': 'want this too', 'x': 'remove this'}
const keep = new Set(['a', 'b', 'c'])

function filterObject(obj, keep) {
  Object.keys(obj).forEach(key => {
    if (!keep.has(key)) {
      delete obj[key]
    }
  })
}

如果您只需要过滤少量对象,并且您的对象没有太多的键,那么您可能不想费心构建一个Set,这种情况下可以使用array.includes代替set.has


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

-2

在这些情况下,我使用jquery $.map来处理对象。正如其他答案中提到的那样,改变本地原型不是一个好的做法(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes)

以下是一个过滤示例,只需检查对象的某个属性即可。如果条件为真,则返回自己的对象;如果不是,则返回undefinedundefined属性将使该记录从您的对象列表中消失;

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

2
$.map可以接受一个对象,但它返回一个数组,因此原始属性名称会丢失。OP需要一个过滤后的普通对象。 - trincot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接