简体   繁体   中英

A more elegant way to write this .map call?

I have some code that essentially takes an array of objects and just adds an additional key to each item. I want to be able to express this as tersely as possible as an experiment.

let fruits = [
   {"type" : "orange"},
   {"type" : "apple"},
   {"type" : "banana"}
];
console.log(fruits.map((fruit) => {
         fruit.price = "$1.00";
         return fruit;
}));

Currently, this works, but it's certainly no one liner and the return statement is still in there, and I feel like there's a way to get rid of it given the fat arrow syntax.

One approach would be to use Object.assign to extend the object and also return the resulting newly-created object:

console.log(fruits.map(fruit => Object.assign(fruit, { price: "1.00" })));

Babel REPL Example

This removes the need for the return keyword, but it's hardly the biggest space-saver. It is also equivalent to what you already have (in that the original fruit object is modified. As joews points out below, if you wanted to leave the original array in-tact you can use an empty target object like so:

Object.assign({}, fruit, { price: "1.00"});

This will ensure that your original array is unmodified (which may or may not be what you want).

Finally, combining this with the spread operator gives us:

console.log(fruits.map(fruit => ({...fruit, price: "1.00" })));

You can also use .forEach() instead of .map() to directly modify fruits if you don't need the original version of fruits

fruits.forEach((fruit) => fruit.price = "$1.00");

http://www.es6fiddle.net/igwdk0gk/

Could do something like this, not recommended for readibility but technically one line.

fruits.map(fruit => (fruit.price = "$1.00") && fruit);

As others have mentioned this method just adds a property to the object and does not copy it. A simple way to keep this as a one liner, use a map and actually create a copy would be:

fruits.map(fruit => Object.assign({price: "$1.00"}, fruit));

Object.assign() will assign all the properties of fruit to the object { price: "$1.00" } and return it.

Live example:

 "use strict"; let log = function() { output.textContent += [].join.call(arguments, ' ') + '\\n\\n'; }; log('# MAP (OR FOREACH) WITHOUT ASSIGN'); let fruits = [ {"type" : "orange"}, {"type" : "apple"}, {"type" : "banana"} ]; let newfruits = fruits.map(fruit => (fruit.price = "$1.00") && fruit); log('fruits', JSON.stringify(fruits)); log('newfruits', JSON.stringify(newfruits)); log('^-- Both are modified since newfruits its a new array with the same objects'); log('# MAP WITH ASSIGN'); fruits = [ {"type" : "orange"}, {"type" : "apple"}, {"type" : "banana"} ]; newfruits = fruits.map(fruit => Object.assign({price: "$1.00"}, fruit)); log('fruits', JSON.stringify(fruits)); log('newfruits', JSON.stringify(newfruits)); log('^-- Only newfruits is modified since its a new array with new objects');
 pre { word-wrap: break-word; }
 <pre id="output"></pre>

There are many ways to do this:

Side effects with the comma operator

If you want to do it inline you can use the comma operator (though it's a little obscure):

fruits.map((fruit) => (fruit.price = "$1.00", fruit))

We could also use && since assignment returns the assigned value and "$1.00" is truthy but the comma operator is more general, since we can also set false or 0 and have everything continue to work.

Higher-ordered functions

It's probably better to make a helper function, however:

// We're currying manually here, but you could also make the signature
// setter(name, value) and use your function of choice to curry when you need to.
function setter(name) {
  return (value) => (obj) => {
    obj[name] = value;
    return obj;
  }
}

Then you can use:

fruits.map(setter("price")("$1.00"))

Embrace mutability

As @Suppen points out in their comment, because normal JavaScript objects are mutable you can also avoid the map and use forEach instead:

fruits.forEach(fruit => fruit.price = "$1.00");
// Each element in fruits has been modified in-place.

A mapping function should almost always be pure. If you are only going to modify the objects, a simple loop will do better ( for (let fruit of fruits) fruit.price = …; console.log(fruits); ).

So when you're returning a new object, a one-liner will be easy:

console.log(fruits.map(({type}) => ({type, price:"$1.00"})));

If you've got many properties, or properties you don't know, then Object.assign({}, …) is your friend (as in @joews' comment to @RGraham's answer).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM