如何获取javascript对象属性的子集

IT技术 javascript object
2021-02-04 04:01:58

说我有一个对象:

elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};

我想使用其属性的子集创建一个新对象。

 // pseudo code
 subset = elmo.slice('color', 'height')

 //=> { color: 'red', height: 'unknown' }

我怎样才能做到这一点?

6个回答

使用对象解构和属性速记

const object = { a: 5, b: 6, c: 7  };
const picked = (({ a, c }) => ({ a, c }))(object);

console.log(picked); // { a: 5, c: 7 }


菲利普·凯维希 (Philipp Kewisch):

这实际上只是一个被立即调用的匿名函数。所有这些都可以在 MDNDestructuring Assignment页面上找到这是一个展开的表格

let unwrap = ({a, c}) => ({a, c});

let unwrap2 = function({a, c}) { return { a, c }; };

let picked = unwrap({ a: 5, b: 6, c: 7 });

let picked2 = unwrap2({a: 5, b: 6, c: 7})

console.log(picked)
console.log(picked2)

有没有办法用扩展运算符动态地做到这一点?
2021-03-12 04:01:58
@TomSaduy 如果您想指定要删除的道具,您可以使用 rest,例如const { b, ...picked } = object将创建picked{ a: 5, c: 7 }. 您已指定简单地删除 b。不过,你的 eslint 可能会因为你声明一个你没有使用的 var 而生气。
2021-03-21 04:01:58
这里的一个缺点是您需要两次完全输入属性名称系列。在需要选择许多属性的情况下,这可能是一个相当大的问题。
2021-04-03 04:01:58
你是如何了解如何做到这一点的?在我见过的任何文档或文章(包括 MDN)中,都没有显示对象解构中使用的箭头语法。很高兴知道这一点。
2021-04-06 04:01:58
这实际上只是一个被立即调用的匿名函数。所有这些都可以在 MDN 的 Destructuring Assignment 页面上找到。这是一个扩展形式: let unwrap = ({a, c}) => ({a, c}); let unwrap2 = function({a, c}) { return { a, c }; }; let picked = unwrap({ a: 5, b: 6, c: 7 });
2021-04-09 04:01:58

我建议看看Lodash它有很多很棒的实用功能。

例如,pick()这正是您所寻求的:

var subset = _.pick(elmo, ['color', 'height']);

小提琴

是否有任何功能可以只排除某些字段而不是选择?所以我的 json 中有大约 50 个字段,并且想要除了 2 个字段之外的所有内容。
2021-03-10 04:01:58
我不喜欢这种方法的一点是,您将字段名称放在引号中,因此它很容易出现拼写错误,常见的重构(例如在 IDE 中重命名属性)不会将其选中,等等。
2021-03-25 04:01:58
是的!你可以_.omit(elmo, ['voice'])用来返回所有东西,但voice
2021-03-31 04:01:58
与 underscore.js 相同
2021-04-08 04:01:58
你不需要下划线/lodash 来实现这一点。我相信 vanilla js 解决方案更好。
2021-04-08 04:01:58

如果您使用的是 ES6,有一种非常简洁的方法可以使用解构来做到这一点。解构允许您使用扩展轻松添加对象,但它也允许您以相同的方式创建子集对象。

const object = {
  a: 'a',
  b: 'b',
  c: 'c',
  d: 'd',
}

// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};

console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};
@ icc97 我认为这与伊万的回答相反。伊万在 IIFE 中指定了一个子集,而这个答案是使用扩展来基本上解构所有除外。也就是说,一个是黑名单,另一个是白名单。如果您使用 Lauren 的答案来解构包含敏感数据的对象(例如,用户会话),这可能很重要
2021-03-14 04:01:58
是的,但它可以删除几个已知字段,然后可以将这些字段重新分配给新对象,因此它仍然与这个问题相关。添加到答案以进一步说明。
2021-03-16 04:01:58
这仅适用于删除字段,而不适用于选择已知子集?可能有无数未知字段要删除,但这可能是某些人正在寻找的
2021-03-17 04:01:58
德拉特。不小心被否决了 :-(。我再次单击向下按钮以撤消(如提示经常指定的那样),但它反而锁定了我的投票!所以让我对此感到困惑。有时它会按预期工作,有时它会做相反的事情我想要!多年来一直无法预测。我不知道它是否取决于浏览器/操作系统/应用程序(现已弃用)/移动/触摸,还是什么!?偶尔我会进行轻率的编辑以更改投票。即使如此,我也不知道它将如何反应。上升会恢复中立,还是成为赞成票?在这里赞成就可以了。在其他地方我可能需要中立。但是如何?需要修复。
2021-03-17 04:01:58
这与@Ivan Nosov 的回答基本相同,尽管这里以更易于理解的方式进行了解释
2021-04-08 04:01:58

两种常见的方法是解构和传统的类似 Lodash 的pick/omit实现。它们之间的主要实际区别在于,解构需要一个键列表是静态的,不能省略它们,包括不存在的选择键,即它是包含的。这可能是可取的,也可能是不可取的,并且无法针对解构语法进行更改。

鉴于:

var obj = { 'foo-bar': 1, bar: 2, qux: 3 };

定期选择foo-bar, bar,baz的预期结果

{ 'foo-bar': 1, bar: 2 }

包容性采摘的预期结果

{ 'foo-bar': 1, bar: 2, baz: undefined }

解构

解构语法允许使用函数参数或变量对对象进行解构和重组。

限制是键列表是预定义的,它们不能作为字符串列出,如问题中所述。如果键是非字母数字的,例如foo-bar.

好处是它是 ES6 自然的高性能解决方案。

缺点是键列表是重复的,如果列表很长,这会导致代码冗长。由于在这种情况下解构复制了对象文字语法,因此可以按原样复制和粘贴列表。

国际金融研究所

let subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);

临时变量

let { 'foo-bar': foo, bar, baz } = obj;
let subset = { 'foo-bar': foo, bar, baz };

字符串列表

正如问题所要求的那样,任意选择的键列表由字符串组成。这允许不预定义它们并使用包含键名的变量,['foo-bar', someKey, ...moreKeys].

ECMAScript 2017 具有Object.entriesArray.prototype.includes,ECMAScript 2019 具有Object.fromEntries,它们可以在需要时进行填充。

单线

考虑到要选择的对象包含额外的键,从列表中迭代键通常比对象键更有效,如果需要省略键,反之亦然。

选择 (ES5)

var subset = ['foo-bar', 'bar', 'baz']
.reduce(function (obj2, key) {
  if (key in obj) // line can be removed to make it inclusive
    obj2[key] = obj[key];
  return obj2;
}, {});

省略 (ES5)

var subset = Object.keys(obj)
.filter(function (key) { 
  return ['baz', 'qux'].indexOf(key) < 0;
})
.reduce(function (obj2, key) {
  obj2[key] = obj[key];
  return obj2;
}, {});

选择 (ES6)

let subset = ['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.reduce((obj2, key) => (obj2[key] = obj[key], obj2));

省略 (ES6)

let subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2));

挑选(ES2019)

let subset = Object.fromEntries(
  ['foo-bar', 'bar', 'baz']
  .filter(key => key in obj) // line can be removed to make it inclusive
  .map(key => [key, obj[key]])
);

省略(ES2019)

let subset = Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => !['baz', 'qux'].includes(key))
);

可重用的功能

One-liners 可以表示为类似于 Lodashpick或 的可重用辅助函数omit,其中键列表通过参数 , 传递pick(obj, 'foo-bar', 'bar', 'baz')

let pick = (obj, ...keys) => Object.fromEntries(
  keys
  .filter(key => key in obj)
  .map(key => [key, obj[key]])
);

let inclusivePick = (obj, ...keys) => Object.fromEntries(
  keys.map(key => [key, obj[key]])
);

let omit = (obj, ...keys) => Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => !keys.includes(key))
);
@EstusFlask 这可能为时过早,但是当有一个更快的 big-O sol'n 需要相同的行数来实现时,我更喜欢这样。如果您内联此代码可能没问题,但是一旦将其转换为实用程序函数,就应该对 IMO 进行优化,因为您不知道将在哪里使用它。
2021-03-23 04:01:58
这个答案是最近的,因此没有得到应有的曝光,真是太遗憾了。IMO 它应该是完整性、简单性、多功能性和实用性的公认答案。我会将 ES6 版本保留在我最有用的代码段库中。
2021-03-25 04:01:58
@mpen顺便说一句,它也可能相反,对象中缺少许多列出的键,在这种情况下,遍历列表将更加多余,因此它始终取决于。
2021-03-29 04:01:58
我不喜欢.indexOf/.includes解决方案——O(keys)每次迭代都进行查找 = O(entries*keys)最好翻转逻辑并只迭代键,然后你就得到了O(keys)总数。
2021-04-03 04:01:58
@mpen 这个问题可以被认为是过早的优化,因为在大多数实际情况下它根本不会影响性能,所以选择一个容易理解的。对于适时的优化,人们可能会发现 Lodash 并没有那么快(实际上不是),并且使用数组方法迭代对象也不应该是首选。无论如何,我通常会发现自己使用迭代列表来选择和迭代对象键以进行省略,并更新了帖子以反映这一点。
2021-04-06 04:01:58

虽然它有点冗长,但您可以通过使用Array.prototype.reduce来完成其他人在 2 年前推荐下划线/lodash 的内容

var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});

这种方法从另一方面解决了它:不是获取一个对象并将属性名称传递给它来提取,而是获取一个属性名称数组并将它们缩减为一个新对象。

虽然在最简单的情况下它更冗长,但这里的回调非常方便,因为您可以轻松满足一些常见要求,例如将新对象上的“颜色”属性更改为“颜色”、展平数组等——任何一个从一个服务/库接收对象并构建其他地方需要的新对象时需要做的事情。虽然 underscore/lodash 是优秀的、实现良好的库,但这是我首选的方法,以减少对供应商的依赖,当我的子集构建逻辑变得更复杂时,这是一种更简单、更一致的方法。

编辑:es7 版本相同:

const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});

编辑:也是咖喱的一个很好的例子!让一个“pick”函数返回另一个函数。

const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});

上面的方法与另一种方法非常接近,只是它可以让您即时构建“选择器”。例如

pick('color', 'height')(elmo);

这种方法特别巧妙的是,您可以轻松地将所选的“选择”传递到任何带有函数的东西中,例如Array#map

[elmo, grover, bigBird].map(pick('color', 'height'));
// [
//   { color: 'red', height: 'short' },
//   { color: 'blue', height: 'medium' },
//   { color: 'yellow', height: 'tall' },
// ]
ES6 版本的性能较低,因为它在每次迭代时复制所有属性。它将 O(n) 操作变成 O(n^2)。与您的第一个代码块等效的 ES6 将是const pick = (obj, props) => props.reduce((a, e) => (a[e] = obj[e], a), {});
2021-03-23 04:01:58
es6 可以通过箭头函数和 Object.assign 的返回(因为分配给对象属性返回属性值,但 Object.assign 返回对象。)
2021-03-26 04:01:58
@ShevchenkoViktor 我实际上在我原来的 es6 版本中使用了这种方法,但在 @4castle 的评论之后改变了它。我认为传播更清晰,但对于代码中的较大对象来说这是一个巨大的差异,这些对象很容易遇到瓶颈(例如延迟从 返回的渲染数据fetch),因此我建议添加解释逗号运算符使用的注释。
2021-03-31 04:01:58
另一个 es6 注释:您几乎不再需要这样做了,因为您通常只是解构赋值或 args。例如function showToy({ color, height }) {只会把你需要的范围。reduce当您简化对象以进行序列化时,方法主要是有意义的。
2021-04-03 04:01:58
@4castle 是的,很好 - 重复这么多没有意义。我喜欢逗号语法 - 比一堆返回更好。
2021-04-08 04:01:58