javascript - Nesneler dizisindeki özelliklerini toplamak için reduce nasıl çağrılır?


Diyelim ki arr içindeki her eleman için a.x değerini toplamak istiyorum.

arr = [ { x: 1 }, { x: 2 }, { x: 4 } ];
arr.reduce(function(a, b){ return a.x + b.x; }); // => NaN

a.x'in bir noktada undefined olduğuna inanmak için nedenim var.

Aşağıdakiler iyi çalışıyor

arr = [ 1, 2, 4 ];
arr.reduce(function(a, b){ return a + b; }); // => 7

İlk örnekte neyi yanlış yapıyorum?

Cevap #1

Bunu başarmanın daha temiz bir yolu, reduce'e ikinci argüman olarak başlangıç değeri sağlamaktır:

var arr = [{x:1}, {x:2}, {x:4}];
var result = arr.reduce(function (acc, obj) { return acc + obj.x; }, 0);
console.log(result);  // 7

Anonim fonksiyon ilk kez çağrıldığında, (0, {x: 1}) ile çağrılır ve 0 + 1=1 değerini döner. Bir sonraki sefer (1, {x: 2}) ile çağrılır ve 1 + 2=3 değerini döner. Daha sonra (3, {x: 4}) ile çağrılır ve sonunda 7 değerini döner.

Bu aynı zamanda dizinin boş olduğu durumu da ele alır ve 0 döner.

Cevap #2

İlk yinelemeden sonra bir sayı döndürüyorsunuz ve sonra bir sonraki nesneye eklemek için bunun x özelliğini almaya çalışıyorsunuz, bu da undefined ve undefined içeren matematik işlemleri NaN ile sonuçlanır.

parametrelerin x özelliklerinin toplamını içeren bir x özelliğine sahip bir nesne döndürmeyi deneyin:

var arr = [{x:1},{x:2},{x:4}];

arr.reduce(function (a, b) {
  return {x: a.x + b.x}; // returns object with property x
})

// ES6
arr.reduce((a, b) => ({x: a.x + b.x}));

// -> {x: 7}

Yorumlardan eklenen açıklama:

[].reduce metodunun her iterasyonunun dönen değeri, sonraki iterasyonda a değişkeni olarak kullanılır.

İterasyon 1: a={x:1}, b={x:2}, {x: 3} İterasyon 2'de a'ya atandı

Yineleme 2: a={x:3}, b={x:4}.

Senin örneğinle ilgili sorun, bir sayı literalini döndürüyor olman.

function (a, b) {
  return a.x + b.x; // returns number literal
}

İterasyon 1: a={x:1}, b={x:2}, // bir sonraki iterasyonda a olarak 3 döner

İterasyon 2: a=3, b={x:2} NaN döndürür

Bir sayı literalinin 3 genellikle x adında bir özelliği yoktur, bu yüzden undefined olur ve undefined + b.x NaN döndürür ve NaN + <herhangi bir şey> her zaman NaN olur

Açıklama: Bu konudaki diğer en iyi cevaba göre yöntemimi tercih ediyorum çünkü isteğe bağlı bir parametreyi magic number ile reduce fonksiyonuna geçirerek bir sayı primitive'ı elde etmenin daha temiz olduğu fikrine katılmıyorum. Bu, yazılan satır sayısını azaltabilir ama bence okunabilirliği daha düşük.

Cevap #3

Özetle, başlangıç değerini ayarlayın

Yapı bozmayı kullanma

arr.reduce( ( sum, { x } ) => sum + x , 0)

Yapıbozumsuz

arr.reduce( ( sum , cur ) => sum + cur.x , 0)

TypeScript ile

arr.reduce( ( sum, { x } : { x: number } ) => sum + x , 0)

Yıkıcı ayırma yöntemini deneyelim:

const arr = [ { x: 1 }, { x: 2 }, { x: 4 } ]
const result = arr.reduce( ( sum, { x } ) => sum + x , 0)
console.log( result ) // 7

Bunun anahtarı başlangıç değerini ayarlamaktır. Dönen değer bir sonraki iterasyonun ilk parametresi olur.

En iyi yanıtta kullanılan teknik deyimsel değildir

Kabul edilen cevap, "optional" değerini geçirmemeyi öneriyor. Bu yanlış, çünkü deyimsel kullanımda ikinci parametrenin her zaman dahil edilmesidir. Neden? Üç neden:

1. Tehlikeli -- Başlangıç değerinin verilmemesi tehlikelidir ve eğer geri çağırma fonksiyonu dikkatsizse yan etkilere ve mutasyonlara yol açabilir.

İşte:

const badCallback = (a,i) => Object.assign(a,i)

const foo = [ { a: 1 }, { b: 2 }, { c: 3 } ]
const bar = foo.reduce( badCallback )  // bad use of Object.assign
// Look, we've tampered with the original array
foo //  [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]

Ancak bunu başlangıç değeri ile bu şekilde yapmış olsaydık:

const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // { a: 1, b: 2, c: 3 }

Kayda geçirmek gerekirse, orijinal nesneyi değiştirmeyi düşünmüyorsanız, Object.assign'in ilk parametresini boş bir nesne olarak ayarlayın. Şöyle: Object.assign({}, a, b, c).

2 - Daha İyi Tür Çıkarımı --TypeScript gibi bir araç veya VS Code gibi bir editör kullanırken, başlatıcıyı derleyiciye söylemenin faydasını elde edersiniz ve yanlış yapıyorsanız hataları yakalayabilir. Başlangıç değerini ayarlamazsanız, birçok durumda tahmin edemeyebilir ve tüyler ürpertici çalışma zamanı hatalarıyla karşılaşabilirsiniz.

3 - Funtörlere Saygı Gösterin -- JavaScript, içindeki fonksiyonel çocuk serbest bırakıldığında en iyi şekilde parlar. Fonksiyonel dünyada, bir diziyi nasıl “katlayacağınız” veya reduce edeceğiniz konusunda bir standart vardır. Diziyi katladığınızda veya bir kathamorfizm uyguladığınızda, o dizinin değerlerini yeni bir tip oluşturmak için kullanırsınız. Ortaya çıkan tipi iletmeniz gerekir--bunu yapmalısınız, hatta nihai tip dizideki değerlerin tipi, başka bir dizi veya herhangi başka bir tip olsa bile.

Bunu başka bir şekilde düşünelim. JavaScript'te, fonksiyonlar veri gibi taşınabilir, işte callback'ler böyle çalışır, aşağıdaki kodun sonucu nedir?

[1,2,3].reduce(callback)

Bir sayı mı döndürecek? Bir nesne mi? Bu daha net hale getiriyor

[1,2,3].reduce(callback,0)

Fonksiyonel programlama spesifikasyonu hakkında daha fazla okumak için buraya bakın: https://github.com/fantasyland/fantasy-land#foldable

Biraz daha arka plan

reduce yöntemi iki parametre alır,

Array.prototype.reduce( callback, initialItem )

callback fonksiyonu aşağıdaki parametreleri alır

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }

İlk yineleme için,

  • Eğer initialItem sağlanırsa, reduce fonksiyonu initialItem'ı accumulator olarak ve dizinin ilk elemanını itemInArray olarak geçirir.

  • Eğer initialItem sağlanmazsa, reduce fonksiyonu dizideki ilk öğeyi initialItem olarak ve dizideki ikinci öğeyi itemInArray olarak geçirir ki bu kafa karıştırıcı bir davranış olabilir.

Her zaman reduce'un başlangıç değerini ayarlamayı öğretiyor ve tavsiye ediyorum.

Belgeleri şuradan inceleyebilirsiniz:

https://developer.mozilla.org/tr/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

Cevap #4

Başka kişiler bu soruyu yanıtladı, ama ben de başka bir yaklaşım sunayım dedim. a.x'i doğrudan toplamak yerine, bir map (a.x'ten x'e) ve reduce (x'leri toplamak için) birleştirebilirsiniz:

arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
   .reduce(function(a,b) {return a + b;});

Kabul etmek gerekir ki, muhtemelen biraz daha yavaş olacak, ama bunu bir seçenek olarak belirtmeye değer olduğunu düşündüm.

İlgili makaleler