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


1062

我有一个对象:

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使用它,所以我不在乎跨浏览器的问题。)


1
大多数答案使用Object.keys,没有明确定义的顺序。这可能会带来问题,我建议Object.getOwnPropertyNames改为使用。
Oriol

14
令我惊讶的是,JS并没有提供这项难以置信的基本任务。
jchook

3
@Oriol你确定吗?根据MDN网站文档,数组元素的顺序在Object.keys和之间是一致的Object.getOwnPropertyNames。请参阅developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…–
巴特(Bart)

@Bart的顺序Object.keys取决于实现。参见stackoverflow.com/a/30919039/1529630
Oriol

4
@Oriol无论如何,您都不应该依赖对象中键的顺序,因此它与问题无关紧要-因为他从未指定顺序对他很重要。如果顺序对他很重要,那么他绝对不应该使用任何对象。
英国电信

Answers:


1538

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)) 


3
是的,对于这种问题,我不推荐我的解决方案。这就是为什么我使用其他更好的解决方案来更新答案的原因。
Amberlamps

1
我已经更新了答案-在重新阅读问题后,OP似乎希望使用具有相同键的对象,其中的值已使用提供的函数进行了更改,而不是就地修改原始对象。
Alnitak

13
@KhalilRavanna我认为您在这里误读了代码-此答案未map正确使用,因为它没有执行return- map就像在forEach打电话一样在滥用。如果他确实这样做了, return myObject[value] * 2那么结果将是一个包含原始值加倍的数组,而不是包含原始值加倍的对象的对象,后者显然是OP所要求的。
Alnitak

3
@Amberlamps您的.reduce示例是可以的,但是恕我直言,它仍然远远落后.map于Objects 的便利性,因为您的.reduce回调不仅必须匹配有效的方式.reduce,而且还要求myObject在词法范围内可用。特别是后者使得不可能仅在回调中传递函数引用,而需要使用就地匿名函数。
Alnitak

8
TBH我宁可赞成一个答案,该答案仅(或最初)提供了此答案中提出的第二个解决方案。第一个有效,不一定是错误的,但是正如其他人所述,我不希望看到地图被那样使用。当我看到地图时,我的大脑自动思考“不变的数据结构”
mjohnsonengr

307

普通JS(ES6 / ES2015)中具有立即变量分配的一个班轮怎么样?

利用扩展运算符计算的键名语法:

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

jsbin

使用reduce的另一个版本:

let newObj = Object.keys(obj).reduce((p, c) => ({...p, [c]: obj[c] * obj[c]}), {});

jsbin

作为函数的第一个示例:

const oMap = (o, f) => Object.assign({}, ...Object.keys(o).map(k => ({ [k]: f(o[k]) })));

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

jsbin

如果要映射嵌套对象递归功能性的风格,这是可以做到这样的:

const sqrObjRecursive = obj =>
  Object.keys(obj).reduce(
    (newObj, key) =>
      obj[key] && typeof obj[key] === "object"
        ? { ...newObj, [key]: sqrObjRecursive(obj[key]) } // recurse.
        : { ...newObj, [key]: obj[key] * obj[key] }, // square val.
    {}
  );       

jsbin

或者更需要这样,例如:

const sqrObjRecursive = obj => {
  Object.keys(obj).forEach(key => {
    if (typeof obj[key] === "object") obj[key] = sqrObjRecursive(obj[key]);
    else obj[key] = obj[key] * obj[key];
  });
  return obj;
};

jsbin

ES7 / ES2016开始,您可以像这样使用Object.entries()代替Object.keys()

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

引入了ES2019Object.fromEntries(),从而进一步简化了这一过程:

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


继承的属性和原型链:

在极少数情况下,您可能需要映射一个对象,该对象在其原型链上保留继承对象的属性。在这种情况下Object.keys()将无法工作,因为Object.keys()不会枚举继承的属性。如果您需要映射继承的属性,则应使用for (key in myObj) {...}

这是一个对象的示例,该对象继承了另一个对象的属性,并且Object.keys()在这种情况下不起作用。

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.

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

jsbin

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


1
很漂亮,但我发现Object.keys没有枚举继承的属性。我建议您添加警告。
David Braun

要缩短答案,只需使用Object.entries({a: 1, b: 2, c: 3})

1
您有一些错别字(您(o, f)obj
称其

4
是的,然后让下一个人花几分钟了解此功能的作用?:)
安德烈·波波夫

1
Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))如果obj为空对象,则解决方案不起作用。更改为:Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))
blackcatweb

115

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

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// → { 'a': 3, 'b': 6, 'c': 9 }

6
无需为一个功能添加额外的HTTP调用和额外的库。无论如何,这个答案现在已经过时了,您只需调用Object.entries({a: 1, b: 2, c: 3})即可获取数组。

10
为什么获取数组有用?要求是一个映射对象。同样,使用Lodash并没有隐含的HTTP调用,Lodash是一种基本上为这种类型的东西而构建的流行库,尽管最近有很多东西使ES6在某种程度上排除了使用实用程序函数库的必要性。
corse32

2
我猜想额外的HTTP调用可以拉Lodash。如果这是您唯一的用例,那么这确实是一个矫kill过正。尽管如此,因为Lodash是一个如此广泛的图书馆,所以有这个答案是有意义的。
igorsantos18年

2
更不用说您应该实际使用它,_.map()以便您将密钥作为第二个参数-根据OP的要求。
igorsantos18年

57

编写一个很简单:

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该对象上命名的任何现有属性发生冲突。


3
好。:)对这个问题的评论使我来到这里。我会为此+1,因为公认的答案显然是在误用map,但我必须说Object.prototype,即使无法枚举,修改也不适合我。
JLRishe

2
@JLRishe谢谢。在ES5中,恕我直言,除了与其他方法发生冲突的(理论上)风险外,实际上再也没有理由进行修改Object.prototype。FWIW,我不明白另一个答案如何获得尽可能多的选票,这是完全错误的。
Alnitak

7
@JLRishe您是老将...与时俱进并修改该原型!
Nick Manning

12
@NickManning您是年轻的胡扯和不负责任的行为。从我的草坪上离开!
JLRishe'3

3
已经行不通了:o = {map: true, image: false}。您冒着只写o.map(fn)而不是写的风险map(o,fn)
fregante

25

您可以使用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;
});

或更模块化的方式:

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检查的循环。


20

这是直接的bs,JS社区中的每个人都知道这一点。有应该是这样的功能:

const obj1 = {a:4, b:7};
const obj2 = Object.map(obj1, (k,v) => v + 5);

console.log(obj1); // {a:4, b:7}
console.log(obj2); // {a:9, b:12}

这是朴素的实现:

Object.map = function(obj, fn, ctx){

    const ret = {};

    for(let k of Object.keys(obj)){
        ret[k] = fn.call(ctx || null, k, obj[k]);
    });

    return ret;
};

总是必须自己实施这真是太烦了;)

如果您想要一些更复杂的东西,并且不会干扰Object类,请尝试以下操作:

let map = function (obj, fn, ctx) {
  return Object.keys(obj).reduce((a, b) => {
    a[b] = fn.call(ctx || null, b, obj[b]);
    return a;
  }, {});
};


const x = map({a: 2, b: 4}, (k,v) => {
    return v*2;
});

但是将此映射函数添加到Object是安全的,只是不要添加到Object.prototype。

Object.map = ... // fairly safe
Object.prototype.map ... // not ok

为什么要将此功能添加到全局Object对象?刚刚有const mapObject = (obj, fn) => { [...]; return ret; }
Amberlamps 16-10-14

7
因为它应该是Object上的方法:)
Alexander Mills

1
只要我们不搞砸Object.prototype,那么我们就应该没事
Alexander Mills

1
如果地图只是随机的模糊事物,那么可以肯定。但是.map通常将其理解为Functor接口,并且对“对象”没有唯一的Functor定义,因为javascript中的几乎任何东西都可以是一个Object,包括具有各自独特且定义的.map接口/逻辑的事物。
Dtipson '17

1
我认为回调的第一个参数应该是迭代元素本身,而不是键。例如,我假设它应该给我相同的对象,因为它通常在通用映射中发生:Object.map({prop:1},el => el),但是它返回具有相同键名值的键...
Gleb Dolzikov

17

我来到这里的目的是寻找并将对象映射到数组的答案,并得到此页面。万一您来这里寻找与我相同的答案,这里是如何映射和对象到数组的方法。

您可以使用map从对象返回新数组,如下所示:

var newObject = Object.keys(myObject).map(function(key) {
   return myObject[key];
});

6
这不能远程工作-它仅返回对象值的数组,而不是新对象。我不敢相信它有这么多赞成票。
Alnitak

1
您是正确的,因为他们正在寻找将对象映射到对象的答案,所以它无法正确回答问题。我来这里是为了寻找将对象映射到数组的答案,并得到了此页面,因此,我将保留我的答案并对其进行编辑,以反映这是可能由于以下原因而来到这里的人们的替代答案。寻找将对象映射到数组。
JoeTron

5
在ES7中,这将是微不足道的Object.values(myObject)
Alnitak

1
很高兴知道,但是有些人仍然必须使用旧版代码。
JoeTron

const obj = {foo:'bar',baz:42}; console.log(Object.entries(obj)); // [['foo','bar'],['baz',42]]
Bashirpour

12

公认的答案有两个缺点:

  • 它滥用Array.prototype.reduce,因为归约意味着更改复合类型的结构,这种情况下不会发生。
  • 它不是特别可重用的

ES6 / ES2015功能方法

请注意,所有功能均以咖喱形式定义。

// small, reusable auxiliary functions

const keys = o => Object.keys(o);

const assign = (...o) => Object.assign({}, ...o);

const map = f => xs => xs.map(x => f(x));

const mul = y => x => x * y;

const sqr = x => mul(x) (x);


// the actual map function

const omap = f => o => {
  o = assign(o); // A
  map(x => o[x] = f(o[x])) (keys(o)); // B
  return o;
};


// mock data

const o = {"a":1, "b":2, "c":3};


// and run

console.log(omap(sqr) (o));
console.log(omap(mul(10)) (o));

  • 在行A中o已重新分配。由于Javascript通过共享传递参考值,因此o会生成的浅表副本。现在,我们可以在o内部范围内omap进行更改而无需更改o父范围。
  • B行map的返回值被忽略,因为map执行的突变o。由于这种副作用仍然存在,omap并且在父作用域中不可见,因此完全可以接受。

这不是最快的解决方案,而是声明性和可重用的解决方案。这与单行,简洁但可读性较低的实现相同:

const omap = f => o => (o = assign(o), map(x => o[x] = f(o[x])) (keys(o)), o);

附录-为什么默认情况下对象不可迭代?

ES2015指定了迭代器和可迭代协议。但是对象仍然不可迭代,因此不可映射。原因是数据和程序级别混合在一起


1
看起来有点过度设计;我将6个函数归结为一个循环。其他解决方案要简单得多,可接受的答案也快5倍。
fregante

4
@ bfred.it您的评论的目的是什么?如果您喜欢微优化,则不应使用Array.prototype.reduce任何一种。我只想在此说明的是,已接受的答案以某种方式“滥用” Array.prototype.reduce以及如何实现纯功能映射。如果您对函数式编程不感兴趣,请忽略我的回答。

2
“减少手段来改变复合类型的结构”我认为那不是真的。产生一个长度完全相同甚至值完全相同的新数组的Array.reduce是完全合法的。Reduce可以用于重新创建地图和/或过滤器(或两者,如在复杂的换能器中,以reduce为基础)的功能。在FP中,这是一种折叠,但最终以相同类型折叠的折叠仍然是折叠。
Dtipson

2
@Dtipson你绝对正确。A foldmap(functor)更通用。但是,这是我从2016年年中以来的知识。那是永恒的一半:D

1
哈,是的。我花了多年的时间才意识到所有这些东西之间的相互联系以及更广泛的术语和界面生态系统。看起来很容易的事情变得非常复杂,而看起来似乎不太复杂的某些东西则整齐地抽象了。:)
Dtipson

10

JavaScript刚获得了新Object.fromEntries方法。

function mapObject (obj, fn) {
  return Object.fromEntries(
    Object
      .entries(obj)
      .map(fn)
  )
}

const myObject = { a: 1, b: 2, c: 3 }
const myNewObject = mapObject(myObject, ([key, value]) => ([key, value * value]))
console.log(myNewObject)

说明

上面的代码将Object转换为[[<key>,<value>], ...]可映射的嵌套Array()。Object.fromEntries将数组转换回对象。

这种模式的妙处在于,您现在可以轻松地在映射时考虑对象键。

文献资料

浏览器支持

Object.fromEntries目前仅受这些浏览器/引擎支持,但是仍然可以使用polyfill(例如@ babel / polyfill)。


2
基本上Object.fromEntries是与的相反Object.entriesObject.entries将对象转换为键值对列表。Object.fromEntries将键值对列表转换为对象。
orad

8

最低版本(es6):

Object.entries(obj).reduce((a, [k, v]) => (a[k] = v * v, a), {})

我认为这是错误的,因为有错字,我认为您的意思是=> Object.entries(obj).reduce((a, [k, v]) => [a[k] = v * v, a], {}),用尖括号而不是括号括起来。
亚历山大·米尔斯

@AlexanderMills,我的代码是正确的。了解更多关于括号内的逗号操作:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Yukulélé

知道了,您下次可能想解释一下,但是我想这就是评论的意思。
亚历山大·米尔斯

1
基本上,我的代码就相当于:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé

1
好答案。Object.entries().reduce是我认为使用ES6 +的最佳解决方案。如果它return在reducer中而不是implicit returnand comma运算符中使用语句,将会得到更多赞誉-简洁但难以理解恕我直言
Drenai

7

为了获得最佳性能。

如果您的对象不经常更改但需要经常迭代,则建议使用本机Map作为缓存。

// example object
var obj = {a: 1, b: 2, c: 'something'};

// caching map
var objMap = new Map(Object.entries(obj));

// fast iteration on Map object
objMap.forEach((item, key) => {
  // do something with an item
  console.log(key, item);
});

Object.entries已在Chrome,Edge,Firefox和Beta Opera中运行,因此这是一项面向未来的功能。它来自ES7,因此在无法正常工作的IE上https://github.com/es-shims/Object.entries进行填充。


如何使用销毁
КонстантинВан

1
@ K._过去非常慢,但我现在不了解性能。似乎V8 v 5.6引入了一些用于销毁的优化,但必须进行测量v8project.blogspot.co.uk
Pawel

或者,性能较低的es7但不可变:const fn = v => v * 2; const newObj = Object.entries(myObject).reduce((acc, [k,v]) => Object.assign({}, acc, {[k]: fn(v)}), {});
mikebridge

我想知道为什么其他答案都没有使用Object.entries。它比遍历键和使用obj [key]更干净。
corwin.amber

6

您可以在数组上使用map方法和方法,forEach但是如果要在数组上使用方法,Object则可以如下所示使用它:

使用Javascript(ES6)

var obj = { 'a': 2, 'b': 4, 'c': 6 };   
Object.entries(obj).map( v => obj[v[0]] *= v[1] );
console.log(obj); //it will log as {a: 4, b: 16, c: 36}

var obj2 = { 'a': 4, 'b': 8, 'c': 10 };
Object.entries(obj2).forEach( v => obj2[v[0]] *= v[1] );
console.log(obj2); //it will log as {a: 16, b: 64, c: 100}

使用jQuery

var ob = { 'a': 2, 'b': 4, 'c': 6 };
$.map(ob, function (val, key) {
   ob[key] *= val;
});
console.log(ob) //it will log as {a: 4, b: 16, c: 36}

或者您也可以使用其他循环$.each,例如下面的示例所示的方法:

$.each(ob,function (key, value) {
  ob[key] *= value;
});
console.log(ob) //it will also log as {a: 4, b: 16, c: 36}

$jQuery 在哪里?
masterxilo

是的,您可以同时使用$和jQuery
Haritsinh Gohil 18-10-13

1
@罗纳德(Ronald)我误会了,请参阅我的最新答案,感谢您没有投票,但真正意识到了我的错误,感谢社区的改善。
Haritsinh Gohil

@bcoughlan是的,这正是我也在评论中写的内容,它对数字进行平方,那么您想要什么?
Haritsinh Gohil

1
@HaritsinhGohil我的意思是说他批评您提供jQuery解决方案。的确,这些天确实有一些脱离jQuery的动力,当然还有Node,但是除非OP专门要求纯JS或Node,否则我认为可以假设有人在Web环境中工作并且使用jQuery是很好的几乎可以确定。
abalter

4

map function不存在的Object.prototype,但是你可以模拟它像这样

var myMap = function ( obj, callback ) {

    var result = {};

    for ( var key in obj ) {
        if ( Object.prototype.hasOwnProperty.call( obj, key ) ) {
            if ( typeof callback === 'function' ) {
                result[ key ] = callback.call( obj, obj[ key ], key, obj );
            }
        }
    }

    return result;

};

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

var newObject = myMap( myObject, function ( value, key ) {
    return value * value;
});

性能问题:typeof callback === 'function'绝对多余。1)map没有回调就没有意义2)如果callback不起作用,您的map实现只会运行无意义的for循环。另外,Object.prototype.hasOwnProperty.call( obj, key )有点矫kill过正。
ankhzet '16

3

我在尝试进行此操作的Google搜索中首先想到了这一点,并认为与其他最近发现此解决方案的人共享,我发现了使用npm包不可变的解决方案。

我认为这很有趣,因为不可变在他们自己的文档中使用了OP的EXACT情况-以下不是我自己的代码,而是从当前的不可变js文档中提取的:

const { Seq } = require('immutable')
const myObject = { a: 1, b: 2, c: 3 }
Seq(myObject).map(x => x * x).toObject();
// { a: 1, b: 4, c: 9 } 

并不是说Seq具有其他属性(“ Seq描述了一个惰性操作,允许它们通过不创建中间集合来有效地链接所有高阶收集方法(例如map和filter)”)和其他一些不可变js数据结构也可以非常有效地完成这项工作。

任何使用此方法的人当然都必须npm install immutable并且可能想要阅读文档:

https://facebook.github.io/immutable-js/


3

编辑:使用更新的JavaScript功能的规范方法是-

const identity = x =>
  x

const omap = (f = identity, o = {}) =>
  Object.fromEntries(
    Object.entries(o).map(([ k, v ]) =>
      [ k, f(v) ]
    )
  )

o对象在哪里,f是您的映射功能。或者我们可以说,给定from的函数a -> b,并且对象的类型值为type a,则生成对象的类型值为type b。作为伪类型签名-

// omap : (a -> b, { a }) -> { b }

最初的答案是为了演示强大的组合器而编写的,mapReduce它使我们能够以不同的方式思考我们的转型

  1. m映射功能–使您有机会在...之前转换传入的元素
  2. r归约函数–该函数将累加器与映射元素的结果结合在一起

直观地,mapReduce创建一个我们可以直接插入的新的异径管Array.prototype.reduce。但更重要的是,我们可以omap利用对象monoid Object.assign和来简单地实现对象函子实现{}

const identity = x =>
  x
  
const mapReduce = (m, r) =>
  (a, x) => r (a, m (x))

const omap = (f = identity, o = {}) =>
  Object
    .keys (o)
    .reduce
      ( mapReduce
          ( k => ({ [k]: f (o[k]) })
          , Object.assign
          )
      , {}
      )
          
const square = x =>
  x * x
  
const data =
  { a : 1, b : 2, c : 3 }
  
console .log (omap (square, data))
// { a : 1, b : 4, c : 9 }

注意,我们实际上必须编写的程序的唯一部分是映射实现本身–

k => ({ [k]: f (o[k]) })

也就是说,给定一个已知的对象o和一些键k,则构造一个对象,其计算属性k是调用f键值的结果o[k]

mapReduce如果我们首先进行摘要,我们将了解的排序潜力oreduce

// oreduce : (string * a -> string * b, b, { a }) -> { b }
const oreduce = (f = identity, r = null, o = {}) =>
  Object
    .keys (o)
    .reduce
      ( mapReduce
          ( k => [ k, o[k] ]
          , f
          )
      , r
      )

// omap : (a -> b, {a}) -> {b}
const omap = (f = identity, o = {}) =>
  oreduce
    ( mapReduce
        ( ([ k, v ]) =>
            ({ [k]: f (v) })
        , Object.assign
        )
    , {}
    , o
    )

一切工作相同,但omap现在可以在更高级别上进行定义。当然,新的方法Object.entries看起来很愚蠢,但是练习对于学习者仍然很重要。

您不会看到mapReduce这里的全部潜力,但是我同意这个答案,因为看到可以应用多少个位置很有趣。如果您对它的派生方式以及其他有用的方法感兴趣,请参见此答案


4
让我们宁可使用MapMap.entries,OK:D。我已经厌倦了将普通对象用作地图数据类型。

2
我同意Map应在可能的情况下使用,但不能完全取代对普通JS对象使用这些过程的需要。tho:D
谢谢您

2

基于@Amberlamps的答案,这是一个实用函数(作为注释,它看起来很丑)

function mapObject(obj, mapFunc){
    return Object.keys(obj).reduce(function(newObj, value) {
        newObj[value] = mapFunc(obj[value]);
        return newObj;
    }, {});
}

用途是:

var obj = {a:1, b:3, c:5}
function double(x){return x * 2}

var newObj = mapObject(obj, double);
//=>  {a: 2, b: 6, c: 10}

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


Object.prototype.map = function(fn){
    var oReturn = {};
    for (sCurObjectPropertyName in this) {
        oReturn[sCurObjectPropertyName] = fn(this[sCurObjectPropertyName], sCurObjectPropertyName);
    }
    return oReturn;
}
Object.defineProperty(Object.prototype,'map',{enumerable:false});





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


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

enumerable默认为false
谢谢

2

我的回复很大程度上基于此处获得最高评分的回复,希望每个人都能理解(在我的GitHub上也有相同的解释)。这就是为什么他对地图的重视:

Object.keys(images).map((key) => images[key] = 'url(' + '"' + images[key] + '"' +    
')');

该函数的目的是使用一个对象并使用可用于所有对象(对象和数组之类)的方法修改该对象的原始内容,而无需返回数组。JS中的几乎所有东西都是一个对象,因此,在继承流程中更远的元素可以在技术上潜在地使用那些对继承人可用的元素(并且出现相反的情况)。

之所以可行,是因为.map函数返回一个数组,要求您提供数组的显式或隐式RETURN,而不是简单地修改现有对象。实际上,您可以通过使用Object.keys欺骗程序,使该对象认为该对象是一个数组,这将允许您使用map函数及其对各个键所关联的值的作用(我实际上不小心返回了数组,但已将其修复)。只要没有正常意义上的回报,就不会创建具有原始对象stil完整且未按编程方式进行修改的数组。

这个特定的程序接受一个称为图像的对象,并获取其键的值,并附加url标签以在另一个函数中使用。原来是这样的:

var images = { 
snow: 'https://www.trbimg.com/img-5aa059f5/turbine/bs-md-weather-20180305', 
sunny: 'http://www.cubaweather.org/images/weather-photos/large/Sunny-morning-east-   
Matanzas-city- Cuba-20170131-1080.jpg', 
rain: 'https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg' };

...并修改为:

var images = { 
snow: url('https://www.trbimg.com/img-5aa059f5/turbine/bs-md-weather-20180305'),     
sunny: url('http://www.cubaweather.org/images/weather-photos/large/Sunny-morning-   
east-Matanzas-city- Cuba-20170131-1080.jpg'), 
rain: url('https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg') 
};

只要没有返回值,对象的原始结构就保持原样,允许常规属性访问。不要像正常一样返回一个数组,一切都会好起来的。目标是将原始值(images [key])重新分配给所需的内容,而不是其他任何内容。据我所知,为了防止数组输出,必须重新分配images [key],并且没有隐式或显式的返回数组的请求(变量赋值可以做到这一点,并且对我来说来回回荡)。

编辑:

要解决他关于新对象创建的另一种方法,以避免修改原始对象(为了避免意外地将数组创建为输出,似乎仍然有必要进行重新分配)。这些函数使用箭头语法,如果您只是想创建一个新对象以供将来使用,则它们是。

const mapper = (obj, mapFn) => Object.keys(obj).reduce((result, key) => {
                result[key] = mapFn(obj)[key];
                return result;
            }, {});

var newImages = mapper(images, (value) => value);

这些功能的工作方式如下:

mapFn使用稍后要添加的函数(在这种情况下为(value)=> value),并简单地返回在mapFn()中存储的任何内容作为该键的值(如果像他一样更改返回值,则乘以2)。 obj)[key],

然后在result [key] = mapFn(obj)[key]中重新定义与该键关联的原始值

并返回对结果执行的操作(累加器位于.reduce函数末尾启动的括号中)。

所有这些操作都在所选对象上执行,并且仍然不能对返回的数组进行隐式请求,并且仅在重新分配值时才起作用。如上所示,这需要进行一些心理体操,但会减少所需的代码行。输出与以下内容完全相同:

{snow: "https://www.trbimg.com/img-5aa059f5/turbine/bs-   
md-weather-20180305", sunny: "http://www.cubaweather.org/images/weather-
photos/lmorning-east-Matanzas-city-Cuba-20170131-1080.jpg", rain: 
"https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg"}

请记住,这适用于NON-NUMBERS。您可以通过简单地返回mapFN函数中的值来复制任何对象。


2

第一个功能回答了这个问题。它创建一个地图。

第二个功能不会创建地图。而是使用循环遍历现有对象中的属性 hasOwnProperty()。在某些情况下,此地图替代方法可能是更好的解决方案。

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


//creates a map (answers question, but function below is 
//much better in some situations)
var newObject = {};
makeMap();    
function makeMap() {
    for (var k in myObject) {
        var value = myObject[k];
        newObject[k] = value * value;
    }
    console.log(newObject); //mapped array
}


//Doesn't create a map, just applies the function to
//a specific property using existing data
function getValue(key) {
  for (var k in myObject) {
    if (myObject.hasOwnProperty(key)) {
      var value = myObject[key]
      return value * value; //stops iteration
    }
  }
}
Input: <input id="input" value="" placeholder="a, b or c"><br>
Output:<input id="output"><br>
<button onclick="output.value=getValue(input.value)" >Get value</button>


2
这不能回答最初的问题-它不是将值映射到具有提供的函数的新对象(即,它与Array.prototype.map完全不同)。
马特·布朗

@MattBrowne我添加了第二种方法来创建可以说是地图的东西。它很慢,但是可以执行OP要求的操作。第一种方法在许多情况下都是理想的,这就是为什么我使用循环和属性函数的原因。
维克多·斯托达德

1

如果您map不仅对ping值而且对ping 感兴趣,我已经编写了Object.map(valueMapper, keyMapper),其行为如下:

var source = { a: 1, b: 2 };
function sum(x) { return x + x }

source.map(sum);            // returns { a: 2, b: 4 }
source.map(undefined, sum); // returns { aa: 1, bb: 2 }
source.map(sum, sum);       // returns { aa: 2, bb: 4 }

3
我想是因为您基本上只是提供了一个链接作为答案,对此不予理owned。
克里斯·赖特

如果对任何人有用:npm install @mattisg/object.map
MattiSG '16

1

我需要一个允许修改键的版本(基于@Amberlamps和@yonatanmn答案);

var facts = [ // can be an object or array - see jsfiddle below
    {uuid:"asdfasdf",color:"red"},
    {uuid:"sdfgsdfg",color:"green"},
    {uuid:"dfghdfgh",color:"blue"}
];

var factObject = mapObject({}, facts, function(key, item) {
    return [item.uuid, {test:item.color, oldKey:key}];
});

function mapObject(empty, obj, mapFunc){
    return Object.keys(obj).reduce(function(newObj, key) {
        var kvPair = mapFunc(key, obj[key]);
        newObj[kvPair[0]] = kvPair[1];
        return newObj;
    }, empty);
}

factObject =

{
"asdfasdf": {"color":"red","oldKey":"0"},
"sdfgsdfg": {"color":"green","oldKey":"1"},
"dfghdfgh": {"color":"blue","oldKey":"2"}
}

编辑:稍作更改即可传入起始对象{}。允许它为[](如果键是整数)


1

我特别想对单个对象使用与数组相同的函数,并且希望保持简单。这为我工作:

var mapped = [item].map(myMapFunction).pop();

这没什么不同var mapped = myMapFunction(item)
谢谢

这比我在这里看到的所有其他“火箭科学”解决方案都简单得多。感谢您的分享:)
Yuri Teixeira

1

为了更精确地响应OP的要求,OP需要一个对象:

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

有一个map方法myObject.map

类似于Array.prototype.map,其用法如下:

newObject = myObject.map(函数(值,标签){
    返回值*值;
});
// newObject现在为{'a':1,'b':4,'c':9}

最好的恕我直言(用“ 接近要求的数量 ” +“不需要不必要的ES {5,6,7} ” 衡量)的答案是:

myObject.map = function mapForObject(callback)
{
  var result = {};
  for(var property in this){
    if(this.hasOwnProperty(property) && property != "map"){
      result[property] = callback(this[property],property,this);
    }
  }
  return result;
}

上面的代码避免了有意使用任何语言功能的情况,只有在最新的ECMAScript版本中才可用。使用上面的代码可以解决此问题:

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

myObject.map = function mapForObject(callback)
{
  var result = {};
  for(var property in this){
    if(this.hasOwnProperty(property) && property != "map"){
      result[property] = callback(this[property],property,this);
    }
  }
  return result;
}

newObject = myObject.map(function (value, label) {
  return value * value;
});
console.log("newObject is now",newObject);
替代测试代码在这里

除了有些人不愿接受之外,还可以将解决方案插入这样的原型链中。

Object.prototype.map = function(callback)
{
  var result = {};
  for(var property in this){
    if(this.hasOwnProperty(property)){
      result[property] = callback(this[property],property,this);
    }
  }
  return result;
}

在仔细监督下完成的操作不会产生任何不良影响,也不会影响map其他对象(即Array的map)的方法。



1

定义一个函数mapEntries。

mapEntries具有一个回调函数,该函数在对象中的每个条目上使用参数value,key和object进行调用。它应该返回一个新值。

mapEntries应该返回一个新对象,并带有从回调中返回的新值。

Object.defineProperty(Object.prototype, 'mapEntries', {
  enumerable: false,
  value: function (mapEntriesCallback) {
    return Object.fromEntries(
      Object.entries(this).map(
        ([key, value]) => [key, mapEntriesCallback(value, key, this)]
      )
    )
  }
})


// Usage example:

var object = {a: 1, b: 2, c: 3}
var newObject = object.mapEntries(value => value * value)
console.log(newObject)
//> {a: 1, b: 4, c: 9}

编辑: 以前的版本未指定这不是可枚举的属性


0

嘿,写了一些可能有用的mapper函数。

    function propertyMapper(object, src){
         for (var property in object) {   
           for (var sourceProp in src) {
               if(property === sourceProp){
                 if(Object.prototype.toString.call( property ) === '[object Array]'){
                   propertyMapper(object[property], src[sourceProp]);
                   }else{
                   object[property] = src[sourceProp];
                }
              }
            }
         }
      }

1
我猜你的意思是“我”而不是“嘿”
Carlo

0

另一种不同的做法是使用自定义json字符串化函数,该函数也可用于深层对象。如果您打算将其以json的形式发布到服务器,这可能会很有用

const obj = { 'a': 1, 'b': 2, x: {'c': 3 }}
const json = JSON.stringify(obj, (k, v) => typeof v === 'number' ? v * v : v)

console.log(json)
console.log('back to json:', JSON.parse(json))


0

我只处理字符串以减少豁免:

Object.keys(params).map(k => typeof params[k] == "string" ? params[k] = params[k].trim() : null);

0

TypeScript中的对象映射器

我喜欢使用的例子Object.fromEntries,如这一个,但尽管如此,他们不是很容易使用。使用Object.keys然后查找答案的答案key实际上是在进行不必要的多次查找。

我希望有一个Object.map功能,但我们可以创建自己的,并把它objectMap与修改既有能力keyvalue

用法(JavaScript):

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

// keep the key and modify the value
let obj = objectMap(myObject, val => val * 2);
// obj = { a: 2, b: 4, c: 6 }


// modify both key and value
obj = objectMap(myObject,
    val => val * 2 + '',
    key => (key + key).toUpperCase());
// obj = { AA: '2', BB: '4', CC: '6' }

代码(TypeScript):

interface Dictionary<T> {
    [key: string]: T;
}

function objectMap<TValue, TResult>(
    obj: Dictionary<TValue>,
    valSelector: (val: TValue, obj: Dictionary<TValue>) => TResult,
    keySelector?: (key: string, obj: Dictionary<TValue>) => string,
    ctx?: Dictionary<TValue>
) {
    const ret = {} as Dictionary<TResult>;
    for (const key of Object.keys(obj)) {
        const retKey = keySelector
            ? keySelector.call(ctx || null, key, obj)
            : key;
        const retVal = valSelector.call(ctx || null, obj[key], obj);
        ret[retKey] = retVal;
    }
    return ret;
}

如果您不使用TypeScript,则将以上代码复制到TypeScript Playground中以获取JavaScript代码。

另外,我把原因keySelector之后valSelector在参数列表,是因为它是可选的。

*亚历山大·米尔斯的回答值得称赞。


0
const mapObject = (targetObject, callbackFn) => {
    if (!targetObject) return targetObject;
    if (Array.isArray(targetObject)){
        return targetObject.map((v)=>mapObject(v, callbackFn))
    }
    return Object.entries(targetObject).reduce((acc,[key, value]) => {
        const res = callbackFn(key, value);
        if (!Array.isArray(res) && typeof res ==='object'){
            return {...acc, [key]: mapObject(res, callbackFn)}
        }
        if (Array.isArray(res)){
            return {...acc, [key]: res.map((v)=>mapObject(v, callbackFn))}
        }
        return {...acc, [key]: res};
    },{})
};
const mapped = mapObject(a,(key,value)=> {
    if (!Array.isArray(value) && key === 'a') return ;
    if (!Array.isArray(value) && key === 'e') return [];
    if (!Array.isArray(value) && key === 'g') return value * value;
    return value;
});
console.log(JSON.stringify(mapped)); 
// {"b":2,"c":[{"d":2,"e":[],"f":[{"g":4}]}]}

此函数递归地遍历对象和对象数组。如果返回未定义的属性,则可以删除

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.