функция map для объектов (вместо массивов)

у меня есть объект:

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 такой для объектов? (Я хочу это для Node.JS, поэтому меня не волнуют проблемы с кросс-браузером.)

24 ответов


нет map до Object "объект", но как насчет этого:

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

console.log(myObject);

// => { 'a': 2, 'b': 4, 'c': 6 }

но вы можете легко перебирать объект с помощью for ... in:

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

обновление

многие люди отмечают, что предыдущие методы не возвращают новый объект, а на сам объект. В этом отношении я хотел добавить другое решение, которое возвращает новый объект и оставляет исходный объект как он есть:

// 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': 1, 'b': 4, 'c': 9 }

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

Array.prototype.reduce уменьшает массив до одного значения путем некоторого слияния предыдущего значения с текущим. Цепочка инициализируется пустым объектом {}. На каждой итерации новый ключ myObject добавляется с его квадратом в качестве значения.


как насчет одного лайнера с немедленным назначением переменных в plain 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})));


унаследованные свойства и цепочка прототипов:

в какой-то редкой ситуации вам может понадобиться карта класса объект, который содержит свойства наследуемого объекта на свое прототип-цепь. В таких случаях Object.keys() не будет работать, потому что Object.keys() не работает унаследовала свойства. Если вам нужно map унаследовала свойства, вы должны использовать 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

однако, пожалуйста, сделайте мне одолжение и избежать наследование. :-)


нет собственных методов, но lodash#mapValues сделает работу блестяще

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

это довольно легко написать:

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 }

NB: эта версия также позволяет (необязательно) установить this контекст для обратного вызова, как и Array метод.

редактировать - изменено, чтобы удалить использование Object.prototype, чтобы убедиться, что он не конфликтует с любым существующим свойством с именем map на объект.


можно использовать 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 проверка.


это прямая 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 = {};

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

    return ret;
};

это очень раздражает, чтобы реализовать это самостоятельно все время ;)

если вы хотите что-то немного более сложное, что не мешает классу объектов, попробуйте следующее:

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.map = ... // fairly safe
Object.prototype.map ... // not ok

Я пришел сюда, чтобы найти и ответить за отображение объекта в массив и получил эту страницу в результате. Если вы пришли сюда в поисках того же ответа, что и я, вот как вы можете сопоставить и возразить массиву.

вы можете использовать map для возврата нового массива из объекта следующим образом:

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

принятый ответ имеет два недостатка:

  • он злоупотребляет 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));
  • в строке o is переназначенный. Так как 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);

Addendum-почему объекты по умолчанию не итерируются?

ES2015 указал итератор и итерационные протоколы. Но объекты до сих пор не Iterable и так нет. причина заключается в смешивании данных и уровня программы.


для достижения максимальной производительности.

Если ваш объект не меняется часто, но должен часто повторяться, я предлагаю использовать собственную карту в качестве кэша.

// 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);
});

минимальная версия (es6):

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

этот ответ был переписан, чтобы продемонстрировать мощный комбинатора, mapReduce что позволяет нам думать о нашей трансформации по-другому

  1. m, the картография функция-дает вам возможность преобразовать входящий элемент перед...
  2. r, the сокращение функция-эта функция объединяет аккумулятор с результатом отображаемого элемента

интуитивно, mapReduce создает новый редуктор, который мы можем подключить непосредственно к Array.prototype.reduce. Но что более важно, мы можем реализовать нашу реализацию функтора объекта omap просто используя моноид объекта,Object.assign и {}.

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

видеть только часть программы, мы на самом деле пришлось писать самой реализации –

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

что говорит, учитывая известный объект o и какой ключ k, построить объект и свойство которого k является результатом вызова f о значении ключа,o [k].

мы получаем проблеск mapReduceсеквенирующий потенциал, если мы сначала абстрактные oreduce

const oreduce = (o, f = first, acc = {}) =>
  Object.keys (o)
    .reduce ( mapReduce ( k => [ k, o[k] ]
                        , f
                        )
            , acc
            )

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

все работает одинаково, но omap теперь можно определить на более высоком уровне. Конечно, новый Object.entries это выглядит глупо, но упражнение по-прежнему важно для ученика.

вы не увидите полный потенциал mapReduce здесь, но я разделяю этот ответ, потому что это интересно посмотреть, сколько мест он может быть применен. Если вас интересует, как он получен и другими способами, это может быть полезно, см. ответ.


на 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;
});

на основе ответа @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}

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 }

я наткнулся на это в качестве первого элемента в поиске Google, пытаясь научиться делать это, и думал, что я поделюсь для других folsk, найдя это недавно решение, которое я нашел, которое использует неизменяемый пакет npm.

Я думаю, что его интересно поделиться, потому что immutable использует точную ситуацию OP в своей собственной документации - следующее Не мой собственный код, но вытащил из текущей неизменяемой документации 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/


Если вы заинтересованы в mapping не только значения, но и ключи, я написал 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 }

Эй написал небольшую функцию 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];
                }
              }
            }
         }
      }

мне нужна была версия, которая позволяла изменять ключи (на основе ответов @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"}
}

Edit: небольшое изменение для передачи в начальный объект {}. Позволяет ему быть [] (если ключи являются целыми числами)


Я специально хотел использовать ту же функцию, что и для массивов для одного объекта, и хотел сохранить ее простой. Это сработало для меня:

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

вы можете использовать простой цикл for-in через упорядоченные пары. Я использовал hasOwnProperty() потому что вы создали три свойства (ценности) для вашего объекта. Первый метод не создает карту. Вместо этого, он просто применяет функцию к одному элементу, который может значительно ускорить выполнение во многих ситуациях.

второй метод создает карту таким же образом, как и первый, но, вероятно, довольно медленнее, чем некоторые другие ответы здесь.

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

//Doesn't create a map, just applies the function to a specific element
function getValue(key) {
  for (var k in myObject) {
    if (myObject.hasOwnProperty(key)) {
      var value = myObject[key]
      return value * value; //stops iteration
    }
  }
}

//creates a map (answers question, but above function is 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
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>

можно использовать map метод с массивами, но если вы хотите использовать его на Object затем вы можете использовать его с твист, как показано ниже:

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}

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

const result = _.transform(orig, (r, v, k) => r[k.trim()] = v * 2);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

использовать new _.преобразования() для преобразования объекта.


Если кто-то искал простое решение, которое сопоставляет объект с новым объектом или массивом:

// Maps an object to a new object by applying a function to each key+value pair.
// Takes the object to map and a function from (key, value) to mapped value.
const mapObject = (obj, fn) => {
    const newObj = {};
    Object.keys(obj).forEach(k => { newObj[k] = fn(k, obj[k]); });
    return newObj;
};

// Maps an object to a new array by applying a function to each key+value pair.
// Takes the object to map and a function from (key, value) to mapped value.
const mapObjectToArray = (obj, fn) => (
    Object.keys(obj).map(k => fn(k, obj[k]))
);

Это может не работать для всех объектов или всех картографических функций, но это работает для простых мелких объектов и простых картографических функций, которые мне нужны.


другой подход-использовать пользовательскую функцию JSON stringify, которая также может работать с глубокими объектами. Это может быть полезно, если вы собираетесь опубликовать его на сервере в любом случае как 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))