对象的映射函数(而不是数组)

IT技术 javascript node.js functional-programming map-function
2021-02-08 10:25:04

我有一个对象:

myObject = { 'a': 1, 'b': 2, 'c': 3 }

我正在寻找一种与本机方法类似的方法,Array.prototype.map如下所示:

newObject = myObject.map(function (value, label) {
    return value * value;
});

// newObject is now { 'a': 1, 'b': 4, 'c': 9 }

JavaScript 有这样的map对象函数吗?(我想要这个用于 Node.JS,所以我不关心跨浏览器的问题。)

6个回答

没有原生mapObject对象,但这个怎么样:

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

Object.keys(myObject).map(function(key, index) {
  myObject[key] *= 2;
});

console.log(myObject);
// => { 'a': 2, 'b': 4, 'c': 6 }

但是您可以使用for ... in以下方法轻松迭代对象

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

for (var key in myObject) {
  if (myObject.hasOwnProperty(key)) {
    myObject[key] *= 2;
  }
}

console.log(myObject);
// { 'a': 2, 'b': 4, 'c': 6 }

更新

很多人都提到,前面的方法不返回新对象,而是对对象本身进行操作。就此而言,我想添加另一个解决方案,该解决方案返回一个新对象并将原始对象保持原样:

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

// returns a new object with the values at each key mapped using mapFn(value)
function objectMap(object, mapFn) {
  return Object.keys(object).reduce(function(result, key) {
    result[key] = mapFn(object[key])
    return result
  }, {})
}

var newObject = objectMap(myObject, function(value) {
  return value * 2
})

console.log(newObject);
// => { 'a': 2, 'b': 4, 'c': 6 }

console.log(myObject);
// => { 'a': 1, 'b': 2, 'c': 3 }

Array.prototype.reduce通过将前一个值与当前值进行某种程度的合并,将数组缩减为单个值。该链由一个空对象初始化{}在每次迭代中,myObject都会添加一个新的键,并将键作为值的两倍。

更新

有了新的 ES6 特性,有一种更优雅的方式来表达objectMap.

const objectMap = (obj, fn) =>
  Object.fromEntries(
    Object.entries(obj).map(
      ([k, v], i) => [k, fn(v, k, i)]
    )
  )
  
const myObject = { a: 1, b: 2, c: 3 }

console.log(objectMap(myObject, v => 2 * v)) 

TBH 我宁愿赞成一个仅(或最初)提供此答案中提出的第二个解决方案的答案。第一个有效并且不一定是错误的,但正如其他人所说,我不喜欢看到地图被那样使用。当我看到地图时,我的脑海中会自动想到“不可变数据结构”
2021-03-11 10:25:04
@KhalilRavanna 我认为你误读了这里的代码 - 这个答案没有map正确使用,因为它没有做return- 它map就像一个forEach电话一样滥用如果他真的这样做了, return myObject[value] * 2那么结果将是一个包含双倍原始值数组,而不是一个包含具有双倍值的原始键对象,后者显然是 OP 所要求的。
2021-03-18 10:25:04
@Amberlamps 你的.reduce例子是好的,但恕我直言,它仍然远远低于 a .mapfor Objects的便利性,因为你的.reduce回调不仅必须匹配.reduce工作方式,而且还要求myObject在词法范围内可用。特别是后者使得无法在回调中只传递一个函数引用,而是需要一个就地匿名函数。
2021-04-01 10:25:04
.map当您不打算使用生成的映射数组时,这不是合适的方法 - 如果您只想要副作用,例如在您的第一个代码中,您绝对应该forEach改用。
2021-04-07 10:25:04
Object.fromEntries是 ES2019,而不是 ES2015。此外,正如其他人所说,我强烈建议将.map第一个片段中的替换为forEach,或者其他比.map
2021-04-09 10:25:04

JS ES10 / ES2019 中的单行怎么

利用Object.entries()Object.fromEntries()

let newObj = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * v]));

同样的事情写成一个函数:

function objMap(obj, func) {
  return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, func(v)]));
}

// To square each value you can call it like this:
let mappedObj = objMap(obj, (x) => x * x);

此函数也使用递归来平方嵌套对象:

function objMap(obj, func) {
  return Object.fromEntries(
    Object.entries(obj).map(([k, v]) => 
      [k, v === Object(v) ? objMap(v, func) : func(v)]
    )
  );
}

// To square each value you can call it like this:
let mappedObj = objMap(obj, (x) => x * x);

ES7 / ES2016 中,您不能使用Objects.fromEntries,但您可以Object.assign结合使用扩展运算符计算键名称语法来实现相同的效果

let newObj = Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v})));

ES6 / ES2015不允许Object.entries,但您可以使用Object.keys

let newObj = Object.assign({}, ...Object.keys(obj).map(k => ({[k]: obj[k] * obj[k]})));

ES6 还引入了for...of循环,允许更命令式的风格:

let newObj = {}

for (let [k, v] of Object.entries(obj)) {
  newObj[k] = v * v;
}


数组.reduce()

代替Object.fromEntriesandObject.assign你也可以使用reduce做到这一点:

let newObj = Object.entries(obj).reduce((p, [k, v]) => ({ ...p, [k]: v * v }), {});


继承属性和原型链:

在一些罕见的情况下,您可能需要映射一个对象,该对象在其原型链上保存继承对象的属性在这种情况下Object.keys()Object.entries()不会起作用,因为这些函数不包括原型链。

如果需要映射继承的属性,可以使用for (key in myObj) {...}.

下面是这种情况的一个例子:

const obj1 = { 'a': 1, 'b': 2, 'c': 3}
const obj2 = Object.create(obj1);  // One of multiple ways to inherit an object in JS.

// Here you see how the properties of obj1 sit on the 'prototype' of obj2
console.log(obj2)  // Prints: obj2.__proto__ = { 'a': 1, 'b': 2, 'c': 3}

console.log(Object.keys(obj2));  // Prints: an empty Array.
console.log(Object.entries(obj2));  // Prints: an empty Array.

for (let key in obj2) {
  console.log(key);              // Prints: 'a', 'b', 'c'
}

但是,请帮我一个忙,避免继承:-)

嘿,你能提供更多关于为什么我们应该避免继承的信息吗?一些好文章就足够了:)
2021-03-22 10:25:04
Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))如果obj是空对象,该解决方案不起作用更改为:Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))
2021-03-23 10:25:04
要缩短您的答案,只需使用Object.entries({a: 1, b: 2, c: 3}).
2021-04-02 10:25:04
很漂亮,但我被Object.keys不枚举继承属性的事实所吸引我建议你添加一个警告。
2021-04-07 10:25:04
您有一些拼写错误(您(o, f)称为参数,但obj在正文中使用
2021-04-09 10:25:04

没有本地方法,但lodash#mapValues可以出色地完成这项工作

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// → { 'a': 3, 'b': 6, 'c': 9 }
_.mapValues()还提供密钥作为第二个参数。_.map()在这里不起作用,因为它只返回数组,而不是对象:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
2021-03-12 10:25:04
为什么获取数组有用?要求是映射对象。此外,在使用 Lodash 时也没有隐含的 HTTP 调用,Lodash 是一个基本上为此类事物构建的流行库,尽管 ES6 最近在某种程度上排除了使用实用函数库的必要性。
2021-03-13 10:25:04
无需为一个函数添加额外的 HTTP 调用和额外的库。无论如何,这个答案现在已经过时了,您只需调用Object.entries({a: 1, b: 2, c: 3})即可获取数组。
2021-03-17 10:25:04
更不用说您应该实际使用,_.map()以便您将密钥作为第二个参数 - 根据 OP 的要求。
2021-03-23 10:25:04
额外的 HTTP 调用来拉 Lodash,我猜。如果这是您唯一的用例,那确实是一种矫枉过正。尽管如此,因为 Lodash 是一个如此广泛的库,所以有这个答案是有道理的。
2021-04-09 10:25:04

写一个很容易:

Object.map = function(o, f, ctx) {
    ctx = ctx || this;
    var result = {};
    Object.keys(o).forEach(function(k) {
        result[k] = f.call(ctx, o[k], k, o); 
    });
    return result;
}

使用示例代码:

> o = { a: 1, b: 2, c: 3 };
> r = Object.map(o, function(v, k, o) {
     return v * v;
  });
> r
{ a : 1, b: 4, c: 9 }

注意:此版本还允许您(可选)设置this回调上下文,就像Array方法一样。

编辑- 更改为删除 , 的使用Object.prototype,以确保它不会与map对象上命名的任何现有属性发生冲突

已经不起作用了:o = {map: true, image: false}冒着风险只是为了写作o.map(fn)而不是map(o,fn)
2021-03-17 10:25:04
@bfred.it,我已相应更新。我注意到这只会消除与同名属性发生冲突的风险。我查了一下,Object我认为如果 ES 确实获得了这个方法,那么它的属性太少Object.mapObject.prototype.map(这也与其他“静态”方法一致Object
2021-03-19 10:25:04
好的。:) 对这个问题的评论把我带到了这里。我会 +1 这个,因为接受的答案显然是误用map,但我必须说修改Object.prototype不适合我,即使它不可枚举。
2021-04-01 10:25:04
@JLRishe 谢谢。恕我直言,在 ES5 中,除了与其他方法发生冲突的(理论上的)风险之外,真的没有任何理由修改Object.prototype了。FWIW,我无法理解另一个答案是如何获得如此多的选票的,这是完全错误的。
2021-04-05 10:25:04
是的,碰撞是我想到的,尤其是像map.
2021-04-05 10:25:04

您可以使用Object.keys然后forEach在返回的键数组上:

var myObject = { 'a': 1, 'b': 2, 'c': 3 },
    newObject = {};
Object.keys(myObject).forEach(function (key) {
    var value = myObject[key];
    newObject[key] = value * value;
});

或者以更module化的方式:

function map(obj, callback) {
    var result = {};
    Object.keys(obj).forEach(function (key) {
        result[key] = callback.call(obj, obj[key], key, obj);
    });
    return result;
}

newObject = map(myObject, function(x) { return x * x; });

请注意,Object.keys返回一个仅包含对象自己的可枚举属性的数组,因此它的行为类似于for..in带有hasOwnProperty检查循环