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); // 7Anonim 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 ) // 7Bunun 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
initialItemsağlanırsa,reducefonksiyonuinitialItem'ıaccumulatorolarak ve dizinin ilk elemanınıitemInArrayolarak geçirir.Eğer
initialItemsağlanmazsa,reducefonksiyonu dizideki ilk öğeyiinitialItemolarak ve dizideki ikinci öğeyiitemInArrayolarak 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.
