Получаем все возможные ключи; под каждый создаём пустой массив; при обработке объекта из исходного массива бежим по массиву ключей, добавляем в соответствующий массив значение из объекта или дефолтное - в зависимости от наличия ключа в объекте:
function groupValues(arr, defaultValue = null) {
const keys = [...new Set(arr.flatMap(Object.keys))];
return arr.reduce((acc, n) => {
keys.forEach(k => acc[k].push(Object.hasOwn(n, k) ? n[k] : defaultValue));
return acc;
}, Object.fromEntries(keys.map(k => [ k, [] ])));
}
const result = groupValues(arr);
Или, результирующий объект изначально пуст; при обработке объекта из исходного массива перебираем его ключи; если ключ отсутствует в результирующем объекте, создаём массив с длиной как у исходного, заполняем его дефолтным значением; записываем в массив в результирующем объекте значение под тем же индексом, который имеет обрабатываемый объект в исходном массиве:
const groupValues = (arr, defaultValue = null) =>
arr.reduce((acc, n, i, a) => (
Object
.keys(n)
.forEach(k => (acc[k] ??= Array(a.length).fill(defaultValue))[i] = n[k]),
acc
), {});