如何使用`.filter()`过滤数组/对象并返回一个包含原始键的新数组,而不是像`filter`函数返回的索引数组/对象?

3
var obj = {
    
    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    }, 
    112: {
        user_id: 112,
        user_name: "use112",
        isActive: 1
    }, 
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    }, 
    ... 
}

我想过滤掉所有("isActive" === 0),但返回的新对象中键保持不变(与用户ID相同)。
newObj = {
    
    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    }, 
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    }, 
    ... 
}

这是我现在拥有的内容:

let newObj = Object.values(obj).filter( user => ( (obj.isActive === 0)));

该函数返回索引键

  • 不使用for循环(除非必须使用ES6的.forEach())。
  • 我希望在这个问题上使用ES6中的filter/map/reduce,如果可能的话。
  • Lodash方案也可以,但我仍然想看到一个"vanilla ES6"的例子。
  • 如果我能得到关于如何/何处最好学习和练习使用这些方法对各种数组进行过滤的技巧提示(Lodash也很棒)。
5个回答

6
真正的函数式编程方式是使用重复的对象展开来进行reduce操作:
const filtered = Object.values(obj).reduce((p, e) => (!e.isActive ? {...p, [e.user_id]: e} : p), {});

const obj = {
    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    }, 
    112: {
        user_id: 112,
        user_name: "use112",
        isActive: 1
    }, 
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    }
};
const filtered = Object.values(obj).reduce((p, e) => (!e.isActive ? {...p, [e.user_id]: e} : p), {});
console.log(filtered);
.as-console-wrapper {
  max-height: 100% !important;
}

这会产生很多不必要的临时对象,但遵循了FP原则(我认为,我对FP不是很“深入” : - )不直接修改对象。

稍微打破规则,我们可以修改单个对象而不是创建很多临时对象:

const filtered = Object.values(obj).reduce((newObj, e) => {
  if (!e.isActive) {
    newObj[e.user_id] = e;
  }
  return newObj;
}, {});

const obj = {
    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    }, 
    112: {
        user_id: 112,
        user_name: "use112",
        isActive: 1
    }, 
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    }
};
const filtered = Object.values(obj).reduce((newObj, e) => {
  if (!e.isActive) {
    newObj[e.user_id] = e;
  }
  return newObj;
}, {});
console.log(filtered);
.as-console-wrapper {
  max-height: 100% !important;
}

(通过滥用逗号操作符,可以用更少的字符来编写代码,但这样做不易于维护且难以阅读。)
(如果没有函数式编程的限制,我会使用一个循环:)
const filtered = {};
for (const e of Object.values(obj)) {
  if (!e.isActive) {
    filtered[e.user_id] = e;
  }
}

const obj = {
    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    }, 
    112: {
        user_id: 112,
        user_name: "use112",
        isActive: 1
    }, 
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    }
};
const filtered = {};
for (const e of Object.values(obj)) {
  if (!e.isActive) {
    filtered[e.user_id] = e;
  }
}
console.log(filtered);
.as-console-wrapper {
  max-height: 100% !important;
}


在我看来,循环版本更易读,而且可能是最高效的。有时候我会想,函数式编程的要求是否只是为了显得聪明(当然也有可能是对简单性和性能的错误假设,大多数情况下,函数式编程落后于常规循环)。 - Kaddath

2

进行对象转换的“官方”建议方法是使用 Object.entries “线性化”对象,对键值对执行 map/filter 操作,然后使用 Object.fromEntries 将它们重新组合。后者是新的,因此您需要一个 polyfill。

示例:

// polyfill

Object.fromEntries = Object.fromEntries || function(pairs) {
    let obj = {};
    for (let [k, v] of pairs)
        obj[k] = v;
    return obj;
};


var myObj = {

    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    },
    112: {
        user_id: 112,
        user_name: "use112",
        isActive: 1
    },
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    },
};

result = Object.fromEntries(
    Object.entries(myObj)
        .filter(([k, v]) => v.isActive));

console.log(result)

既然你要求一个函数式编程的解决方案,这里有一个可能的一般化方法:

let apply = (x, fn) => fn(x);

let pipe = (...fns) => x => fns.reduce(apply, x);

let transform = fn => pipe(Object.entries, fn, Object.fromEntries);

let filter = fn => a => a.filter(fn);

let filterObject = fn => transform(filter(fn));

let removeInactive = filterObject(([k, v]) => v.isActive);

console.log(removeInactive(myObj))

FP(函数式编程)是关于用函数组合的方式来表达你的程序,而不是通过reduce在循环中写“里面的外面”。


我不能对一个答案挑三拣四,而对另一个不做要求,这会失去我的荣誉!我完全同意你的最后一条评论,但是使用filter不是一种写循环却不承认的方式吗? - Kaddath
@Kaddath:关键是,如果你把一块过程式代码包装在reduce里,这并不能使你的程序“函数式”。 - georg
好的,我会记住的。这完全有助于我对FP中众多“无用”用途的反击。它不仅影响性能和可读性,在我看过的大多数示例中,它甚至没有被正确地功能化! - Kaddath

1
你可以获取条目,筛选并构建新对象。

var object = { 111: { user_id: 111, user_name: "user111", isActive: 0 }, 112: { user_id: 112, user_name: "use112", isActive: 1 }, 113: { user_id: 113, user_name: "use113", isActive: 0 } },
    result = Object.assign(...Object
        .entries(object)
        .filter(({ 1: { isActive } }) => isActive === 0)
        .map(([k, v]) => ({ [k]: v }))
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }


这是否比使用 .filter/.map/.reduce 更好的做法? - Imnotapotato
@RickSanchez,在我看来是的,但对于其他人可能会有所不同。 - Nina Scholz

1
我会使用一个生成器函数来处理可迭代对象:

// make object type iterable

function* objEntries(o) {
  for (let k in o)
    yield [k, o[k]];
}

// generator function that takes an iterable

const itFilter = p => function* (ix) {
  for (const x of ix)
    if (p(x))
      yield x;
};

const obj = {
    111: {
        user_id: 111,
        user_name: "user111",
        isActive: 0
    }, 
    112: {
        user_id: 112,
        user_name: "use112",
        isActive: 1
    }, 
    113: {
        user_id: 113,
        user_name: "use113",
        isActive: 0
    }
};

// exhaust the iterator with a strict evaluated fold

const itFoldStrict = f => acc => ix => {
  let acc_ = acc;

  for (const x of ix)
    acc_ = f(acc_) (x);

  return acc_;
};

const ix = itFilter(([k, o]) => o.isActive === 0)
  (objEntries(obj));

// nothin has happened here due to lazy evaluation

// unleash the effect (of constructing the filtered object)

console.log(
  itFoldStrict(acc => ([k, v]) => (acc[k] = v, acc))
    ({}) (ix));

这种算法的特点是“懒惰”,不会产生中间值。

好主意,不幸的是 OP 想要一个对象返回,而不是一个数组。 - georg
我在这里故意挑剔,但我倾向于认为objValuesitFilter确实是中间值。不过这是一个有趣的解决方案。 - Kaddath
@georg 我不想为OP做所有的工作。无论如何,既然你坚持…… - user5536315
@Kaddath 是的,这些生成器函数会产生中间值,但只是每次一个小的迭代器对象。算法不会建立整个“Array”结构。 - user5536315

0

以下方法也可以正常工作:

var newObj = Object.entries(obj).filter(value => {return value[1].isActive ===0});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接