【问题标题】:JavaScript: filter() for ObjectsJavaScript:对象的 filter()
【发布时间】:2011-07-01 14:58:21
【问题描述】:

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

如何在 JavaScript 中为 Objects 实现 filter()

假设我有这个对象:

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

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>

【问题讨论】:

  • 你遇到了什么错误,具体来说?
  • 您遇到了什么错误?如果可能的话发布它们:)
  • 关于 jQuery 和扩展 Object.prototype: bugs.jquery.com/ticket/2721 的脚本的历史有点模糊
  • 正是我需要的,除了你必须删除“!”在 !predicate(this[key]) 中有真正的过滤方法。
  • 那些甚至提到扩展对象原型的人,不是实习生,得到我的建议立即被解雇。没有任何用例是理想的解决方案。你基本上是在用它重写语言,因为你把 JS 中的所有东西都拿来说:让我让它有点不同。是的,这种语言是迟钝的,让你弄乱它的最高原型,但你真的应该知道得更好。打个比方——想象一下,如果你开着你的车,让它变得有点不同。如果速度在 59 到 60 之间,休息将不再有效。

标签: javascript jquery object filtering


【解决方案1】:

您也可以执行类似的操作,过滤条目以查找提供的键并返回值

   let func = function(items){
      let val
      Object.entries(this.items).map(k => {
        if(k[0]===kind){
         val = k[1]
        }
      })
      return val
   }

【讨论】:

    【解决方案2】:

    如果你不需要原始对象,这是一个简单,很无聊的答案,不会浪费内存:

    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 .

    【讨论】:

      【解决方案3】:

      首先,it's considered bad practice to extend 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 arrow syntax):

      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使用的exclusion条件相反),这样才符合how Array.prototype.filter 有效。

      2。如(1),结合Object.assign

      在上述解决方案中,comma operator 用于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 syntax):

      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)和相反的(即create an object from an array of key/value pairs)和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);

      谓词函数在这里获取一个键/值对作为参数,这有点不同,但在谓词函数的逻辑中允许更多的可能性。

      【讨论】:

      • @IamStalker,你试过了吗?没关系,只要您在第二个参数中提供有效函数即可。注意:我不知道.Filter最后是什么,但如果是函数,你需要调用它(x => x.Expression.Filters.Filter()
      • 较新的功能可能会减少代码,但它们也有助于slower performance。符合 Ed 3 的代码在大多数浏览器中的运行速度是其两倍以上。
      • 最后一个变体的 TypeScript 版本:gist.github.com/OliverJAsh/acafba4f099f6e677dbb0a38c60dc33d
      • 干得好!感谢您提供这些精彩的解释和示例。
      • 我毫不怀疑这是最好的解决方案。恭喜。我最喜欢的解决方案是结合entriesfromEntries。如此简单,但又如此易读、易于理解、功能强大且具有更高层次的抽象!
      【解决方案4】:

      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} 
      

      【讨论】:

      • 很棒的解决方案!将一个对象分解为entries,按所需条件过滤,然后使用fromEntries 创建一个新对象。
      • 我使用该解决方案通过includes 通过多个键过滤对象。 Object.fromEntries(Object.entries(romNumbers).filter(([key, value]) => ['a', 'b', 'c', 'd', 'e'].includes(key)))
      • 不知道您为什么重复给出的答案超过year earlier
      【解决方案5】:

      如果您的对象中有Symbol 属性,也应该过滤,您可以使用:Object.keysObject.entriesObject.fromEntries,...因为:

      Symbol 键不是enumerable

      您可以使用Reflect.ownKeysreduce 中的过滤键

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

      (打开 DevTools 进行日志输出 - 符号未记录在 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"}
      

      【讨论】:

        【解决方案6】:

        普通 ES6:

        var foo = {
            bar: "Yes"
        };
        
        const res = Object.keys(foo).filter(i => foo[i] === 'Yes')
        
        console.log(res)
        // ["bar"]
        

        【讨论】:

          【解决方案7】:

          我在需要时使用它:

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

          【讨论】:

            【解决方案8】:

            我自以为是的解决方案:

            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

            【讨论】:

              【解决方案9】:

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

              【讨论】:

              • @patrick: 给一个人一个面包,你可以喂他一天,教他如何烘焙,你会喂他一辈子(或者什么,我是丹麦人,我不知道正确的英语谚语;)
              • 你做错了... !predicate(obj[key]) 应该是 predicate(obj[key])
              • @pyrotechnick:不。首先,答案的要点是不要扩展Object.prototype,而是将函数放在Object上。其次,这是 OP 的代码。显然,OP 的意图是让.filter() 能够过滤积极的结果。换句话说,它是一个负过滤器,其中一个正返回值意味着它被 排除 从结果中。如果您查看 jsFiddle 示例,他会过滤掉现有的 undefined 属性。
              • @patrick dw:不。首先,我没有提到扩展/不扩展原型。其次,developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…——“创建一个新数组,其中包含通过所提供函数实现的测试的所有元素。”在全局上实现完全相反的效果似乎很愚蠢,不是吗?
              • @pyrotechnick:没错,你没有提到扩展/不扩展原型,这就是我的意思。你说我做错了,但我做的唯一“它”是告诉 OP 不要扩展Object.prototype。来自问题:“这有效...,但是当我将它添加到我的网站时...,我收到 JavaScript 错误” 如果 OP 决定以相反的行为实现 .filter() Array.prototpe.filter,这取决于他/她。如果您想通知 OP 代码错误,请在问题下方留下评论,但不要告诉我 做错了,因为它不是我的代码。
              【解决方案10】:

              如果您希望改变同一个对象而不是创建一个新对象。

              以下示例将删除所有 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);
              

              【讨论】:

                【解决方案11】:

                就像大家说的,不要乱搞原型。相反,只需编写一个函数即可。这是我的 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;
                };
                

                【讨论】:

                  【解决方案12】:

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

                  【讨论】:

                  • 看起来不错!但为什么它只返回另一个对象(而不是名称/filterValue 为“Alireza”的对象)?
                  • @Pille,OP 要求它是这样的(注意他们自己的代码中带有!predicate 的否定过滤器)。
                  【解决方案13】:

                  我创建了一个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>

                  【讨论】:

                  • 请检查我的香草answergist
                  • @trincot 谢谢,我更新了响应以返回对象的副本,而不是就地返回。
                  【解决方案14】:

                  在这些情况下,我使用 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;
                  });
                  

                  【讨论】:

                  • $.map 可以接受一个对象,但是它返回一个数组,所以原来的属性名丢失了。 OP 需要一个过滤后的普通对象。
                  【解决方案15】:

                  给定

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

                  然后:

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

                  // Helper
                  function filter(object, ...keys) {
                    return keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
                    
                  };
                  
                  //Example
                  const person = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};
                  
                  // Expected to pick only firstname and age keys
                  console.log(
                    filter(person, 'firstname', 'age')
                  )

                  【讨论】:

                  • 这不使用问题所要求的给定谓词函数。
                  【解决方案16】:

                  怎么样:

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

                  【讨论】:

                    【解决方案17】:

                    如果你愿意使用 underscorelodash,你可以使用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}
                    

                    【讨论】:

                    • lodash 是一个糟糕的解决方案,因为过滤空对象也会删除数字。
                    • 我只是为此使用了 lodash,这是一个很好的解决方案。谢谢@Bogdan D!
                    • @mibbit 你能更具体一点吗?我相信这只是正确实现回调的问题
                    【解决方案18】:

                    正如 patrick 已经说过的那样,这是一个坏主意,因为它几乎肯定会破坏您可能希望使用的任何 3rd 方代码。

                    如果您扩展 Object.prototype,所有库(如 jquery 或原型)都会中断,原因是对象的惰性迭代(没有 hasOwnProperty 检查)将中断,因为您添加的函数将成为迭代的一部分。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-06-15
                      • 2018-04-16
                      • 2019-04-04
                      • 1970-01-01
                      • 2014-04-18
                      相关资源
                      最近更新 更多