Từ một mảng các đối tượng, trích xuất giá trị của một thuộc tính dưới dạng mảng


1019

Tôi có mảng đối tượng JavaScript với cấu trúc sau:

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

Tôi muốn trích xuất một trường từ mỗi đối tượng và lấy một mảng chứa các giá trị, ví dụ như trường foosẽ cho mảng [ 1, 3, 5 ].

Tôi có thể làm điều này với cách tiếp cận tầm thường này:

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 ]

Có một cách thanh lịch hoặc thành ngữ hơn để làm điều này, để một chức năng tiện ích tùy chỉnh sẽ không cần thiết?


Lưu ý về trùng lặp được đề xuất , nó bao gồm cách chuyển đổi một đối tượng thành một mảng.


4
Thư viện Prototype đã thêm chức năng "nhổ" vào nguyên mẫu Array (tôi nghĩ vậy), để bạn có thể viết var foos = objArray.pluck("foo");.
Mũi nhọn

3
@hyde - jsperf.com/map-vs-native-for-loop - vui lòng xem cái này, hy vọng đơn giản lặp lại một giải pháp tốt
N20084753

1
@ N20084753 để kiểm tra công bằng, bạn cũng nên so sánh Array.prototype.mapchức năng gốc nơi nó tồn tại
Alnitak

@Alnitak - ya bạn đúng nhưng tôi không thể hiểu làm thế nào Array.prototype.map được tối ưu hóa hơn bản địa cho vòng lặp, bất kỳ cách nào chúng ta cần để duyệt qua mảng hoàn chỉnh.
N20084753

3
OP, tôi thích cách tiếp cận của bạn với bất kỳ người nào khác đã được đề xuất. Không có gì sai với nó.

Câu trả lời:


1336

Đây là một cách ngắn hơn để đạt được nó:

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

HOẶC LÀ

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

Bạn cũng có thể kiểm tra Array.prototype.map().


3
Chà, điều này cũng giống như nhận xét của một câu trả lời khác của totymedli, nhưng theo cách khác, nó thực sự tốt hơn (theo ý kiến ​​của tôi) so với các câu trả lời khác , vì vậy ... Thay đổi nó thành câu trả lời được chấp nhận.
hyde

4
Các chức năng @PauloRoberto Mũi tên về cơ bản được hỗ trợ ở mọi nơi trừ IE.
tgies

1
chắc chắn, điều đó được cho phép, nhưng IMHO không có gì làm cho câu trả lời này khách quan hơn, ngoại trừ việc nó sử dụng một cú pháp không có sẵn tại thời điểm bạn đặt câu hỏi và thậm chí không được hỗ trợ trong một số trình duyệt. Tôi cũng lưu ý rằng câu trả lời này là một bản sao trực tiếp của các bình luận được đưa ra cho câu trả lời được chấp nhận ban đầu gần một năm trước khi câu trả lời này được đăng.
Alnitak

26
@Alnitak Sử dụng chức năng mới hơn, theo quan điểm của tôi, khách quan hơn. Đoạn trích này cực kỳ phổ biến, vì vậy tôi không tin đây là đạo văn. Thực sự không có bất kỳ giá trị nào trong việc giữ các câu trả lời lỗi thời được ghim lên đầu.
Cướp

6
bình luận không phải là câu trả lời, imo nếu ai đó đăng bình luận thay vì trả lời, đó là lỗi của chính họ nếu ai đó sao chép nó thành câu trả lời.
Aequitas

619

Có, nhưng nó phụ thuộc vào tính năng ES5 của JavaScript. Điều này có nghĩa là nó sẽ không hoạt động trong IE8 trở lên.

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

Trên các trình thông dịch JS tương thích ES6, bạn có thể sử dụng hàm mũi tên cho ngắn gọn:

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

Tài liệu Array.prototype.map


5
Giải pháp này có vẻ gọn gàng và đơn giản nhưng nó được tối ưu hóa hơn phương pháp lặp đơn giản.
N20084753

OP không muốn một phương thức để có được bất kỳ trường nào , không chỉ mã hóa cứng foo?
Alnitak

2
trong ES6 bạn có thể làmvar result = objArray.map((a) => (a.foo));
Đen

28
@Black Thậm chí tốt hơn:var result = objArray.map(a => a.foo);
totymedli

4
@FaizKhan Thông báo năm. Câu trả lời này đã được đăng vào ngày 25 tháng 10 năm 2013. Câu trả lời "được chấp nhận" đã được đăng vào ngày 11 tháng 10 năm 2017. Nếu có gì đáng chú ý thì dấu kiểm đã chuyển sang câu trả lời khác.
Niet the Dark Tuyệt đối

52

Kiểm tra chức năng của Lodash_.pluck() hoặc chức năng Underscore_.pluck() . Cả hai đều làm chính xác những gì bạn muốn trong một cuộc gọi chức năng duy nhất!

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

Cập nhật: _.pluck()đã bị xóa kể từ phiên bản Lodash v4.0.0 , ủng hộ _.map()kết hợp với câu trả lời tương tự như của Niet . _.pluck()vẫn có sẵn trong Underscore .

Cập nhật 2: Như Mark chỉ ra trong các bình luận , ở đâu đó giữa Lodash v4 và 4.3, một chức năng mới đã được thêm vào để cung cấp lại chức năng này. _.property()là một hàm tốc ký trả về một hàm để lấy giá trị của một thuộc tính trong một đối tượng.

Ngoài ra, _.map()bây giờ cho phép một chuỗi được truyền vào dưới dạng tham số thứ hai, được truyền vào _.property(). Kết quả là, hai dòng sau tương đương với mẫu mã ở trên từ tiền tố 4.

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

_.property()và do đó _.map(), cũng cho phép bạn cung cấp một chuỗi hoặc mảng được phân tách bằng dấu chấm để truy cập các thuộc tính phụ:

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']));

Cả hai _.map()cuộc gọi trong ví dụ trên sẽ trở lại [5, 2, 9].

Nếu bạn có thêm một chút về lập trình chức năng, hãy xem chức năng của Ramda R.pluck() , nó sẽ trông giống như thế này:

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

5
Tin tốt: một nơi nào đó giữa Lodash 4.0.0 và 4.3.0 _.property('foo')( lodash.com/docs#property ) đã được thêm vào như một cách viết tắt cho function(o) { return o.foo; }. Điều này rút ngắn trường hợp sử dụng Lodash để var result = _.pluck(objArray, _.property('foo'));thuận tiện hơn, _.map() phương pháp của Lodash 4.3.0 cũng cho phép sử dụng tốc ký _.property()dưới mui xe, dẫn đếnvar result = _.map(objArray, 'foo');
Mark A. Fitzgerald

45

Nói về các giải pháp chỉ dành cho JS, tôi đã thấy rằng, không có khả năng, một forvòng lặp được lập chỉ mục đơn giản sẽ có hiệu suất cao hơn các giải pháp thay thế.

Trích xuất một thuộc tính từ một mảng phần tử 100000 (thông qua jsPerf)

Truyền thống cho vòng lặp 368 Ops / giây

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

ES6 cho..có vòng lặp 303 Ops / giây

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

Array.prototype.map 19 Ops / giây

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

TL; DR - .map () chậm, nhưng hãy sử dụng nó nếu bạn cảm thấy khả năng đọc đáng giá hơn hiệu suất.

Chỉnh sửa # 2: 6/2019 - liên kết jsPerf bị hỏng, bị xóa.


1
Gọi cho tôi những gì bạn thích, nhưng hiệu suất tính. Nếu bạn có thể hiểu bản đồ, bạn có thể hiểu một vòng lặp for.
illcrx

Mặc dù rất hữu ích khi tìm hiểu về kết quả điểm chuẩn của bạn, tôi nghĩ rằng nó không phù hợp ở đây, vì nó không trả lời câu hỏi này. Để cải thiện nó, hãy thêm một câu trả lời thực sự, hoặc nếu không thì nén nó vào một bình luận (nếu có thể).
Ifedi Okonkwo

16

Tốt hơn là sử dụng một số loại thư viện như lodash hoặc gạch dưới để đảm bảo trình duyệt chéo.

Trong Lodash, bạn có thể nhận các giá trị của một thuộc tính trong mảng theo phương pháp sau

_.map(objArray,"foo")

và trong gạch dưới

_.pluck(objArray,"foo")

Cả hai sẽ trở lại

[1, 2, 3]

15

Sử dụng Array.prototype.map:

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

Xem liên kết ở trên để biết sơ lược về trình duyệt trước ES5.


10

Trong ES6, bạn có thể làm:

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

Nếu foo không được xác định thì sao? Một mảng không xác định là không tốt.
godhar

6

Mặc dù maplà một giải pháp thích hợp để chọn 'cột' từ danh sách các đối tượng, nhưng nó có một nhược điểm. Nếu không được kiểm tra rõ ràng liệu các cột có tồn tại hay không, nó sẽ đưa ra lỗi và (tốt nhất) cung cấp cho bạn undefined. Tôi muốn chọn reducegiải pháp, có thể chỉ cần bỏ qua thuộc tính hoặc thậm chí thiết lập cho bạn một giá trị mặc định.

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;
    }, []);
}

ví dụ jsbin

Điều này sẽ hoạt động ngay cả khi một trong các mục trong danh sách được cung cấp không phải là một đối tượng hoặc không chứa trường.

Nó thậm chí có thể được thực hiện linh hoạt hơn bằng cách đàm phán một giá trị mặc định nếu một mục không phải là một đối tượng hoặc không chứa trường.

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;
    }, []);
}

ví dụ jsbin

Điều này sẽ giống với bản đồ, vì độ dài của mảng được trả về sẽ giống với mảng được cung cấp. (Trong trường hợp a maprẻ hơn một chút 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;
    }, []);
}

ví dụ jsbin

Và sau đó là giải pháp linh hoạt nhất, một giải pháp cho phép bạn chuyển đổi giữa cả hai hành vi chỉ bằng cách cung cấp một giá trị thay thế.

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;
    }, []);
}

ví dụ jsbin

Như các ví dụ ở trên (hy vọng) làm sáng tỏ cách thức hoạt động của nó, hãy rút ngắn chức năng một chút bằng cách sử dụng Array.concatchức năng.

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 : []));
    }, []);
}

ví dụ jsbin


6

Nói chung, nếu bạn muốn ngoại suy các giá trị đối tượng nằm trong một mảng (như được mô tả trong câu hỏi) thì bạn có thể sử dụng hàm khử, ánh xạ và phá hủy mảng.

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)

Tương đương sử dụng vòng lặp for sẽ là:

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

Sản lượng đầu ra: ["từ", "lần nữa", "một số", "1", "2", "3"]


3

Nó phụ thuộc vào định nghĩa của bạn về "tốt hơn".

Các câu trả lời khác chỉ ra việc sử dụng bản đồ, đó là điều tự nhiên (đặc biệt đối với những người sử dụng phong cách chức năng) và súc tích. Tôi thực sự khuyên bạn nên sử dụng nó (nếu bạn không bận tâm với vài kẻ IE8- IT). Vì vậy, nếu "tốt hơn" có nghĩa là "ngắn gọn hơn", "có thể duy trì", "có thể hiểu được" thì có, đó là cách tốt hơn.

Mặt khác, vẻ đẹp này không đến mà không phải trả thêm chi phí. Tôi không phải là một fan hâm mộ lớn của microbench, nhưng tôi đã đưa ra một thử nghiệm nhỏ ở đây . Kết quả là có thể dự đoán được, cách xấu xí cũ dường như nhanh hơn chức năng bản đồ. Vì vậy, nếu "tốt hơn" có nghĩa là "nhanh hơn", thì không, hãy ở lại với thời trang trường học cũ.

Một lần nữa, đây chỉ là một microbench và không có cách nào ủng hộ việc sử dụng map, đó chỉ là hai xu của tôi :).


Chà, đây thực sự là phía máy chủ, không phải trong trình duyệt, vì vậy IE8 không liên quan. Đúng, maplà cách khó khăn để đi, bằng cách nào đó tôi đã thất bại trong việc tìm kiếm nó bằng google (tôi chỉ tìm thấy bản đồ của jquery, không liên quan).
hyde

3

Nếu bạn cũng muốn hỗ trợ các đối tượng giống như mảng, hãy sử dụng Array.from (ES2015):

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

Ưu điểm của nó so với phương thức Array.prototype.map () là đầu vào cũng có thể là Set :

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

2

Nếu bạn muốn nhiều giá trị trong ES6 + sau đây sẽ hoạt động

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

Điều này hoạt động như {foo, baz}ở bên trái đang sử dụng phá hủy đối tượng và ở bên phải của mũi tên tương đương với {foo: foo, baz: baz}do các đối tượng được tăng cường của ES6 .


chỉ cần những gì tôi cần, bạn nghĩ giải pháp này nhanh / chậm như thế nào?
Ruben

1
@Ruben Để thực sự nói với tôi, tôi nghĩ rằng bạn sẽ cần phải thực hiện các tùy chọn khác nhau từ trang này và đưa chúng vào các thử nghiệm bằng cách sử dụng một cái gì đó như jsperf.com
Chris Magnuson

1

Sơ đồ chức năng là một lựa chọn tốt khi xử lý các mảng đối tượng. Mặc dù đã có một số câu trả lời hay được đăng, ví dụ về việc sử dụng bản đồ kết hợp với bộ lọc có thể hữu ích.

Trong trường hợp bạn muốn loại trừ các thuộc tính mà các giá trị không được xác định hoặc chỉ loại trừ một thuộc tính cụ thể, bạn có thể làm như sau:

    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

Câu trả lời được cung cấp ở trên là tốt cho việc trích xuất một thuộc tính, nếu bạn muốn trích xuất nhiều hơn một thuộc tính từ mảng các đối tượng. Đây là giải pháp !! Trong trường hợp đó, chúng ta chỉ cần sử dụng _.pick (object, [path])

_.pick(object, [paths])

Giả sử objArray có các đối tượng có ba thuộc tính như bên dưới

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

Bây giờ chúng tôi muốn trích xuất thuộc tính foo và bar từ mọi đối tượng và lưu trữ chúng trong một mảng riêng biệt. Đầu tiên, chúng tôi sẽ lặp lại các phần tử mảng bằng cách sử dụng bản đồ và sau đó chúng tôi áp dụng phương thức Lodash Library Standard _.pick () trên đó.

Bây giờ chúng tôi có thể trích xuất thuộc tính 'foo' và 'bar'.

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

và kết quả sẽ là [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

thưởng thức!!!


1
Nơi nào _.pickđến từ đâu? Nó không phải là một chức năng tiêu chuẩn.
Neves

Tôi vừa cập nhật câu trả lời, _.pick () là một phương thức chuẩn của thư viện Lodash.
Akash Jain

0

Nếu bạn có các mảng lồng nhau, bạn có thể làm cho nó hoạt động như thế này:

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)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.