จากอาร์เรย์ของวัตถุแยกค่าของคุณสมบัติเป็นอาร์เรย์


1016

ฉันมีอาร์เรย์วัตถุ JavaScript ที่มีโครงสร้างต่อไปนี้:

objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];

ฉันต้องการที่จะดึงข้อมูลจากแต่ละวัตถุและได้รับอาร์เรย์มีค่าสำหรับข้อมูลตัวอย่างเช่นจะให้อาร์เรย์foo[ 1, 3, 5 ]

ฉันสามารถทำได้ด้วยวิธีการที่ไม่สำคัญนี้:

function getFields(input, field) {
    var output = [];
    for (var i=0; i < input.length ; ++i)
        output.push(input[i][field]);
    return output;
}

var result = getFields(objArray, "foo"); // returns [ 1, 3, 5 ]

มีวิธีที่สง่างามหรือเป็นสำนวนในการทำเช่นนี้เพื่อให้ฟังก์ชั่นยูทิลิตี้ที่กำหนดเองจะไม่จำเป็น?


หมายเหตุเกี่ยวกับคำแนะนำที่ซ้ำกันซึ่งครอบคลุมถึงวิธีการแปลงวัตถุเดียวเป็นอาร์เรย์


4
ไลบรารี Prototype ได้เพิ่มฟังก์ชั่น "ดึง" ให้กับต้นแบบ Array (ฉันคิดว่า) ดังนั้นคุณสามารถเขียนvar foos = objArray.pluck("foo");ได้
Pointy

3
@hyde - jsperf.com/map-vs-native-for-loop - โปรดดูที่นี่หวังว่าการวนซ้ำตัวเองจะเป็นทางออกที่ดี
N20084753

1
@ N20084753 สำหรับการทดสอบที่เป็นธรรมคุณควรเปรียบเทียบArray.prototype.mapฟังก์ชันดั้งเดิมที่มีอยู่
Alnitak

@Alnitak - ใช่แล้ว แต่ฉันไม่เข้าใจว่า Native Array.prototype.map นั้นได้รับการปรับให้เหมาะสมที่สุดอย่างไรสำหรับ Native สำหรับ Loop วิธีใดก็ตามที่เราต้องสำรวจอาร์เรย์ที่สมบูรณ์
N20084753

3
โอพีฉันชอบวิธีการของคุณกับคนอื่น ๆ ที่ได้รับการแนะนำ ไม่มีอะไรผิดปกติกับมัน

คำตอบ:


1334

นี่เป็นวิธีที่สั้นกว่าในการบรรลุเป้าหมาย:

let result = objArray.map(a => a.foo);

หรือ

let result = objArray.map(({ foo }) => foo)

Array.prototype.map()นอกจากนี้คุณยังสามารถตรวจสอบ


3
นี่ก็เหมือนกับความเห็นของคำตอบอื่นโดย totymedli แต่ไม่มีทางที่จะดีกว่า (ในความคิดของฉัน) ในทางที่ดีกว่าคำตอบอื่น ๆดังนั้น ... เปลี่ยนเป็นคำตอบที่ยอมรับได้
hyde

4
ฟังก์ชั่น @PauloRoberto Arrow นั้นรองรับทุกที่ยกเว้น IE
tgies

1
แน่นอนว่าได้รับอนุญาต แต่ IMHO ไม่มีสิ่งใดที่ทำให้คำตอบนี้ดีขึ้นอย่างมีเหตุผลยกเว้นว่าจะใช้ไวยากรณ์ที่ไม่สามารถใช้ได้ในเวลาที่คุณถามคำถามและไม่ได้รับการสนับสนุนแม้แต่เบราว์เซอร์บางตัว ฉันยังทราบด้วยว่าคำตอบนี้เป็นสำเนาความคิดเห็นโดยตรงที่ทำไว้กับคำตอบที่ได้รับการยอมรับมาเกือบปีก่อนที่คำตอบนี้จะโพสต์
Alnitak

26
@Alnitak การใช้งานฟังก์ชั่นที่ใหม่กว่าในมุมมองของฉันจะดีกว่าวัตถุ ตัวอย่างนี้เป็นเรื่องธรรมดามากดังนั้นฉันจึงไม่เชื่อว่านี่เป็นการลอกเลียนแบบ ไม่มีคุณค่าใด ๆ ในการรักษาคำตอบที่ล้าสมัยที่ตรึงไว้ด้านบน
Rob

6
ความคิดเห็นไม่ใช่คำตอบ imo ถ้าใครบางคนโพสต์ความคิดเห็นแทนคำตอบมันเป็นความผิดของตัวเองถ้ามีคนคัดลอกมันเป็นคำตอบ
Aequitas

618

ใช่ แต่ต้องอาศัยคุณสมบัติ ES5 ของ JavaScript ซึ่งหมายความว่าจะไม่ทำงานใน IE8 หรือเก่ากว่า

var result = objArray.map(function(a) {return a.foo;});

ในล่าม JS ที่ใช้งานร่วมกับ ES6 ได้คุณสามารถใช้ฟังก์ชั่นลูกศรเพื่อความกระชับได้:

var result = objArray.map(a => a.foo);

เอกสาร Array.prototype.map


5
วิธีนี้ดูเหมือนจะเรียบร้อยและเรียบง่าย แต่มันได้รับการปรับปรุงให้ดีที่สุดกว่าวิธีการวนซ้ำแบบธรรมดา
N20084753

ไม่ OP ต้องการวิธีการที่จะได้รับการใด ๆฟิลด์ไม่ hardcoded เพียงfoo?
Alnitak

2
ใน ES6 คุณสามารถทำได้var result = objArray.map((a) => (a.foo));
Black

28
var result = objArray.map(a => a.foo);
@Black

4
@Fizizhan แจ้งให้ทราบล่วงหน้าปี คำตอบนี้ถูกโพสต์เมื่อ 25 ตุลาคม ... 2013 คำตอบ "ยอมรับ" ถูกโพสต์เมื่อวันที่ 11 ตุลาคม 2017 หากมีสิ่งใดที่น่าทึ่งที่เครื่องหมายถูกไปที่อีกตัวหนึ่ง
Niet the Dark Absolute

52

ตรวจสอบฟังก์ชั่นของ Lodash_.pluck()หรือฟังก์ชั่นขีด_.pluck()ล่าง ทั้งสองทำสิ่งที่คุณต้องการในการเรียกใช้ฟังก์ชันเดียว!

var result = _.pluck(objArray, 'foo');

ปรับปรุง: _.pluck()ได้ถูกลบออก ณ Lodash v4.0.0ในความโปรดปรานของ_.map()ร่วมกับสิ่งที่คล้ายกับคำตอบของ Niet _.pluck()ยังคงมีอยู่ในขีดล่าง

อัปเดต 2:ตามที่มาร์คชี้ให้เห็นในความคิดเห็นบางแห่งระหว่าง Lodash v4 และ 4.3 จะมีการเพิ่มฟังก์ชั่นใหม่ที่ให้ฟังก์ชันการทำงานนี้อีกครั้ง _.property()เป็นฟังก์ชั่นชวเลขที่ส่งกลับฟังก์ชั่นสำหรับการรับค่าของคุณสมบัติในวัตถุ

นอกจากนี้ขณะนี้ช่วยให้สตริงที่จะผ่านในเป็นพารามิเตอร์ที่สองซึ่งจะผ่านเข้าสู่_.map() _.property()เป็นผลให้สองบรรทัดต่อไปนี้เทียบเท่ากับตัวอย่างโค้ดด้านบนจาก pre-Lodash 4

var result = _.map(objArray, 'foo');
var result = _.map(objArray, _.property('foo'));

_.property()และดังนั้นจึง_.map()อนุญาตให้คุณระบุสตริงหรืออาร์เรย์ที่คั่นด้วยจุดเพื่อเข้าถึงคุณสมบัติย่อย:

var objArray = [
    {
        someProperty: { aNumber: 5 }
    },
    {
        someProperty: { aNumber: 2 }
    },
    {
        someProperty: { aNumber: 9 }
    }
];
var result = _.map(objArray, _.property('someProperty.aNumber'));
var result = _.map(objArray, _.property(['someProperty', 'aNumber']));

ทั้งสองสายในตัวอย่างข้างต้นจะกลับมา_.map()[5, 2, 9]

หากคุณเพิ่มอีกนิดในการเขียนโปรแกรมเชิงฟังก์ชั่นลองดูฟังก์ชั่นของ Ramda R.pluck()ซึ่งจะมีลักษณะดังนี้:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)

5
ข่าวดี: ที่ไหนสักแห่งระหว่าง Lodash 4.0.0 และ 4.3.0 _.property('foo')( lodash.com/docs#property ) function(o) { return o.foo; }ถูกบันทึกเป็นชวเลขเป็น วิธีนี้จะทำให้กรณีการใช้งานของ Lodash สั้นลงเพื่อvar result = _.pluck(objArray, _.property('foo'));ความสะดวกยิ่งขึ้น_.map() วิธีการของ Lodash 4.3.0 ยังอนุญาตให้ใช้ชวเลข_.property()ภายใต้ประทุนได้ด้วยvar result = _.map(objArray, 'foo');
Mark A. Fitzgerald

45

สำหรับการพูดถึงวิธีแก้ปัญหาของ JS เท่านั้นฉันพบว่ามันอาจจะไม่เหมาะสมเท่าที่ควรการforวนรอบแบบดัชนีง่ายกว่าตัวเลือกอื่น

แยกคุณสมบัติเดียวจากอาร์เรย์องค์ประกอบ 100,000 (ผ่าน jsPerf)

แบบดั้งเดิมสำหรับลูป 368 Ops / วินาที

var vals=[];
for(var i=0;i<testArray.length;i++){
   vals.push(testArray[i].val);
}

ES6 สำหรับ.. ของวนรอบ 303 Ops / วินาที

var vals=[];
for(var item of testArray){
   vals.push(item.val); 
}

Array.prototype.map 19 Ops / วินาที

var vals = testArray.map(function(a) {return a.val;});

TL; DR - .map () ช้า แต่อย่าลังเลที่จะใช้หากคุณรู้สึกว่าการอ่านมีค่ามากกว่าประสิทธิภาพ

แก้ไข # 2: 6/2019 - ลิงก์ jsPerf เสียหายลบออก


1
โทรหาฉันในสิ่งที่คุณชอบ แต่ประสิทธิภาพก็นับ หากคุณเข้าใจแผนที่คุณสามารถเข้าใจการวนซ้ำ
illcrx

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

15

การใช้Array.prototype.map:

function getFields(input, field) {
    return input.map(function(o) {
        return o[field];
    });
}

ดูลิงค์ด้านบนสำหรับ shim สำหรับเบราว์เซอร์ pre-ES5


15

มันเป็นการดีกว่าที่จะใช้ไลบรารี่บางประเภทเช่น lodash หรือขีดล่างสำหรับการประกันเบราว์เซอร์ข้าม

ใน Lodash คุณสามารถรับค่าของคุณสมบัติในอาร์เรย์ได้โดยวิธีการดังต่อไปนี้

_.map(objArray,"foo")

และในขีดล่าง

_.pluck(objArray,"foo")

ทั้งสองจะกลับมา

[1, 2, 3]

10

ใน ES6 คุณสามารถทำได้:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)

ถ้า foo ไม่ได้นิยามไว้ล่ะ? อาร์เรย์ที่ไม่ได้กำหนดไม่ดี
godhar

6

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

function getFields(list, field) {
    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  check if the item is actually an object and does contain the field
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

ตัวอย่าง jsbin

สิ่งนี้จะใช้ได้แม้ว่ารายการใดรายการหนึ่งในรายการที่ระบุไม่ใช่วัตถุหรือไม่มีเขตข้อมูล

มันสามารถทำให้มีความยืดหยุ่นมากขึ้นโดยการเจรจาค่าเริ่มต้นหากรายการไม่ได้เป็นวัตถุหรือไม่มีเขตข้อมูล

function getFields(list, field, otherwise) {
    //  reduce the provided list to an array containing either the requested field or the alternative value
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        carry.push(typeof item === 'object' && field in item ? item[field] : otherwise);

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

ตัวอย่าง jsbin

สิ่งนี้จะเหมือนกันกับแผนที่เนื่องจากความยาวของอาร์เรย์ที่ส่งคืนจะเท่ากับอาร์เรย์ที่ระบุ (ในกรณีmapนี้กถูกกว่าเล็กน้อยreduce):

function getFields(list, field, otherwise) {
    //  map the provided list to an array containing either the requested field or the alternative value
    return list.map(function(item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        return typeof item === 'object' && field in item ? item[field] : otherwise;
    }, []);
}

ตัวอย่าง jsbin

จากนั้นมีวิธีแก้ปัญหาที่ยืดหยุ่นมากที่สุดวิธีหนึ่งที่ช่วยให้คุณสามารถสลับระหว่างพฤติกรรมทั้งสองได้อย่างง่ายดายโดยให้ค่าทางเลือก

function getFields(list, field, otherwise) {
    //  determine once whether or not to use the 'otherwise'
    var alt = typeof otherwise !== 'undefined';

    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of 'otherwise' if it was provided
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }
        else if (alt) {
            carry.push(otherwise);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

ตัวอย่าง jsbin

ดังที่ตัวอย่างด้านบน (หวังว่า) จะทำให้แสงในการทำงานของฟังก์ชันนี้สั้นลงทำให้การทำงานสั้นลงเล็กน้อยโดยใช้Array.concatฟังก์ชัน

function getFields(list, field, otherwise) {
    var alt = typeof otherwise !== 'undefined';

    return list.reduce(function(carry, item) {
        return carry.concat(typeof item === 'object' && field in item ? item[field] : (alt ? otherwise : []));
    }, []);
}

ตัวอย่าง jsbin


6

โดยทั่วไปถ้าคุณต้องการประเมินค่าวัตถุที่อยู่ภายในอาเรย์ (เช่นที่อธิบายไว้ในคำถาม) คุณสามารถใช้การลดการแมปและการทำลายอาร์เรย์

ES6

let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, obj) => [...acc, Object.values(obj).map(y => y)], []);

console.log(b)

การใช้ที่เทียบเท่าสำหรับ in loop จะเป็น:

for (let i in a) {
  let temp = [];
  for (let j in a[i]) {
    temp.push(a[i][j]);
  }
  array.push(temp);
}

เอาต์พุตที่ผลิต: ["คำว่า", "อีกครั้ง", "บางคน", "1", "2", "3"]


3

ขึ้นอยู่กับคำจำกัดความของคุณของ "ดีกว่า"

คำตอบอื่น ๆ ชี้ให้เห็นถึงการใช้งานแผนที่ซึ่งเป็นเรื่องธรรมดา ฉันแนะนำอย่างยิ่งให้ใช้มัน (ถ้าคุณไม่รำคาญกับ IE8- คนไม่กี่คน) ดังนั้นถ้า "ดีกว่า" หมายถึง "รัดกุม", "บำรุงรักษา", "เข้าใจได้" ถ้าใช่ก็ดีกว่า

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

อีกครั้งนี่เป็นเพียง microbench และไม่เคยต่อต้านการใช้mapมันเป็นเพียงสองเซ็นต์ของฉัน :)


นี่คือฝั่งเซิร์ฟเวอร์จริงไม่ใช่ในเบราว์เซอร์ดังนั้น IE8 จึงไม่เกี่ยวข้อง อ๋อmapเป็นวิธีที่ไม่ต้องลงแรงอย่างใดฉันก็ล้มเหลวที่จะหามันด้วย google (ฉันพบเพียงแค่แผนที่ของ jquery ซึ่งไม่เกี่ยวข้อง)
ไฮด์

3

หากคุณต้องการสนับสนุนวัตถุที่มีลักษณะคล้ายอาร์เรย์ให้ใช้Array.from (ES2015):

Array.from(arrayLike, x => x.foo);

ข้อได้เปรียบที่มีมากกว่าวิธีArray.prototype.map ()คืออินพุตสามารถเป็นSet ได้ :

let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);

2

หากคุณต้องการค่าหลายค่าใน ES6 + ข้อมูลต่อไปนี้จะใช้งานได้

objArray = [ { foo: 1, bar: 2, baz: 9}, { foo: 3, bar: 4, baz: 10}, { foo: 5, bar: 6, baz: 20} ];

let result = objArray.map(({ foo, baz }) => ({ foo, baz }))

ผลงานนี้เป็น{foo, baz}ทางด้านซ้ายจะใช้destructoring วัตถุและทางด้านขวาของลูกศรเทียบเท่ากับ{foo: foo, baz: baz}เนื่องจากES6 ของเพิ่มตัวอักษรวัตถุ


สิ่งที่ฉันต้องการคุณคิดว่าวิธีนี้เร็วหรือช้าแค่ไหน?
รูเบน

1
@ Ruben บอกจริง ๆ แล้วฉันคิดว่าคุณจะต้องใช้ตัวเลือกต่าง ๆ จากหน้านี้และทำการทดสอบโดยใช้บางอย่างเช่นjsperf.com
Chris Magnuson

1

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

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

    var obj = {value1: "val1", value2: "val2", Ndb_No: "testing", myVal: undefined};
    var keysFiltered = Object.keys(obj).filter(function(item){return !(item == "Ndb_No" || obj[item] == undefined)});
    var valuesFiltered = keysFiltered.map(function(item) {return obj[item]});

https://jsfiddle.net/ohea7mgk/


0

คำตอบที่ให้ไว้ข้างต้นเป็นสิ่งที่ดีสำหรับการแยกคุณสมบัติเดียวจะเกิดอะไรขึ้นถ้าคุณต้องการแยกคุณสมบัติมากกว่าหนึ่งรายการจากอาร์เรย์ของวัตถุ นี่คือทางออก !! ในกรณีที่เราสามารถใช้ _.pick (object, [path])

_.pick(object, [paths])

ให้ถือว่า objArray มีวัตถุที่มีคุณสมบัติสามอย่างดังนี้

objArray = [ { foo: 1, bar: 2, car:10}, { foo: 3, bar: 4, car:10}, { foo: 5, bar: 6, car:10} ];

ตอนนี้เราต้องการแยกคุณสมบัติ foo และ bar จากทุกวัตถุและเก็บไว้ในอาร์เรย์ที่แยกต่างหาก ก่อนอื่นเราจะวนองค์ประกอบอาร์เรย์โดยใช้แผนที่จากนั้นเราใช้วิธี Lodash Library Standard _.pick () ในนั้น

ตอนนี้เราสามารถแยกคุณสมบัติ 'foo' และ 'bar'

var newArray = objArray.map((element)=>{ return _.pick(element, ['foo','bar'])}) console.log(newArray);

และผลลัพธ์จะเป็น [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

สนุก!!!


1
ในกรณีที่ไม่_.pickมาจากไหน? มันไม่ใช่ฟังก์ชั่นมาตรฐาน
neves

ฉันเพิ่งปรับปรุงคำตอบ _.pick () เป็นวิธีมาตรฐานของห้องสมุด Lodash
Akash Jain

0

หากคุณมีอาร์เรย์ที่ซ้อนกันคุณสามารถทำให้มันทำงานได้ดังนี้:

const objArray = [ 
     { id: 1, items: { foo:4, bar: 2}},
     { id: 2, items: { foo:3, bar: 2}},
     { id: 3, items: { foo:1, bar: 2}} 
    ];

    let result = objArray.map(({id, items: {foo}}) => ({id, foo}))
    
    console.log(result)

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