使用Underscore.js从对象中删除空属性/假值

88

我有一个包含多个属性的对象。我希望移除任何具有假值的属性。

对于数组,可以使用compact方法来实现,但是对于对象呢?


为了避免在各个存储库之间复制粘贴,您可以使用Bit导入此组件(其中有3个测试通过和MIT许可证)。您也可以尝试使用此NPM包(这可能对于小组件来说过于繁琐)。 - JoniS
13个回答

173
自从Underscore版本1.7.0以后,你可以使用_.pick函数:
_.pick(sourceObj, _.identity)

解释

_.pick的第二个参数可以是一个用于选择值的谓词函数。谓词返回真值的值将被选中,而谓词返回假值的值将被忽略。

pick _.pick(object, *keys)

返回object的副本,仅包含允许的keys(或有效键数组)的值。也可以接受一个指示要选择哪些键的谓词。

_.identity是一个辅助函数,它返回其第一个参数,这意味着它也可以作为一个谓词函数,选择真值并拒绝假值。Underscore库还提供了一系列其他谓词函数,例如_.pick(sourceObj, _.isBoolean)将仅保留布尔属性。

如果您经常使用这种技术,您可能希望使其更加表达清晰:

var pickNonfalsy = _.partial(_.pick, _, _.identity); // Place this in a library module or something
pickNonfalsy(sourceObj);

Underscore版本1.6.0也提供了_.pick,但它不接受谓词函数而只接受键列表。

2
特别感谢提到_.identity函数,非常方便。 - ivkremer
9
非常方便!你也可以使用 _.omit(sourceObj, _.isUndefined) 来仅移除 undefined 值(允许 false、null、0)。 - Ben Patterson
1
还可以使用pick(obj, Boolean)来消除假值,同样的方法也可以用在arr.filter(Boolean)上,清理数组中的假值。 - David Chase
3
在ES6中,这变成了_.pick(sourceObj, prop => prop)。 (翻译者注:该代码使用了lodash库中的pick函数,用于从对象中选择指定的属性并返回一个新对象。在ES6之前的版本中,可以写成_.pick(sourceObj, _.identity),其中_.identity是一个返回参数本身的函数。而在ES6中,箭头函数的语法简洁明了,所以可以直接写成箭头函数的形式。) - Deniz Ozger
17
在 lodash 4.4.0 中,_.pick 可以使用属性名称进行操作。如果需要此功能,请使用 _.pickBy,如文章所述。 - zooblin
显示剩余3条评论

49

你可以制作自己的下划线插件(mixin):

_.mixin({
  compactObject: function(o) {
    _.each(o, function(v, k) {
      if(!v) {
        delete o[k];
      }
    });
    return o;
  }
});

然后将其用作本地的下划线方法:

var o = _.compactObject({
  foo: 'bar',
  a: 0,
  b: false,
  c: '',
  d: null,
  e: undefined
});

更新

正如@AndreiNeculau评论中所指出的,这个 mixin 会影响原始对象,而原始的 compact underscore 方法返回一个数组的副本
为了解决这个问题并使我们的 compactObject 表现得更像它的“表亲”,这里有一个小的更新:

_.mixin({
  compactObject : function(o) {
     var clone = _.clone(o);
     _.each(clone, function(v, k) {
       if(!v) {
         delete clone[k];
       }
     });
     return clone;
  }
});

1
由于问题具有下划线引用,因此最好提到它的行为与_.compact不同。它将删除属性,而不是仅使用真值创建浅克隆。请参见下面的https://dev59.com/lGYq5IYBdhLWcg3w8lNo#19750822 - Andrei Neculau
@AndreiNeculau 没错!我之前好像漏掉了那个。请看我的更新回答。 - gion_13
3
为什么要先复制对象的所有属性,然后循环遍历并删除 falsy 的属性?这样效率不高。此外,一般不建议使用 delete,因为它会立即暴露原型链中具有相同名称的属性,并且由于 "hidden classes"(V8),性能也会受到影响——改变对象结构会导致引擎额外工作。最好和最简短的解决方案是 _.pick(o, _.identity) - Radko Dinev

46

快速而清晰: _.omitBy( source, i => !i );

这是对 Emil 回答的一种反向陈述。在我看来,这种方式更易于理解,更加自我说明。

如果没有 ES6 的优势,则略显不够简洁: _.omitBy( source, function(i){return !i;});

另一种方法: _.omitBy( source, _.isEmpty)

使用 _.isEmpty 而非 _.identity 来判断真假性时,将方便地从集合中删除空数组和对象,但也可能不便地删除数字和日期。因此,结果并不是 OP 问题的精确回答,但在寻求删除空集合时可能会有用。


8
在 Lodash 4.0 中,此功能现在属于 omitBy。https://lodash.com/docs#omitBy - JackMorrissey
3
我认为这与以下代码是相同的:_.pick(source, i => i);这个代码避免了否定操作。 - Jeff Lowery
2
@JeffLowery 这在 Lodash 中甚至更好,因为默认谓词是恒等函数!只需要 _.pickBy(source) - Shibumi
注意:数字被视为空。_.isEmpty(5) === true。因此,数值将被忽略。 - Sir.Nathan Stassen

21

使用lodash的transform函数,

_.transform(obj, function(res, v, k) {
  if (v) res[k] = v;
});

23
使用 Lodash 的 _.pick(obj, _.identity) 可以更简洁地实现相同的功能。 - evilive
这个答案或@evilive的评论就是答案。 - Radko Dinev
2
基于上述评论的一个更短的变体是 var compactObject = _.partialRight(_.pick, _.identity); - zaboco
4
在Lodash v4中,应该使用_.pickBy而不是_.pick - laggingreflex
是的,_.pickBy(object) 就是你所需要的。 - wdetac

19
Object.keys(o).forEach(function(k) {
    if (!o[k]) {
        delete o[k];
    }
});

1
可以使用下划线来进行.keys.forEach操作。 - Felix Kling
这在Underscore中会是什么样子呢?我正在试图把它拼凑起来... - user1082754
+1 这太棒了。请给我 JavaScript 的 forEach 方法的链接。 - xkeshav
@diEcho https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach - Florian Margaine

9
您可以创建一个浅克隆:
_(obj).reduce(function(a,v,k){ 
     if(v){ a[k]=v; } 
     return a; 
},{});

5

突然间我需要创建一个函数来递归地删除 falsies。希望这能帮到你。我正在使用 Lodash。

var removeFalsies = function (obj) {
    return _.transform(obj, function (o, v, k) {
        if (v && typeof v === 'object') {
            o[k] = _.removeFalsies(v);
        } else if (v) {
            o[k] = v;
        }
    });
};

_.mixin({ 'removeFalsies': removeFalsies });

然后您可以使用它:
var o = _.removeFalsies({
  foo: 'bar',
  a: 0,
  b: false,
  c: '',
  d: null,
  e: undefined,
  obj: {
    foo: 'bar',
    a: 0,
    b: false,
    c: '',
    d: null,
    e: undefined
  }
});

// {
//   foo: 'bar',
//   obj: {
//     foo: 'bar'
//   }
// }

5

使用 delete 来释放对象。

for(var k in obj){

  if(obj.hasOwnProperty(k) && !obj[k]){
    delete obj[k];
  }
}

既然他想要一个下划线的解决方案,你可以使用underscore的方法之一来迭代数组。 - gion_13

2

我明白这个问题具体要求使用underscore如何实现,但是因为我从谷歌来到了这里,我想添加一下如何使用ES6来实现,并且我真的很喜欢这种方法。

Object.fromEntries(Object.entries(obj).filter(([k,v],i)=>v))

原因是它会创建一个新的对象(而不是修改先前的对象),不使用任何库,不定义任何变量,并且不会污染当前作用域。


1

补充gion_13的答案:

_.mixin({
  compactObject : function(o) {
     var newObject = {};
     _.each(o, function(v, k) {
       if(v !== null && v !== undefined) {
         newObject[k] = v
       }
     });
     return newObject;
  }
});

这个代码创建一个新对象并添加键值对,而不是复制所有内容然后删除键值对。这是微小的区别。
更重要的是,它显式地检查null和undefined,而不是falsey值,这将删除值为false的键值对。

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