Javascript giảm () trên Object


182

Có một phương thức Array đẹp reduce()để lấy một giá trị từ Array. Thí dụ:

[0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){
  return previousValue + currentValue;
});

Cách tốt nhất để đạt được điều tương tự với các đối tượng là gì? Tôi muốn làm điều này:

{ 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
}.reduce(function(previous, current, index, array){
  return previous.value + current.value;
});

Tuy nhiên, Object dường như không có reduce()phương thức nào được thực hiện.


1
Bạn đang sử dụng Underscore.js?
Sethen

Không. Liệu Underscore cung cấp giảm cho các đối tượng?
Pavel S.

Tôi không thể nhớ. Tôi biết nó có một reducephương pháp. Tôi sẽ kiểm tra ở đó. Mặc dù vậy, giải pháp không có vẻ khó khăn.
Sethen

1
@Sethen Maleno, @Pavel: có _làm giảm đối tượng. Không chắc chắn nếu nó hoạt động một cách tình cờ hoặc nếu sự hỗ trợ đối tượng là có chủ ý, nhưng thực sự bạn có thể vượt qua một đối tượng như trong ví dụ của câu hỏi này và nó (về mặt khái niệm) for..in, gọi hàm lặp của bạn với các giá trị được tìm thấy ở mỗi khóa.
Roatin Marth

Câu trả lời:


55

Những gì bạn thực sự muốn trong trường hợp này là Object.values. Đây là một triển khai ES6 ngắn gọn với ý nghĩ đó:

const add = {
  a: {value:1},
  b: {value:2},
  c: {value:3}
}

const total = Object.values(add).reduce((t, {value}) => t + value, 0)

console.log(total) // 6

hoặc đơn giản:

const add = {
  a: 1,
  b: 2,
  c: 3
}

const total = Object.values(add).reduce((t, n) => t + n)

console.log(total) // 6

Đó là Array.prototype.values ​​() mà bạn đã liên kết - đã chỉnh sửa ngay bây giờ
Jonathan Wood

281

Một lựa chọn sẽ được reducecác keys():

var o = { 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
};

Object.keys(o).reduce(function (previous, key) {
    return previous + o[key].value;
}, 0);

Với điều này, bạn sẽ muốn chỉ định một giá trị ban đầu hoặc vòng 1 sẽ là 'a' + 2.

Nếu bạn muốn kết quả là Đối tượng ( { value: ... }), bạn sẽ phải khởi tạo và trả lại đối tượng mỗi lần:

Object.keys(o).reduce(function (previous, key) {
    previous.value += o[key].value;
    return previous;
}, { value: 0 });

15
Câu trả lời hay nhưng dễ đọc hơn khi sử dụng Object.values ​​thay vì Object.keys vì chúng tôi lo ngại về các giá trị ở đây không phải là các khóa. Nó phải như thế này: Object.values ​​(o) .reduce ((tổng, hiện tại) => tổng + current.value, 0);
Mina Luke

3
Object.values có hỗ trợ trình duyệt tồi tệ hơn nhiều so với Object.keys, nhưng điều đó có thể không phải là một vấn đề nếu bạn sử dụng một polyfill hoặc bạn transpile với Babel
duhaime

chính xác, tôi đã sử dụng điều này trong các khóa đã được lấy từ mô hình mongo và tôi đã chuyển một đối tượng trống làm giá trị ban đầu cho kết quả giảm các khóa và khi tôi sử dụng @babel/plugin-proposal-object-rest-spreadplugin, tôi đã lan truyền giá trị tích lũy trong trở lại của giảm và nó hoạt động như một bùa mê, nhưng tôi đã tự hỏi nếu tôi làm điều gì đó sai, bởi vì tôi đã chuyển sự phản đối đến giá trị ban đầu của giảm và câu trả lời của bạn đã chứng minh rằng tôi đã làm đúng!
a_m_dev

55

Triển khai ES6: Object.entries ()

const o = {
  a: {value: 1},
  b: {value: 2},
  c: {value: 3}
};

const total = Object.entries(o).reduce(function (total, pair) {
  const [key, value] = pair;
  return total + value;
}, 0);

3
Object.entries(o); // returns [['value',1],['value',2],['value',3]]
faboulaws

1
const [khóa, giá trị] = cặp; Tôi chưa bao giờ thấy nó cả!
Martin Meeser

9
@ martin-meeser - điều này được gọi là phá hủy. Chúng tôi thậm chí có thể bỏ qua dòng này bằng cách thay đổi function (total, pair)thànhfunction (total, [key, value])
Jakub Zawiślak

Trong khi entrieslà một cách sạch hơn để làm điều này hơn keys, nó là ít hiệu quả hơn. hackernoon.com/ từ
robdonn

@faboulaws Object.entries (o); // trả về [["a", {value: 1}], ["b", {value: 2}], ["c", {value: 3}]]
Thomas

19

Trước hết, bạn không hoàn toàn nhận được những gì của giảm của giá trị trước đó là.

Trong mã giả của bạn return previous.value + current.value, do đó, previousgiá trị sẽ là một số trong cuộc gọi tiếp theo, không phải là một đối tượng.

Thứ hai, reducelà một phương thức Array, không phải là một đối tượng và bạn không thể dựa vào thứ tự khi bạn lặp lại các thuộc tính của một đối tượng (xem: https://developer.mozilla.org/en-US/docs/ JavaScript / Reference / Statements / for ... in , điều này cũng được áp dụng cho Object.keys ); Vì vậy, tôi không chắc chắn nếu áp dụng reducetrên một đối tượng có ý nghĩa.

Tuy nhiên, nếu thứ tự không quan trọng, bạn có thể có:

Object.keys(obj).reduce(function(sum, key) {
    return sum + obj[key].value;
}, 0);

Hoặc bạn chỉ có thể ánh xạ giá trị của đối tượng:

Object.keys(obj).map(function(key) { return this[key].value }, obj).reduce(function (previous, current) {
    return previous + current;
});

PS trong ES6 với cú pháp của hàm mũi tên béo (đã có trong Firefox Nightly), bạn có thể thu nhỏ một chút:

Object.keys(obj).map(key => obj[key].value).reduce((previous, current) => previous + current);

3

1:

[{value:5}, {value:10}].reduce((previousValue, currentValue) => { return {value: previousValue.value + currentValue.value}})

>> Object {value: 15}

2:

[{value:5}, {value:10}].map(item => item.value).reduce((previousValue, currentValue) => {return previousValue + currentValue })

>> 15

3:

[{value:5}, {value:10}].reduce(function (previousValue, currentValue) {
      return {value: previousValue.value + currentValue.value};
})

>> Object {value: 15}

2

Mở rộng Object.prototype.

Object.prototype.reduce = function( reduceCallback, initialValue ) {
    var obj = this, keys = Object.keys( obj );

    return keys.reduce( function( prevVal, item, idx, arr ) {
        return reduceCallback( prevVal, item, obj[item], obj );
    }, initialValue );
};

Mẫu sử dụng.

var dataset = {
    key1 : 'value1',
    key2 : 'value2',
    key3 : 'value3'
};

function reduceFn( prevVal, key, val, obj ) {
    return prevVal + key + ' : ' + val + '; ';
}

console.log( dataset.reduce( reduceFn, 'initialValue' ) );
'Output' == 'initialValue; key1 : value1; key2 : value2; key3 : value3; '.

n'Joy nó, các bạn !! ;-)


-1, bây giờ bạn có một tài sản vô số mới trên tất cả các đối tượng trong tương lai: jsfiddle.net/ygonjooh
Johan


1
Vui lòng không sửa đổi nguyên mẫu cơ sở như thế này. Điều này có thể dẫn đến rất nhiều vấn đề cho các nhà phát triển trong tương lai làm việc trong cùng một cơ sở mã
adam.k

Vâng, đó là một "bản vá khỉ" Giải pháp này đã được viết 6 năm trước và bây giờ không quá liên quan, chỉ cần ghi nhớ Và ví dụ, sẽ tốt hơn khi sử dụng Object.entries()vào năm 2021
user1247458

2

Bạn có thể sử dụng biểu thức trình tạo (được hỗ trợ trong tất cả các trình duyệt trong nhiều năm nay và trong Nút) để lấy các cặp khóa-giá trị trong danh sách bạn có thể giảm trên:

>>> a = {"b": 3}
Object { b=3}

>>> [[i, a[i]] for (i in a) if (a.hasOwnProperty(i))]
[["b", 3]]

2

Một đối tượng có thể được biến thành một mảng với: Object.entries () , Object.keys () , Object.values ​​() , sau đó được thu nhỏ thành mảng. Nhưng bạn cũng có thể giảm một đối tượng mà không cần tạo mảng trung gian.

Tôi đã tạo một helper ít thư viện odict để làm việc với các đối tượng.

npm install --save odict

Nó có reducechức năng hoạt động rất giống Array.prototype.reduce () :

export const reduce = (dict, reducer, accumulator) => {
  for (const key in dict)
    accumulator = reducer(accumulator, dict[key], key, dict);
  return accumulator;
};

Bạn cũng có thể gán nó cho:

Object.reduce = reduce;

vì phương pháp này rất hữu ích!

Vì vậy, câu trả lời cho câu hỏi của bạn sẽ là:

const result = Object.reduce(
  {
    a: {value:1},
    b: {value:2},
    c: {value:3},
  },
  (accumulator, current) => (accumulator.value += current.value, accumulator), // reducer function must return accumulator
  {value: 0} // initial accumulator value
);

1

Nếu bạn có thể sử dụng một mảng, hãy sử dụng một mảng, độ dài và thứ tự của một mảng là một nửa giá trị của nó.

function reducer(obj, fun, temp){
    if(typeof fun=== 'function'){
        if(temp== undefined) temp= '';
        for(var p in obj){
            if(obj.hasOwnProperty(p)){
                temp= fun(obj[p], temp, p, obj);
            }
        }
    }
    return temp;
}
var O={a:{value:1},b:{value:2},c:{value:3}}

reducer(O, function(a, b){return a.value+b;},0);

/ * giá trị trả về: (Số) 6 * /


1

Điều này không khó lắm để tự thực hiện:

function reduceObj(obj, callback, initial) {
    "use strict";
    var key, lastvalue, firstIteration = true;
    if (typeof callback !== 'function') {
        throw new TypeError(callback + 'is not a function');
    }   
    if (arguments.length > 2) {
        // initial value set
        firstIteration = false;
        lastvalue = initial;
    }
    for (key in obj) {
        if (!obj.hasOwnProperty(key)) continue;
        if (firstIteration)
            firstIteration = false;
            lastvalue = obj[key];
            continue;
        }
        lastvalue = callback(lastvalue, obj[key], key, obj);
    }
    if (firstIteration) {
        throw new TypeError('Reduce of empty object with no initial value');
    }
    return lastvalue;
}

Trong hành động:

var o = {a: {value:1}, b: {value:2}, c: {value:3}};
reduceObj(o, function(prev, curr) { prev.value += cur.value; return prev;}, {value:0});
reduceObj(o, function(prev, curr) { return {value: prev.value + curr.value};});
// both == { value: 6 };

reduceObj(o, function(prev, curr) { return prev + curr.value; }, 0);
// == 6

Bạn cũng có thể thêm nó vào nguyên mẫu Object:

if (typeof Object.prototype.reduce !== 'function') {
    Object.prototype.reduce = function(callback, initial) {
        "use strict";
        var args = Array.prototype.slice(arguments);
        args.unshift(this);
        return reduceObj.apply(null, args);
    }
}

0

Vì nó chưa thực sự được xác nhận trong câu trả lời, Underscore reducecũng hoạt động cho việc này.

_.reduce({ 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
}, function(prev, current){
    //prev is either first object or total value
    var total = prev.value || prev

    return total + current.value
})

Lưu ý, _.reducesẽ trả về giá trị duy nhất (đối tượng hoặc cách khác) nếu đối tượng danh sách chỉ có một mục, mà không gọi hàm iterator.

_.reduce({ 
    a: {value:1} 
}, function(prev, current){
    //not called
})

//returns {value: 1} instead of 1

"Hãy thử thư viện khác này" không hữu ích
Grunion Shaftoe

Không hữu ích với bạn
Chris Dolphin

0

Hãy thử chức năng một mũi tên lót này

Object.values(o).map(a => a.value, o).reduce((ac, key, index, arr) => ac+=key)

0

Hãy thử cái này Nó sẽ sắp xếp số từ các biến khác.

const obj = {
   a: 1,
   b: 2,
   c: 3
};
const result = Object.keys(obj)
.reduce((acc, rec) => typeof obj[rec] === "number" ? acc.concat([obj[rec]]) : acc, [])
.reduce((acc, rec) => acc + rec)
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.