有人能解释一下这个“双重否定”的技巧吗?

220

我并不是 Javascript 的专家,但我一直在阅读 Mark Pilgrim 的《深入浅出 HTML5》网页,并看到了他提到的一个我想更好地理解的东西。

他说:

最后,您可以使用双重否定技巧将结果强制转换为布尔值(true 或 false)。

function supports_canvas() {
  return !!document.createElement('canvas').getContext;
}

如果有人能够更好地解释一下这个问题,我会非常感激!

9个回答

259

逻辑非运算符 ! 将一个值转换为它的逻辑值相反的布尔值。

第二个 ! 将上一个布尔结果转换回原始逻辑值的布尔表示。

从这份文档 中了解逻辑非运算符:

如果操作数可以转换为 true,则返回 false;否则,返回 true。

因此,如果 getContext 返回一个"假值",那么 !! 将会使它返回布尔值 false。否则,它将返回 true

"假值"包括以下内容:

  • false
  • NaN
  • undefined
  • null
  • ""(空字符串)
  • 0

1
@Grinn:user113716没有列出全部。他忘记了-0。我不是指带零的一元负号,而是其结果,即一个单独的值。例如,您可以通过将-0分配给变量来创建它。 - Marco de Wit
@MarcodeWit:在JavaScript中,它们是相等的值。-0 === 0 // true user113716没有错过与问题相关的任何内容。 - user1106925
1
@squint:首先,user113716试图重现falsey值列表,我仍然认为其中缺少-0。我认为它与问题有关,因为也许有人会认为如果0是false,那么由于负号的存在,-0又变成了true。-0 === 0之所以评估为true,只是因为它被定义为这样,而不是因为它们是相同类型的等值。此外,如果它们真的相等,那么1/0应该给出与1/-0相同的结果。 - Marco de Wit
@MarcodeWit:用户可能会这样想,但没有理由这样认为。这个问题是关于逻辑运算符的,而 0 总是假值。这就是他的答案所陈述的。-0 === 0 表明它们是相同类型的相等值,因为它们是这样定义的。你的除法示例是一个非常特殊的情况,与这个问题或 0 值的99.999% 的用途无关。甚至 .toString() 也以相同的方式表示它们。(-0).toString(); // "0" - user1106925
1
Object.is(0, -0) 返回 false,因此它们不是同一件事情。 - Sebastian Simon

33

Javascript有一套混乱的规则,用于在期望布尔值的上下文中确定何为“真”和“假”。 但是,逻辑非运算符总是生成一个正确的布尔值(常量之一truefalse)。通过链接两个逻辑非运算符,习惯用法!!expression可以生成具有与原始表达式相同truthiness的正确布尔值。

为什么要这样做?因为它使得像您展示的函数更加可预测。如果没有双重否定,它可能会返回undefinedFunction对象或类似于Function对象的某些内容。如果此函数的调用者对返回值进行奇怪的操作,整个代码可能会表现不正常(这里的“奇怪”意味着“除了强制布尔值上下文之外的任何操作”)。双重否定习惯用法可以防止这种情况发生。


4
这并不是一组“令人困惑”的规则。 - Chris
11
列表(如上所示)既不是最短的(false,任何其他情况都需要显式比较运算符),也不是最长的(至少要添加{}[])。因此,你必须记住列表而不是规则。这就是我所说的令人困惑的语言特性。 - zwol
当然:这是一个列表,而不是规则。我认为它是否令人困惑高度主观。个人而言,当转换为布尔值时,我发现知道什么是“falsey”和什么是“truthy”非常直观。 - Chris
无论如何,我并不是要激烈反对。 - Chris

14
在 JavaScript 中,使用 "bang" 运算符(!)将返回 true,如果给定的值为 true、1、非 null 等。如果该值为 undefined、null、0 或空字符串,则它将返回 false。
因此,bang 运算符始终返回布尔值,但它表示与初始值相反的值。如果你取该操作的结果并再次 "bang" 它,你可以再次翻转它,但仍然得到一个布尔值(而不是 undefined、null 等)。
两次使用 bang 将采用一个可能是 undefined、null 等的值,并使其成为普通的 false 值。它将采用可能是 1、"true" 等的值,并使其成为普通的 true 值。
代码也可以这样写:
var context = document.createElement('canvas').getContext;
var contextDoesNotExist = !context;
var contextExists = !contextDoesNotExist;
return contextExists;

11
使用 !!variable 可以保证将变量类型转换为布尔类型。
举个简单的例子:
"" == false (is true)
"" === false (is false)

!!"" == false (is true)
!!"" === false (is true)

但是如果你在做类似于以下的事情,使用它就没有意义:

var a = ""; // or a = null; or a = undefined ...
if(!!a){
...

如果条件语句会将其转换为布尔值,因此无需进行隐式的双重否定转换。


7

! 将 "something"/"anything" 转换为 boolean 类型。

!! 返回原始的 boolean 值(并确保表达式现在是一个 boolean,而不管之前是什么类型)。


6

第一个!将变量强制转换为布尔类型并反转它。第二个!再次反转它(给你检查的原始(正确)布尔值)。

为了清晰起见,最好使用

return Boolean(....);

5
Boolean() 创建了一个装箱后的布尔值,它的行为与通过 !! 创建的原始布尔值不同(例如,typeof 将报告 "object")。因此,推荐使用 !! - zwol
1
将其返回原始布尔值:return (new Boolean(...)).valueOf() - serg
4
对我来说没有用,只有与 new 结合时才会起作用。 - alex
@alex 我可能过时了。当时JS解释器远没有现在成熟,这种做法可以正常运作,而且现在的行为可能与浏览器有关。 - zwol
1
我知道这是一个有些陈旧的帖子,但我认为指出双叹号(!!)比Boolean(val)的性能更高是很重要的。http://jsperf.com/bang-bang-vs-boolean - Mad Man Moon
3
如果有人在研究使用双感叹号(!!)方法和布尔值(val)方法的性能时遇到了这个非常老的帖子,可以参考一下我之前评论中的内容,但现在看起来Mad Man Moon之前的评论已经不再正确了。根据jsperf.com/bang-bang-vs-boolean的测试结果,在Chrome和Firefox浏览器中,使用布尔值(val)方法的性能更快。 - twalters

4

document.createElement('canvas').getContext可能会评估为undefined或对象引用。 !undefined产生true![some_object] 产生false。这几乎是我们需要的,只是相反。因此,!!用于将undefined转换为false,将对象引用转换为true


3

这与JavaScript的弱类型有关。 document.createElement('canvas').getContext是一个函数对象。通过在前面加上单个,它将其作为布尔表达式进行评估并翻转答案。再次添加,它会将答案翻转回来。最终结果是该函数将其作为布尔表达式进行评估,但返回的实际上是布尔结果,而不是函数对象本身。在表达式转换为布尔类型时,快速脏方式是在前面添加!!


2
如果document.createElement('canvas').getContext不是undefinednull,它将返回true。否则,它将返回false

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