JavaScript 否定谓语函数

原文:Negating Predicate Functions in JavaScript
翻译:涂鸦码龙

如果你从没听过这个概念,那么至少用过谓语函数。谓语本质上是一个函数,根据它的参数,可以判断返回结果是 true 或者 false。一般都以“isX”命名,比如 isEven 或者 isNumber

假如我们有个程序,需要处理漫画书里的英雄和坏蛋,以下面的简单对象为例:

1
2
3
4
5
var superman = {
name: "Superman",
strength: "Super",
heroism: true
};

程序中难免会用到一些谓语,像这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function isSuperStrong (character) {
return character.strength === "Super";
}
function isNotSuperStrong (character) {
return character.strength !== "Super";
}
function isHeroic (character) {
return character.heroism === true;
}
function isNotHeroic (character) {
return character.heroism !== true;
}
// Outputs: false
console.log(isNotSuperStrong(superman));
// Outputs: false
console.log(isNotHeroic(superman));

如你所见,代码有些冗余。问题不是代码太长,而是核心逻辑定义了两次(每一对谓语,“is”和“isNot”)。逻辑重复意味着逻辑改变时,可能只更新了其中一处谓语,导致错误发生。

如何解决呢?我们首先想到的是这样改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function isSuperStrong (character) {
return character.strength === "Super";
}
function isNotSuperStrong (character) {
return !isSuperStrong(character);
}
function isHeroic (character) {
return character.heroism === true;
}
function isNotHeroic (character) {
return !isHeroic(character);
}
// Outputs: false
console.log(isNotSuperStrong(superman));
// Outputs: false
console.log(isNotHeroic(superman));

的确有进步,但是仍有冗余。“isNot”谓语只不过颠倒了“is”谓语的功能。

我们何不抽象成一个更清晰,更易维护的函数呢?

1
2
3
4
5
function negate (predicateFunc) {
return function () {
return !predicateFunc.apply(this, arguments);
};
}

negate 函数接收一个谓语函数作为参数,返回一个函数用来对之前谓语的功能取反。

(如果对 apply 不了解,可以读读这篇《Invoking JavaScript Functions With ‘call’ and ‘apply’》)

使用 negate 函数解决我们先前的问题吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isSuperStrong (character) {
return character.strength === "Super";
}
var isNotSuperStrong = negate(isSuperStrong);
function isHeroic (character) {
return character.heroism === true;
}
var isNotHeroic = negate(isHeroic);
// Outputs: false
console.log(isNotSuperStrong(superman));
// Outputs: false
console.log(isNotHeroic(superman));

程序如期运行,但是我们把核心逻辑放到了一处,从“is”谓语也很容易推导出“isNot”谓语。

这点小小的重构看起来微不足道,但是应用于复杂系统的许多分散函数时,negate 函数可以使程序更易维护。

UnderscoreLo-Dash 都已经填加了 negate 函数。