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

Chr*_*ker 349 javascript

说我有一个对象:

elmo = { 
  color: 'red',
  annoying: true,
  height: 'unknown',
  meta: { one: '1', two: '2'}
};
Run Code Online (Sandbox Code Playgroud)

我想用一个属性的子集创建一个新对象.

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

 //=> { color: 'red', height: 'unknown' }
Run Code Online (Sandbox Code Playgroud)

我怎么能实现这个目标?

小智 560

使用对象解构和属性速记

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

console.log(picked); // { a: 5, c: 7 }
Run Code Online (Sandbox Code Playgroud)


来自Philipp Kewisch:

这实际上只是一个即时调用的匿名函数.所有这些都可以在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 });

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

console.log(picked)
console.log(picked2)
Run Code Online (Sandbox Code Playgroud)

  • 这实际上只是一个即时调用的匿名函数.所有这些都可以在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});` (46认同)
  • 你是怎么知道怎么做的?在我看过的任何文档或文章(包括MDN)中都没有显示在Object Destructuring中使用的箭头语法.这很好知道. (31认同)
  • 这里的缺点是您需要完全键入两次属性名称系列.在需要挑选许多属性的情况下,这可能是一个相当大的问题. (15认同)
  • 有没有办法用扩展运算符动态地做到这一点? (7认同)
  • @TomSarduy你可以使用rest来指定要删除的道具,例如`const {b,... picked} = object`会创建`picked`为`{a:5,c:7}`.您已指定删除b.但是,你可能会因为声明你没有使用的变量而对你抱怨. (4认同)
  • 我真的很喜欢这个,从看起来很酷的角度来看(它看起来真的很酷),但它是否比`const selected = {a: object.a, b: object.b}` 更实用,我认为它更容易阅读,更短。 (3认同)
  • @GershomMaes 对,我们需要为每个属性输入两次,这很痛苦。知道我们可以缩短代码吗? (2认同)
  • 现在,你怎样才能做到这一点而不重复自己呢? (2认同)
  • 在编写时这是一个很好的答案,但是 Estus Flask 下面的答案现在更好了,并对 JS 进行了所有改进。 (2认同)

Ygg*_*Ygg 125

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

例如pick(),你正是寻求的:

var subset = _.pick(elmo, ['color', 'height']);
Run Code Online (Sandbox Code Playgroud)

小提琴

  • 是的!您可以使用`_.omit(elmo,['voice'])`来返回除"语音"之外的所有内容 (7认同)
  • 同样适用于underscore.js (2认同)
  • 是否有任何功能可以只排除某些字段而不是选择?所以我的 json 中有大约 50 个字段,并且想要除了 2 个字段之外的所有内容。 (2认同)
  • 我不喜欢这种方法的地方是你将字段名称放在引号中,因此很容易出现拼写错误,常见的重构(例如在 IDE 中重命名属性)不会识别它,等等。 (2认同)
  • 您不需要下划线/lodash 来完成此操作。我相信普通的 js 解决方案更好。 (2认同)

Lau*_*ren 86

如果您使用ES6,则可以使用destruct以非常简洁的方式执行此操作.Destructing允许您使用spread轻松添加对象,但它也允许您以相同的方式创建子集对象.

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'};
Run Code Online (Sandbox Code Playgroud)

  • 这仅适用于删除字段,而不选择已知子集吗?可能会删除无限数量的未知字段,但这可能是某些人正在寻找的内容 (3认同)
  • 确实如此,但它可以删除几个已知字段,然后可以将这些字段重新分配给新对象,因此它仍然与这个问题相关。添加到答案中以进一步说明。 (2认同)

Jos*_*bou 68

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

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

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

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

编辑:es7版本相同:

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

编辑:也是一个很好的例子!让'pick'函数返回另一个函数.

const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
Run Code Online (Sandbox Code Playgroud)

上面的方法非常接近另一种方法,除了它可以让你动态构建一个"选择器".例如

pick('color', 'height')(elmo);
Run Code Online (Sandbox Code Playgroud)

这种方法特别简洁,你可以轻松地将所选择的"选择"传递给任何需要函数的东西,例如Array#map:

[elmo, grover, bigBird].map(pick('color', 'height'));
// [
//   { color: 'red', height: 'short' },
//   { color: 'blue', height: 'medium' },
//   { color: 'yellow', height: 'tall' },
// ]
Run Code Online (Sandbox Code Playgroud)

  • ES6版本性能较差,因为它会在每次迭代时生成所有属性的副本.它对O(n ^ 2)进行O(n)运算.与你的第一个代码块相当的ES6将是`const pick =(obj,props)=> props.reduce((a,e)=>(a [e] = obj [e],a),{}); ` (6认同)
  • es6使得通过箭头函数和Object.assign的返回更加清晰(因为分配给对象属性返回属性值,但Object.assign返回对象.) (2认同)

ale*_*lex 31

没有像核心库内置的内容,但你可以使用对象解构来做到这一点......

const {color, height} = sourceObject;
const newObject = {color, height};
Run Code Online (Sandbox Code Playgroud)

你也可以写一个实用功能吧...

const cloneAndPluck = function(sourceObject, keys) {
    const newObject = {};
    keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
    return newObject;
};

const subset = cloneAndPluck(elmo, ["color", "height"]);
Run Code Online (Sandbox Code Playgroud)

像Lodash这样的图书馆也有_.pick().

  • 太好了,我只需要将 forEach 更改为:keys.forEach(key => { newObject[key] = sourceObject[key]; });。如果有意义,请更新评论。 (2认同)

Cod*_*iac 25

我添加此答案是因为没有使用任何答案Comma operator

destructuring assignment,操作符非常简单

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

console.log(picked);
Run Code Online (Sandbox Code Playgroud)

  • 命名空间污染使得这完全不切实际。在作用域中已经存在与对象属性匹配的变量是非常常见的,这就是存在 prop 简写 + 解构的原因。很可能您已经像原始示例中一样定义了高度或颜色。 (4认同)
  • 正确的方法是声明临时变量,`let a, c; const pick = ({a,c} = 对象, {a,c})`。不幸的是,出于很好的原因,其他答案中没有建议使用逗号运算符,它并不比 `const {a, c} = object; 更容易。const pick = {a,c}`。 (4认同)
  • 请注意,此方法使用两个变量“ a”和“ c”污染了当前范围-注意不要根据上下文随机覆盖局部或全局变量。(已接受的答案通过在内联函数中使用两个局部变量来避免此问题,该函数在立即执行后超出范围。) (3认同)
  • 我的坏,我不知道之前我做了什么,但它没有用,但它确实可以用 (2认同)
  • 这是最好的表达,当谈到解构时 (2认同)
  • 这个解决方案很聪明,但它在严格模式下不起作用(即“使用严格”)。我收到“ReferenceError:a 未定义”。 (2认同)

mpe*_*pen 21

打字稿解决方案:

function pick<T extends object, U extends keyof T>(
  obj: T,
  paths: Array<U>
): Pick<T, U> {
  const ret = Object.create(null);
  for (const k of paths) {
    ret[k] = obj[k];
  }
  return ret;
}
Run Code Online (Sandbox Code Playgroud)

键入信息甚至允许自动完成:

归功于绝对类型U extends keyof T技巧!

打字稿游乐场


ied*_*mrc 20

我想提一提的是这里很不错的策展:

pick-es2019.js

Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => ['whitelisted', 'keys'].includes(key))
);
Run Code Online (Sandbox Code Playgroud)

pick-es2017.js

Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
Run Code Online (Sandbox Code Playgroud)

选择-es2015.js

Object.keys(obj)
.filter((key) => ['whitelisted', 'keys'].indexOf(key) >= 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
Run Code Online (Sandbox Code Playgroud)

省略-es2019.js

Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => !['blacklisted', 'keys'].includes(key))
);
Run Code Online (Sandbox Code Playgroud)

省略-es2017.js

Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
Run Code Online (Sandbox Code Playgroud)

省略-es2015.js

Object.keys(obj)
.filter((key) => ['blacklisted', 'keys'].indexOf(key) < 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
Run Code Online (Sandbox Code Playgroud)


Eve*_*ert 17

还有一个解决方案

var subset = {
   color: elmo.color,
   height: elmo.height 
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这看起来比任何答案都要可读得多,但也许这就是我!

  • 我更喜欢高效而不是花哨但令人困惑的代码,而在现实生活中的软件工程中,这是最易读和可维护的解决方案. (5认同)
  • 是的,但是对我来说,使用解构和速记符号的一个好处是它不易出错。如果我每次错误地复制和粘贴代码以“subset = {color: elmo.color, height: elmo.color}”结束时都能得到一分钱,那么我至少会得到一个.. . 好吧,也许一毛钱。 (2认同)
  • 我不得不同意。在不使用不需要的变量污染上下文的情况下,这是迄今为止最具可读性的解决方案。其余的看起来太混乱了。我更喜欢在看到代码的时候就理解它。 (2认同)

Est*_*ask 13

在每个JS版本中,任意选择的键列表的一线式都会变得更短:

ES5

Object.keys(obj)
.filter(function (key) { 
  return ['foo', 'bar'].indexOf(key) >= 0;
})
.reduce(function (obj2, key) {
  obj2[key] = obj[key];
  return obj2;
}, {});
Run Code Online (Sandbox Code Playgroud)

ES6

Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => Object.assign(obj2, { [key]: obj[key] }), {});
Run Code Online (Sandbox Code Playgroud)

或使用逗号运算符:

Object.keys(obj)
.filter(key => ['foo', 'bar'].indexOf(key) >= 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Run Code Online (Sandbox Code Playgroud)

ES2019

ECMAScript 2017具有Object.entriesArray.prototype.includes,ECMAScript 2019具有Object.fromEntries,可以在需要时进行填充,使任务更轻松:

Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => ['foo', 'bar'].includes(key))
)
Run Code Online (Sandbox Code Playgroud)

可以将单行代码重写为类似于Lodash pick/的帮助程序功能,omit其中键列表通过参数传递:

const pick = (obj, ...keys) => Object.fromEntries(
  Object.entries(obj)
  .filter(([key]) => keys.includes(key))
);

pick(obj, 'foo', 'bar');
Run Code Online (Sandbox Code Playgroud)

  • 我不喜欢 `.indexOf`/`.includes` 解决方案——那是在每次迭代上执行 `O(keys)` 查找 = `O(entries*keys)`。最好翻转逻辑并仅迭代键,然后您将得到总共“O(keys)”。 (5认同)
  • 这个答案如此之新,以至于没有得到应有的曝光,真是太可惜了。IMO应该是完整性,简单性,多功能性和公正性的公认答案。我会将ES6版本保留在最有用的代码段库中。 (4认同)
  • @EstusFlask这可能还为时过早,但是当有一个更快的大O sol'n需要相同的行数来实现时,我更喜欢这样做。如果您内联此代码可能没问题,但一旦您将其转换为实用函数,就应该对其进行优化(IMO),因为您不知道它将在哪里使用。 (4认同)

Art*_*vim 10

你也可以使用Lodash.

var subset = _.pick(elmo ,'color', 'height');
Run Code Online (Sandbox Code Playgroud)

补充一下,假设你有一系列的"elmo":

elmos = [{ 
      color: 'red',
      annoying: true,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'blue',
      annoying: true,
      height: 'known',
      meta: { one: '1', two: '2'}
    },{ 
      color: 'yellow',
      annoying: false,
      height: 'unknown',
      meta: { one: '1', two: '2'}
    }
];
Run Code Online (Sandbox Code Playgroud)

如果你想要相同的行为,使用lodash,你只需:

var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Run Code Online (Sandbox Code Playgroud)


Muh*_*nar 8

如本问题所述,在JavaScript中无法将结构化为动态命名的变量.

动态设置键,可以使用reduce函数而不改变对象,如下所示:

const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});

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

const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)
Run Code Online (Sandbox Code Playgroud)


Kam*_*ski 8

动态解决方案

['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})
Run Code Online (Sandbox Code Playgroud)

['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})
Run Code Online (Sandbox Code Playgroud)


Cos*_*tin 7

我发现的最简单的方法(不会创建不必要的变量)是一个可以调用的函数,并且与 lodash 的工作方式相同,如下所示:

pick(obj, keys){
    return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
Run Code Online (Sandbox Code Playgroud)

例如:

pick(obj, keys){
    return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
const obj = {a:1, b:2, c:3, d:4}
const keys = ['a', 'c', 'f']
const picked = pick(obj,keys)
console.log(picked)
Run Code Online (Sandbox Code Playgroud)

pick(obj, keys){
    return  Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
Run Code Online (Sandbox Code Playgroud)


Kas*_*zar 5

picklodash库的使用方法(如果已在使用)。

var obj = { 'a': 1, 'b': '2', 'c': 3 };

_.pick(object, ['a', 'c']);

// => { 'a': 1, 'c': 3 }
Run Code Online (Sandbox Code Playgroud)

https://lodash.com/docs/4.17.10#pick


adj*_*nks 5

使用带有速记对象字面量语法的“with”语句

还没有人演示过这种方法,可能是因为它很糟糕而且你不应该这样做,但我觉得它必须被列出来。

var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
  var output =  {a,b,f}
}
console.log(output)
Run Code Online (Sandbox Code Playgroud)

优点:您不必输入两次属性名称。

缺点:出于多种原因,不推荐使用“ with ”语句。

结论:效果很好,但不要使用它。


小智 5

对象数组

const aListOfObjects = [{
    prop1: 50,
    prop2: "Nothing",
    prop3: "hello",
    prop4: "What's up",
  },
  {
    prop1: 88,
    prop2: "Whatever",
    prop3: "world",
    prop4: "You get it",
  },
]
Run Code Online (Sandbox Code Playgroud)

通过以这种方式解构对象可以实现创建一个或多个对象的子集。

const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Run Code Online (Sandbox Code Playgroud)