ฟังก์ชั่นแผนที่สำหรับวัตถุ (แทนอาร์เรย์)


1065

ฉันมีวัตถุ:

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ฟังก์ชั่นดังกล่าวสำหรับวัตถุหรือไม่ (ฉันต้องการสิ่งนี้สำหรับโหนด 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 ลำดับของObject.keysการขึ้นอยู่กับการนำไปใช้ ดูstackoverflow.com/a/30919039/1529630
Oriol

4
@Oriol คุณไม่ควรพึ่งพาลำดับของคีย์ในวัตถุต่อไปดังนั้นมันจึงไม่เกี่ยวข้องกับคำถาม - เนื่องจากเขาไม่เคยระบุว่าคำสั่งนั้นสำคัญกับเขา และถ้าคำสั่งสำคัญกับเขาเขาก็ไม่ควรใช้วัตถุเลย
BT

คำตอบ:


1541

ไม่มีวัตถุmapในท้องถิ่นObjectแต่มีวิธีการนี้:

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
@ อำพัน.reduceตัวอย่างของคุณก็โอเค แต่ IMHO มันยังคงสะดวกในการ.mapใช้อ็อบเจกต์ Objects เนื่องจากการ.reduceโทรกลับของคุณไม่เพียง แต่จะต้องตรงกับวิธีการใช้.reduceงานmyObjectเท่านั้น โดยเฉพาะอย่างยิ่งในช่วงหลังทำให้เป็นไปไม่ได้ที่จะส่งการอ้างอิงฟังก์ชันในการเรียกกลับซึ่งต้องใช้ฟังก์ชันที่ไม่ระบุชื่อแทน
Alnitak

8
TBH ฉันอยากได้คำตอบที่ค่อนข้าง upvoted ว่ามีเพียง (หรือ แต่เดิม) ให้โซลูชั่นที่สองที่นำเสนอในคำตอบนี้ คนแรกใช้งานได้และไม่จำเป็นต้องผิด แต่อย่างที่คนอื่นพูดผมไม่ชอบที่จะเห็นแผนที่ที่ใช้วิธีนั้น เมื่อฉันเห็นแผนที่จิตใจของฉันกำลังคิดโดยอัตโนมัติ "โครงสร้างข้อมูลที่เปลี่ยนแปลงไม่ได้"
mjohnsonengr

308

ซับหนึ่งที่มีการกำหนดตัวแปรทันทีใน JS ธรรมดา ( ES6 / ES2015 ) เป็นอย่างไร

การใช้ตัวดำเนินการสเปรดและไวยากรณ์ชื่อคีย์ที่คำนวณ :

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

jsbin

อีกรุ่นใช้ลด:

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

ES2019แนะนำObject.fromEntries()ซึ่งช่วยลดความยุ่งยากนี้มากยิ่งขึ้น:

let newObj = Object.fromEntries(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

อย่างไรก็ตามโปรดทำฉันชอบและหลีกเลี่ยงการรับมรดก :-)


1
สวยงาม แต่ฉันถูกจับโดยความจริงที่Object.keysไม่ได้ระบุคุณสมบัติที่สืบทอดมา ฉันแนะนำให้คุณเพิ่มคำเตือน
David Braun

Object.entries({a: 1, b: 2, c: 3})เพื่อตัดคำตอบของคุณเพียงแค่ใช้

1
คุณมีความผิดพลาดบางอย่าง (คุณอ้างถึง(o, f)ข้อโต้แย้ง แต่ใช้objในร่างกาย
kzahel

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})หาอาเรย์ได้เลย

11
เหตุใดการใช้อาร์เรย์จึงมีประโยชน์ ข้อกำหนดสำหรับวัตถุที่แมป นอกจากนี้ยังไม่มีการโทร HTTP โดยนัยในการใช้ Lodash ซึ่งเป็นห้องสมุดยอดนิยมที่สร้างขึ้นสำหรับสิ่งนี้แม้ว่าจะมีบางสิ่งที่ ES6 ได้จำกัดความจำเป็นของการใช้ไลบรารีฟังก์ชันอรรถประโยชน์ในระดับหนึ่งเมื่อเร็ว ๆ นี้
corse32

2
ฉันเรียก HTTP พิเศษเพื่อดึง Lodash ฉันเดา หากนี่เป็นกรณีการใช้งานของคุณเพียงอย่างเดียวก็ถือว่าเกินความจริง อย่างไรก็ตามมันสมเหตุสมผลที่จะมีคำตอบสำหรับ Lodash เช่นนี้เป็นห้องสมุดที่แพร่หลาย
igorsantos07

2
ไม่ต้องพูดถึงคุณควรใช้จริง ๆ_.map()เพื่อให้คุณได้รับคีย์เป็นอาร์กิวเมนต์ที่สอง - ตามที่ OP ต้องการ
igorsantos07

_.mapValues()จะให้กุญแจเป็นอาร์กิวเมนต์ที่ 2 และ_.map()จะไม่ทำงานที่นี่เนื่องจากจะส่งคืนเฉพาะอาร์เรย์ไม่ใช่วัตถุ:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
MeltedPenguin

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 ขอบคุณ IMHO ใน ES5 ไม่มีเหตุผลใด ๆ ที่จะไม่แก้ไขObject.prototypeความเสี่ยง (เชิงทฤษฎี) จากการชนกับวิธีอื่น ๆ FWIW ฉันไม่สามารถเข้าใจว่าคำตอบอื่น ๆ ได้รับคะแนนเสียงมากเท่าที่มีอยู่มันผิดทั้งหมด
Alnitak

7
@JLRishe คุณเป็นคนแก่ ... ไปกับเวลาและแก้ไขต้นแบบนั้น!
Nick Manning

12
@NickManning คุณวิปเปอร์สแลปหนุ่มและพฤติกรรมที่ไม่รับผิดชอบของคุณ ลงสนามหญ้าของฉัน!
JLRishe

3
นี้แล้วo = {map: true, image: false}ไม่ทำงาน: คุณกำลังเสี่ยงต่อการเขียนo.map(fn)แทนmap(o,fn)
อิสระ

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

7
เพราะมันควรเป็นวิธีใน Object :)
Alexander Mills

1
ตราบใดที่เราไม่ยุ่งกับ Object.prototype เราก็ควรจะยอมรับ
Alexander Mills

1
หากแผนที่เป็นเพียงสิ่งที่คลุมเครือแบบสุ่มแน่นอน แต่.mapโดยทั่วไปจะเข้าใจว่าเป็นอินเทอร์เฟซของ Functor และไม่มีคำจำกัดความเดียวของ Functor สำหรับ "Object" เพราะอะไรก็ตามในจาวาสคริปต์ก็สามารถเป็น Object ได้รวมถึงสิ่งที่มีอินเตอร์เฟส / ตรรกะ.
Dtipson

1
ในความเห็นของฉันอาร์กิวเมนต์แรกของการโทรกลับควรเป็นองค์ประกอบซ้ำแล้วซ้ำอีกไม่สำคัญ ตัวอย่างเช่นฉันคิดว่ามันควรให้วัตถุเดียวกันกับฉันเพราะมันเกิดขึ้นกับแผนที่ทั่วไป: Object.map ({prop: 1}, el => el) แต่มันจะส่งคืนกุญแจที่มีค่าชื่อคีย์เดียวกัน ...
Gleb Dolzikov

17

ฉันมาที่นี่เพื่อค้นหาและตอบรับการแมปวัตถุกับอาเรย์และรับหน้านี้เป็นผลลัพธ์ ในกรณีที่คุณมาที่นี่เพื่อค้นหาคำตอบเดียวกับฉันนี่คือวิธีที่คุณสามารถแมปและคัดค้านอาร์เรย์

คุณสามารถใช้แผนที่เพื่อส่งกลับอาร์เรย์ใหม่จากวัตถุดังนี้:

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

โปรดทราบว่าฟังก์ชั่นทั้งหมดจะถูกกำหนดในรูปแบบ curried

// 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's ค่าตอบแทนจะถูกละเว้นเพราะดำเนินการกลายพันธุ์ของ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 ความคิดเห็นของคุณมีจุดประสงค์อะไร? ถ้าคุณชอบ micro-optimization คุณไม่ควรใช้Array.prototype.reduceเช่นกัน ทั้งหมดที่ฉันต้องการแสดงที่นี่คือคำตอบที่ได้รับการยอมรับอย่างใด "ละเมิด" Array.prototype.reduceและวิธีการทำแผนที่การทำงานอย่างหมดจดสามารถนำมาใช้ หากคุณไม่สนใจการเขียนโปรแกรมที่ใช้งานได้เพียงเพิกเฉยคำตอบของฉัน

2
"การลดหมายถึงการเปลี่ยนโครงสร้างของคอมโพสิตชนิด" ฉันไม่คิดว่ามันเป็นเรื่องจริง Array.reduce ที่สร้างอาเรย์ใหม่ที่มีความยาวเท่ากันหรือแม้แต่ค่าเดียวกันที่แน่นอนนั้นเป็นสิ่งที่ถูกต้องสมบูรณ์ ลดสามารถใช้เพื่อสร้างฟังก์ชั่นของแผนที่และ / หรือตัวกรอง (หรือทั้งสองอย่างเช่นในตัวแปลงสัญญาณที่ซับซ้อนซึ่งเป็นพื้นฐานลด) ใน FP มันเป็นประเภทของการพับและพับที่ลงท้ายด้วยประเภทเดียวกันยังคงเป็นเท่า
Dtipson

2
@Tipson คุณพูดถูก A foldนั้นสามัญกว่าmap(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 ให้เป็น Array ( [[<key>,<value>], ...]) แบบซ้อนที่คุณสามารถแมปได้ Object.fromEntriesแปลง Array กลับไปเป็นวัตถุ

สิ่งที่ยอดเยี่ยมเกี่ยวกับรูปแบบนี้คือตอนนี้คุณสามารถนำคีย์วัตถุมาพิจารณาได้อย่างง่ายดายขณะทำแผนที่

เอกสาร

สนับสนุนเบราว์เซอร์

Object.fromEntriesปัจจุบันรองรับเบราว์เซอร์ / เครื่องมือเหล่านี้เท่านั้นอย่างไรก็ตามยังมีโพลีฟิลส์ (เช่น@ babel / polyfill )


2
โดยทั่วไปเป็นตรงข้ามของObject.fromEntries แปลงวัตถุเป็นรายการคู่คีย์ - ค่า แปลงรายการคู่คีย์ - ค่าให้กับวัตถุ Object.entriesObject.entriesObject.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], {})วงเล็บเหลี่ยมแทนที่จะเป็นวงเล็บ
Alexander Mills

@AlexanderMills รหัสของฉันถูกต้อง อ่านเพิ่มเติมเกี่ยวกับเครื่องหมายจุลภาคในวงเล็บ: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yukulélé

เข้าใจแล้วคุณอาจต้องการอธิบายว่าครั้งต่อไป แต่ฉันเดาว่านี่เป็นความคิดเห็น
Alexander Mills

1
โดยพื้นฐานแล้วรหัสของฉันเทียบเท่ากับ:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé

1
คำตอบที่ดี Object.entries().reduceเป็นทางออกที่ดีที่สุดที่ฉันคิดว่ากับ ES6 + จะได้รับ upvotes มากขึ้นถ้าใช้returnคำสั่งใน reducer มากกว่าimplicit returnและcommaoperator - ซึ่งเรียบร้อย แต่ยากที่จะอ่าน IMHO
Drenai

7

เพื่อประสิทธิภาพสูงสุด

หากวัตถุของคุณไม่เปลี่ยนแปลงบ่อยนัก แต่จำเป็นต้องทำซ้ำบ่อยๆฉันขอแนะนำให้ใช้แผนที่ดั้งเดิมเป็นแคช

// 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 และเบต้า Opera ดังนั้นมันจึงเป็นคุณสมบัติพิสูจน์อนาคต มาจาก ES7 ดังนั้น polyfill https://github.com/es-shims/Object.entriesสำหรับ IE ที่ใช้งานไม่ได้


วิธีการเกี่ยวกับการทำลายล้าง ?
КонстантинВан

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

1
@ Ronald ฉันเข้าใจผิดดูคำตอบที่อัปเดตของฉันขอบคุณสำหรับการไม่ downvoting แต่ทำให้ตระหนักถึงความผิดพลาดของฉันอย่างแท้จริงขอบคุณสำหรับการปรับปรุงชุมชน
Haritsinh Gohil

@boughlan ใช่มันเป็นสิ่งที่ฉันได้เขียนไว้ในความคิดเห็นด้วยเช่นกันมันกำลังสองจำนวนคุณต้องการอะไรแล้ว?
Haritsinh Gohil

1
@HaritsinhGohil ฉันเอาไปหมายความว่าเขากำลังวิพากษ์วิจารณ์คุณในการให้โซลูชัน jQuery มันเป็นความจริงที่ว่ามีบางแรงผลักดันในวันนี้ที่จะย้ายออกจาก jQuery และแน่นอนว่ามีโหนด แต่ถ้า OP ขอเป็นพิเศษสำหรับ JS หรือโหนดบริสุทธิ์ฉันคิดว่ามันเป็นเรื่องดีที่จะสมมติว่ามีคนทำงานในสภาพแวดล้อมเว็บและ jQuery เกือบจะแน่นอนที่จะมีอยู่
39; 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;
});

ปัญหาด้าน perfomance: typeof callback === 'function'ซ้ำซ้อนอย่างแน่นอน 1) mapไม่มีความหมายหากไม่มีการติดต่อกลับ 2) หากcallbackไม่ใช่ฟังก์ชั่นmapการใช้งานของคุณจะทำงานโดยไม่มีความหมายต่อลูป นอกจากนี้ยังObject.prototype.hasOwnProperty.call( obj, key )เป็นบิตมากเกินไป
ankhzet

3

ฉันคิดว่านี่เป็นรายการแรกในการค้นหาโดย Google ที่พยายามเรียนรู้การทำเช่นนี้และคิดว่าฉันจะแบ่งปันให้คนอื่น ๆ ค้นพบสิ่งนี้เมื่อไม่นานมานี้ซึ่งเป็นวิธีแก้ปัญหาที่ฉันค้นพบ

ฉันคิดว่ามันน่าสนใจที่จะแบ่งปันเพราะไม่เปลี่ยนรูปใช้สถานการณ์ EXACT ของ 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 อธิบายถึงการดำเนินการที่ไม่อนุญาตให้พวกเขาใช้โซ่อย่างมีประสิทธิภาพของวิธีการรวบรวมลำดับที่สูงขึ้นทั้งหมด (เช่นแผนที่และตัวกรอง) โดยไม่สร้างคอลเลกชันระดับกลาง") และข้อมูลอื่น ๆ โครงสร้างอาจทำงานค่อนข้างมีประสิทธิภาพ

แน่นอนว่าทุกคนที่ใช้วิธีนี้จะต้อง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ทำหน้าที่เป็นแผนที่ของคุณ หรือเราอาจจะพูดให้ฟังก์ชั่นจากa -> bและวัตถุที่มีคุณค่าของการพิมพ์ที่ผลิตวัตถุที่มีค่าประเภทa bในฐานะที่เป็นลายเซ็นประเภทหลอก -

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

คำตอบดั้งเดิมถูกเขียนขึ้นเพื่อแสดงให้เห็นถึง combinator ที่มีประสิทธิภาพmapReduceซึ่งช่วยให้เราคิดว่าการเปลี่ยนแปลงของเราในวิธีที่แตกต่าง

  1. mการทำแผนที่ฟังก์ชั่น - ช่วยให้คุณมีโอกาสที่จะเปลี่ยนองค์ประกอบที่เข้ามาก่อน ...
  2. rการลดฟังก์ชัน - ฟังก์ชั่นนี้รวมสะสมที่มีผลมาจากองค์ประกอบแมปที่

สังหรณ์ใจสร้างลดใหม่ที่เราสามารถเชื่อมต่อโดยตรงmapReduce Array.prototype.reduceแต่ที่สำคัญกว่าที่เราสามารถใช้การดำเนินการ functor วัตถุของเราomapชัดถ้อยชัดคำโดยใช้หนังสือวัตถุและ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เป็นผลมาจากการเรียกเกี่ยวกับคุณค่าที่สำคัญของfo[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
ให้เราใช้แทนMapและMap.entriesตกลง: D ฉันเบื่อการใช้วัตถุธรรมดาในทางที่ผิดประเภทข้อมูลแผนที่

2
ฉันเห็นด้วยMapควรใช้ที่ / เมื่อเป็นไปได้ แต่มันไม่ได้แทนที่ความจำเป็นในการใช้ขั้นตอนเหล่านี้ทั้งหมดในวัตถุ JS ธรรมดาสรรพสิ่ง: D
ขอบคุณ

2

จากคำตอบ @Alamplamps นี่คือฟังก์ชั่นยูทิลิตี้ (ตามความเห็นมันดูน่าเกลียด)

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 ซึ่งจะช่วยให้คุณใช้ฟังก์ชั่นแผนที่โดยทำหน้าที่เกี่ยวกับค่าของแต่ละคีย์ที่เกี่ยวข้อง (จริง ๆ แล้วฉันกลับอาร์เรย์โดยไม่ตั้งใจ ตราบใดที่ยังไม่มีการคืนค่าในความรู้สึกปกติจะไม่มีการสร้างอาเรย์ด้วยออบเจ็กต์แบบเดิมเหมือนเดิมและแก้ไขตามที่โปรแกรมไว้

โปรแกรมนี้ใช้วัตถุที่เรียกว่ารูปภาพและนำค่าของคีย์และผนวกแท็ก 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') 
};

โครงสร้างดั้งเดิมของวัตถุนั้นยังคงอยู่เพื่อให้สามารถเข้าถึงคุณสมบัติปกติได้ตราบใดที่ไม่มีการส่งคืน อย่าให้มันคืนอาร์เรย์เหมือนปกติและทุกอย่างจะดี เป้าหมายคือการปรับเปลี่ยนค่าดั้งเดิม (ภาพ [กุญแจ]) ตามความต้องการและไม่ใช่สิ่งอื่นใด เท่าที่ฉันรู้เพื่อป้องกันการส่งออกอาร์เรย์มีได้มีการจัดรูปใหม่ของภาพ [สำคัญ] และไม่มีการร้องขอโดยปริยายหรือชัดเจนที่จะกลับอาร์เรย์ (การกำหนดตัวแปรทำเช่นนี้

แก้ไข:

จะกล่าวถึงวิธีการอื่นของเขาเกี่ยวกับการสร้างวัตถุใหม่เพื่อหลีกเลี่ยงการแก้ไขวัตถุดั้งเดิม (และการมอบหมายใหม่ยังคงมีความจำเป็นเพื่อหลีกเลี่ยงการสร้างอาร์เรย์เป็นผลลัพธ์โดยไม่ได้ตั้งใจ) ฟังก์ชั่นเหล่านี้ใช้ไวยากรณ์ลูกศรและหากคุณต้องการสร้างวัตถุใหม่สำหรับใช้ในอนาคต

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

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

วิธีการทำงานของฟังก์ชั่นเหล่านี้เป็นเช่นนั้น:

mapFn รับฟังก์ชั่นที่จะเพิ่มในภายหลัง (ในกรณีนี้ (ค่า) => ค่า) และเพียงคืนสิ่งที่เก็บไว้ที่นั่นเป็นค่าสำหรับคีย์นั้น (หรือคูณสองถ้าคุณเปลี่ยนค่าส่งคืนเหมือนที่เขาทำ) ใน mapFn ( obj) [สำคัญ]

จากนั้นกำหนดค่าดั้งเดิมใหม่ที่เกี่ยวข้องกับคีย์ในผลลัพธ์ [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

ฟังก์ชั่นแรกตอบคำถาม มันสร้างแผนที่

ฟังก์ชั่นที่สองไม่ได้สร้างแผนที่ แต่มัน loops 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)
Matt Browne

@ MattBrowne ฉันได้เพิ่มวิธีที่สองที่สร้างสิ่งที่เป็นแผนที่ ช้า แต่ทำตามที่ OP ร้องขอ วิธีแรกเหมาะอย่างยิ่งในหลาย ๆ สถานการณ์ซึ่งเป็นสาเหตุที่ฉันใช้ฟังก์ชันลูปและคุณสมบัติ
Victor Stoddard

1

หากคุณสนใจในการ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 }

3
ฉันคิดว่าเพราะคุณเพียงแค่ให้ลิงค์เป็นคำตอบที่ขมวดคิ้ว
Chris Wright

ถ้ามีประโยชน์สำหรับทุกคน: npm install @mattisg/object.map.
MattiSG

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 }

จะมีวิธีการใช้แผนที่myObject.map,

คล้ายกับ Array.prototype.map ที่จะใช้ดังนี้:

newObject = myObject.map (ฟังก์ชั่น (ค่า, ฉลาก) {
    คืนค่า * ค่า;
});
// newObject ตอนนี้ {'a': 1, 'b': 4, 'c': 9}

IMHOที่ดีที่สุด (วัดในแง่ที่ " ใกล้กับสิ่งที่จะถาม " + "ไม่มี 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's map)



1

กำหนดฟังก์ชั่น mapEntries

mapEntries รับฟังก์ชั่นการโทรกลับที่เรียกว่าในแต่ละรายการในวัตถุที่มีค่าพารามิเตอร์คีย์และวัตถุ มันควรจะคืนค่าใหม่

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


0

ฉันจัดการสตริงเท่านั้นเพื่อลดข้อยกเว้น:

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

0

Object Mapper ใน TypeScript

ฉันชอบตัวอย่างที่ใช้Object.fromEntriesเช่นนี้แต่ก็ยังไม่ได้ใช้งานง่าย คำตอบที่ใช้Object.keysแล้วค้นหาkeyจะทำหลาย ๆ การค้นหาที่อาจไม่จำเป็น

ฉันหวังว่าจะมีObject.mapฟังก์ชั่น แต่เราสามารถสร้างของเราเองและเรียกมันว่าobjectMapมีความสามารถในการปรับเปลี่ยนทั้งสองkeyและvalue:

การใช้งาน (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}]}]}

ฟังก์ชั่นนี้จะวนซ้ำผ่านวัตถุและอาร์เรย์ของวัตถุ สามารถลบแอตทริบิวต์ได้หากส่งคืนไม่ได้กำหนด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.