функция 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]})));
другая версия с помощью reduce:
let newObj = Object.keys(obj).reduce((p, c) => ({...p, [c]: obj[c] * obj[c]}), {});
первый пример функция:
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);
если вы хотите отобразить вложенный объект рекурсивно на функциональное стиль, это можно сделать так:
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.
,{})
или более повелительно, вот так:
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;
};
С 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'
}
однако, пожалуйста, сделайте мне одолжение и избежать наследование. :-)
нет собственных методов, но 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);
});
этот ответ был переписан, чтобы продемонстрировать мощный комбинатора, mapReduce
что позволяет нам думать о нашей трансформации по-другому
-
m
, the картография функция-дает вам возможность преобразовать входящий элемент перед... -
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
и, возможно, захотите прочитать документы:
Если вы заинтересованы в map
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 }
Эй написал небольшую функцию 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))