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

Ran*_*lue 903 javascript functional-programming node.js map-function

我有一个对象:

myObject = { 'a': 1, 'b': 2, 'c': 3 }
Run Code Online (Sandbox Code Playgroud)

我正在寻找一个本机方法,类似于Array.prototype.map将使用如下:

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

// newObject is now { 'a': 1, 'b': 4, 'c': 9 }
Run Code Online (Sandbox Code Playgroud)

JavaScript是否具有map对象的这种功能?(我想将它用于Node.JS,所以我不关心跨浏览器问题.)

Amb*_*mps 1331

没有原生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 }
Run Code Online (Sandbox Code Playgroud)

但您可以使用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 }
Run Code Online (Sandbox Code Playgroud)

更新

很多人都提到以前的方法不返回新对象,而是操作对象本身.就此而言,我想添加另一个返回新对象的解决方案,并保留原始对象:

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

Array.prototype.reduce通过稍微将先前值与当前值合并,将数组减少为单个值.链由空对象初始化{}.在每次迭代时,myObject都会添加一个新的键,其正方形为值.

  • @KhalilRavanna我认为你误读了这里的代码 - 这个答案并没有正确地使用`map`,因为它没有做`return` - 它滥用`map`就好像它是一个`forEach`调用.如果他实际上_did_`返回myObject [value]*2`那么结果将是包含原始值加倍的_array_,而不是包含具有加倍值的原始键的_object_,后者显然是OP所要求的. (12认同)
  • TBH我宁愿投票给出答案,只有(或最初)提供了这个答案中提出的第二个解决方案.第一个起作用并且不一定是错误的,但正如其他人所说,我不喜欢看到地图被这样使用.当我看到地图时,我的思绪自动地思考"不可变数据结构" (7认同)
  • 当您不打算使用生成的映射数组时,`.map` 不是合适的方法 - 如果您只想要副作用,例如在您的第一个代码中,您绝对应该使用 `forEach` 代替。 (6认同)
  • 是的,我不建议我解决这类问题.这就是我用另一种更好的解决方案更新我的答案的原因. (3认同)
  • @Amberlamps你的`.reduce`例子没问题,但恕我直言,它仍然远远不能用于对象的`.map`的便利性,因为你的.reduce`回调不仅要与`.reduce的方式相匹配`工作,但也要求`myObject`在词法范围内可用.后者尤其使得不可能在回调中传递函数引用,而是需要就地匿名函数. (3认同)
  • `Object.fromEntries` 是 ES2019,而不是 ES2015。另外,正如其他人所说,我**强烈**建议将第一个片段中的“.map”替换为“forEach”,或者其他比“.map”更合适的内容 (2认同)

Rot*_*eti 266

如何在普通JS(ES6/ES2015)中使用即时变量赋值的单行程怎么样?

利用spread运算符计算出的键名语法:

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

jsbin

使用reduce的另一个版本:

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

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

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

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

jsbin

ES7/ES2016开始,您可以使用Object.entries()而不是Object.keys()像这样:

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


继承的属性和原型链:

在极少数情况下,您可能需要映射类似于对象的对象,该对象在其原型链上包含继承对象的属性.在这种情况下Object.fromEntries()将无法工作,因为Object.keys()不会枚举继承的属性.如果需要映射继承的属性,则应使用Object.keys().

下面是一个继承另一个对象属性的对象示例,以及for (key in myObj) {...}在这种情况下如何工作.

let newObj = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * v]));
Run Code Online (Sandbox Code Playgroud)

jsbin

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

  • 是的,然后让下一个人花几分钟了解此功能的作用?:) (4认同)

Mar*_*rio 105

没有本地方法,但是lodash#mapValues可以很好地完成工作

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// ? { 'a': 3, 'b': 6, 'c': 9 }
Run Code Online (Sandbox Code Playgroud)

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

Aln*_*tak 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;
}
Run Code Online (Sandbox Code Playgroud)

使用示例代码:

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

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

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

  • @NickManning你年轻的whippersnappers和你不负责任的行为.离开我的草坪! (11认同)
  • @JLR她是一个古老的geezer ...与时俱进并修改原型! (6认同)
  • 好.:)对[这个问题]的评论(http://stackoverflow.com/questions/28179509/transforming-nested-objects-to-an-array#28179747)把我带到了这里.我会+1,因为接受的答案显然是在滥用`map`,但我必须说修改`Object.prototype`并不适合我,即使它不是可枚举的. (3认同)
  • 这**已**不起作用:`o = {map:true,image:false}`.你正在_risking it_只是为了编写`o.map(fn)`而不是`map(o,fn)` (3认同)
  • @JLRishe谢谢.在ES5中的恕我直言,除了(理论上)与其他方法发生碰撞的风险之外,确实不再有**修改`Object.prototype`.FWIW,我无法理解其他答案如何获得尽可能多的选票,这是完全错误的. (2认同)
  • 是的,碰撞是我的想法,特别是像`map`这样的名字. (2认同)
  • @bfred.it,我已相应更新。我注意到这只会消除与同名属性发生冲突的风险。我查了一下,`Object` 上的属性太少了,我想如果 ES 真的获得了这个方法,它会是 `Object.map` 而不是 `Object.prototype.map`(这也与其他“静态"`Object` 上的方法) (2认同)

Mat*_*ens 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;
});
Run Code Online (Sandbox Code Playgroud)

或者以更模块化的方式:

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

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


Ale*_*lls 18

这是直接的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}
Run Code Online (Sandbox Code Playgroud)

这是天真的实现:

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

必须始终自己实现这一点非常烦人;)

如果你想要一些更复杂的东西,这不会干扰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;
});
Run Code Online (Sandbox Code Playgroud)

但是将这个map函数添加到Object是安全的,只是不要添加到Object.prototype.

Object.map = ... // fairly safe
Object.prototype.map ... // not ok
Run Code Online (Sandbox Code Playgroud)

  • 因为它应该是Object上的方法:) (7认同)

Joe*_*ron 17

我来到这里寻找并回答将对象映射到数组并得到此页面的结果.如果你来到这里寻找我的答案,这里是你如何映射和对象的数组.

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

var newObject = Object.keys(myObject).map(function(key) {
   return myObject[key];
});
Run Code Online (Sandbox Code Playgroud)

  • 这不会远程工作 - 它只返回对象值的数组,而不是新对象.我无法相信它有这么多的投票. (5认同)
  • 在ES7中,它通常是`Object.values(myObject)` (5认同)

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

  • 在A行o重新分配.由于Javascript通过共享传递参考值,因此o会生成一个浅表副本.我们现在能够oomap不改变o父范围的情况下进行变异.
  • 在B行map的返回值被忽略,因为map执行了变异o.由于这种副作用仍然存在于omap父范围内并且在父范围内不可见,因此完全可以接受.

这不是最快的解决方案,而是一个声明性和可重用的解决方案.这里的实现与单行,简洁但不太可读相同:

const omap = f => o => (o = assign(o), map(x => o[x] = f(o[x])) (keys(o)), o);
Run Code Online (Sandbox Code Playgroud)

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

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

  • @ bfred.it你评论的目的是什么?如果你喜欢微优化,你也不应该使用`Array.prototype.reduce`.我想在这里说明的是,接受的答案以某种方式"滥用"`Array.prototype.reduce`以及如何实现纯粹的功能映射.如果您对函数式编程不感兴趣,请忽略我的答案. (4认同)
  • “减少意味着改变复合类型的结构”我认为这不是真的。产生完全相同长度甚至完全相同值的新数组的 Array.reduce 是完全合法的。Reduce 可用于重新创建映射和/或过滤器的功能(或两者,如在复杂的转换器中,reduce 是其基础)。在 FP 中,它是一种折叠,以相同类型结束的折叠仍然是折叠。 (2认同)
  • @Dtipson 你说得对。`fold` 比 `map`(函子)更通用。然而,这是我从 2016 年中期开始的知识。那是半个永恒 :D (2认同)

HaN*_*riX 9

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

说明

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

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

文献资料

浏览器支持

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

  • 基本上,“Object.fromEntries”与“Object.entries”相反。`Object.entries` 将对象转换为键值对列表。`Object.fromEntries` 将键值对列表转换为对象。 (2认同)

Paw*_*wel 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);
});
Run Code Online (Sandbox Code Playgroud)

Object.entries已经在Chrome,Edge,Firefox和beta Opera中运行,因此它具有面向未来的功能.这是来自ES7所以polyfill它https://github.com/es-shims/Object.entries为IE它不起作用.


Yuk*_*élé 7

最小版本(es6):

Object.entries(obj).reduce((a, [k, v]) => (a[k] = v * v, a), {})
Run Code Online (Sandbox Code Playgroud)


Har*_*hil 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}
Run Code Online (Sandbox Code Playgroud)

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

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

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


Dan*_*ler 6

您只需使用以下命令即可将对象转换为数组:

您可以将对象值转换为数组:

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

let valuesArray = Object.values(myObject);

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

您可以将对象键转换为数组:

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

let keysArray = Object.keys(myObject);

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

现在您可以执行普通的数组操作,包括 'map' 函数


小智 6

  const array = Object.keys(object)
  //returns all keys as array ["key", "key"]

  const array = Object.values(object)
  //returns all values as array ["value", "value"]

  const array = Object.entries(object)
  //returns all entries as array = [["key","value"], ["key","value"]]
Run Code Online (Sandbox Code Playgroud)

对结果使用 Map。

  array.map()
Run Code Online (Sandbox Code Playgroud)


whi*_*yit 5

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


Tha*_*you 5

编辑:使用较新的 JavaScript 功能的规范方式是 -

const identity = x =>
  x

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

哪里o是某个对象,f是您的映射函数。或者我们可以说,给定一个函数 froma -> b和一个值为 typea的对象,生成一个值为 type 的对象b。作为伪类型签名 -

// omap : (a -> b, { a }) -> { b }
Run Code Online (Sandbox Code Playgroud)

最初的答案是为了展示一个强大的组合器,mapReduce它使我们能够以不同的方式思考我们的转换

  1. m映射函数——让你有机会在之前转换传入的元素......
  2. rreduce函数——这个函数将累加器与映射元素的结果结合起来

直观地,mapReduce创建一个我们可以直接插入的新减速器Array.prototype.reduce。但更重要的是,我们可以omap通过使用对象 monoidObject.assign和来简单地实现我们的对象函子实现{}

const identity = x =>
  x

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

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

k => ({ [k]: f (o[k]) })
Run Code Online (Sandbox Code Playgroud)

也就是说,给定一个已知对象o和一些 key 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
    )
Run Code Online (Sandbox Code Playgroud)

一切都一样,但omap现在可以在更高级别定义。当然,新的Object.entries让这看起来很傻,但练习对学习者来说仍然很重要。

你不会看到mapReduce这里的全部潜力,但我分享这个答案,因为看看它可以应用于多少地方很有趣。如果您对它的派生方式以及它可能有用的其他方式感兴趣,请参阅此答案

  • 让我们宁愿使用`Map` 和`Map.entries`,好吧:D。我厌倦了滥用普通对象作为地图数据类型。 (4认同)
  • 我同意应该在可能的地方/情况下使用 `Map`,但它并不能完全取代在普通 JS 对象上使用这些过程的需要:D (2认同)