Brian Z Brian Z - 25 days ago 10
Javascript Question

Reduce an array of objects to the unique strings found in a property shared by all objects (javascript)

I've written a javascript function that successfully returns an array of all the unique classes used in a HTML document.

const elements = document.getElementsByTagName('*');

let classesUsedInHtml = htmlCollection => {
let uniqClassSelectors = [];
Array.from(htmlCollection)
.filter(element => element.classList.length > 0)
.map(element =>
element.classList.forEach(
item =>
uniqClassSelectors.includes(item)
? null
: uniqClassSelectors.push(item)
)
);
return uniqClassSelectors;
};

console.log(classesUsedInHtml(elements));


I'd like to refactor this function using
Array.prototype.reduce()
, and skip the need to define the variable
uniqClassSelectors
, but haven't been able to make it work.

Here is my refactored function that does not work (returns
accumulator is undefined
). I often get tripped up with
reduce()
in this regard. Why is
accumulator
undefined? Am I not using
forEach()
correctly inside the
reduce()
function? Can anyone point me in the right direction here? Thanks!

let classesUsedInHtml = htmlCollection => {
return Array.from(htmlCollection)
.filter(element => element.classList.length > 0)
.reduce((accumulator, element) => {
return element.classList.forEach(
classSelector =>
accumulator.includes(classSelector)
? accumulator
: accumulator.concat(classSelector)
);
}, []);
};





EDIT: removed the uneccessary
map()
from the function in question due to Nina Scholz comment

Answer Source

I suggest to use another inner accumulator for the final domTokenList and take the outer accumulator as start value for the inner accumulator acc2.

let classesUsedInHtml = htmlCollection =>
    Array
        .from(htmlCollection)
        .filter(element => element.classList.length)
        .map(element => element.classList)
        .reduce((accumulator, domTokenList) =>
            domTokenList.reduce((acc2, classSelector) =>
                acc2.concat(acc2.includes(classSelector) ? [] : classSelector),
                accumulator
            ),
            []
        );

An other solution could be the use of Set for collecting unique class selectors.

let classesUsedInHtml = htmlCollection =>
    [...Array
        .from(htmlCollection)
        .filter(element => element.classList.length)
        .map(element => element.classList)
        .reduce((acc1, domTokenList) =>
            domTokenList.reduce((acc2, classSelector) => acc2.add(classSelector), acc1),
            new Set)
    ];