JavaScript中的“黑话”
因为球是圆的,所以不论发生什么都有可能,对这点我是深信不疑的,但最近我总是在怀疑,JavaScript也是圆的!
什么是“黑话”
黑话,本指旧时江湖帮会人物的暗语、暗号,往往见于小说,后指流行于某一特殊行业中,非局外人所能了解的语言。而本文涉及到的“黑话”,其实是一些利用语言的特征使用的一些不常见的奇淫技巧,JavaScript的语法是十分简单灵活的,在项目中建议大家遵从ESLint规范编写可维护性的代码,各路神仙们也应该进行自我约束,毕竟“黑话”也并不全是什么好的东西,如果很多话可以直接讲,何必拐弯抹角的去说呢?
“算术”
算术中的位运算已被作者列为禁术,因此希望你在工程 中使用位运算时,请确保你有充足的理由使用,并在需要时写好Hack注释。
以下知识点会涉及一些原码、反码与补码的概念,请参考文章原码、反码与补码的产生。
!与!!
!
为逻辑非操作符,可以应用于ECMAScript中的任何值,无论这个值是什么类型,它会被强制转化为一个布尔值变量,再对其值取反。
!!
只是单纯的将操作数执行两次逻辑非,它能将任意类型的值转化为相应的布尔值,它包含的步骤为:
- 将一个值转化为布尔值;
- 将其取反;
- 再次取反。
假设你需要通过一个布尔型变量表示是否有id值,以下写法推荐你使用最后一种方式来进行转化:
const enable1 = !!id;
const enable2 = id ? true : false;
const enable3 = Boolean(id);
~ 与 ~~
~
表示按位取反,~5
的运行步骤为:
- 转为一个字节的二进制表示:00000101,
- 按位取反:11111010
- 取其反码:10000101
- 取其补码:10000110
- 转化为十进制:-6
~~
它代表双非按位取反运算符,如果你想使用比Math.floor()更快的方法,那就是它了。需要注意,对于正数,它向下取整;对于负数,向上取整;非数字取值为0,它具体的表现形式为:
~~null; // => 0
~~undefined; // => 0
~~Infinity; // => 0
--NaN; // => 0
~~0; // => 0
~~{}; // => 0
~~[]; // => 0
~~(1/0); // => 0
~~false; // => 0
~~true; // => 1
~~1.9; // => 1
~~-1.9; // => -1
+
在变量值前使用+的本意是将变量转换为数字,在一个函数接受数字类型的参数时特别有用:
+'1' // 1
+'-1' // '-1
+[] // 0
+{} // NaN
根据观察,+a
与a * 1
结果类似。除此之外,使用+也可以作为立即执行函数:+function() {}()
,等效于(function(){})()
。
字符串与数字相加时会将数值默认转为字符串,因此有了一下将数字转为字符串的快捷方法:'' + 1
。
& 与 &&
如何你是从类C语言过来的话,请抛弃之前的刻板印象:&可以充当逻辑操作符号。在JavaScript中,&只能进行位运算。
&
,它表示按位与,此运算符需要两个数字并返回一个数字。如果它们不是数字,则会转换为数字。如果执行7 & 3
, 则会经过以下步骤:
- 先转换为2进制:
111 & 11
- 比较结果为:
011
- 将二进制转回十进制,因此:
7 & 3 = 3
它也可用于基偶数判断:const isOdd = num => !!(num & 1);
&&
,表示逻辑与,通常用于if条件判断,可跟你想象的不太一样,&&并不是单纯的返回true或者false,而是依据:
- 若第一个表达式为false,则返回第一个表达式;
- 若第一个表达式为true,返回第二个表达式。 在这里举几个例子:
0 && false 0 (both are false-y, but 0 is the first)
true && false false (second one is false-y)
true && true true (both are true-y)
true && 20 20 (both are true-y)
&&
可以连接多个操作符,如:a && b && c && d
,返回值的规则与上面一样。除此以 外,它还经常被作为短路逻辑使用:若前面表达式不是truthy,则不会继续执行之后的表达式。如在取一个对象的属性,我们需要先判断是否为空才能进行取值,否则会抛出Uncaught TypeError,这种情况下一般我们也会通过逻辑或,给与表达式一个默认值:
const value = obj && obj.value || false
当JavaScript压缩工具遇到if判断时,也会使用&&短路逻辑从而节省内存空间:
// before
if (test) { alert('hello') }
// after
test && alert('hello')
| 与 ||
它们与&
和&&
使用方法很相似,不同的是它们表示的是逻辑或,因此使用|
会进行按位或运算,而||
会返回第一个Truthy值。
使用||进行默认值赋值在JavaScript中十分常见,这样可以省略很多不必要的if语句,比如:
// before
let res;
if (a) {
res = a;
} else if (b) {
res = b;
} else if (c) {
res = c;
} else {
res = 1;
}
// after
const res = a || b || c || 1;
== 与 ===
==
为相等运算符,操作符会先将左右两边的操作数强制转型,转换为相同的操作数,再进行相等性比较 。
===
为全等运算符,它除了在比较时不会将操作数强制转型,其余相等判断与==
一致。
简单而言,==
用于判断值是否相等,===
判断值与类型是否都相等,因此使用全等运算符判断操作数会更准确,新手也在学习JavaScript接收到的前几条Tips就是避免使用相等运算符,真的是这样吗?没错,这样能确保在你不彻底熟悉语言的情况下,尽可能的去避免犯错,但是我们也应该清楚在哪些情况下应该使用相等运算符,规则往往只针对于新手,而对聪明的你来说,最重要的是要清楚自己在做什么。
相等操作符对于不同类型的值,进行的比较如下图所示:
B | |||||||
---|---|---|---|---|---|---|---|
Undefined | Null | Number | String | Boolean | Object | ||
A | Undefined | true | true | false | false | false | IsFalsy(B) |
Null | true | true | false | false | false | IsFalsy(B) | |
Number | false | false | A === B | A === ToNumber(B) | A=== ToNumber(B) | A=== ToPrimitive(B) | |
String | false | false | ToNumber(A) === B | A === B | ToNumber(A) === ToNumber(B) | ToPrimitive(B) == A | |
Boolean | false | false | ToNumber(A) === B | ToNumber(A) === ToNumber(B) | A === B | ToNumber(A) == ToPrimitive(B) | |
Object | false | false | ToPrimitive(A) == B | ToPrimitive(A) == B | ToPrimitive(A) == ToNumber(B) |
A === B
|
针对于undefined与null:undefined与null互等,与其余任意对象都不相等,因此在某些lib里,你可能会看到如下写法:
if (VAR == undefined) {}
if (VAR == null) {}
它等效于:
if (VAR === undefined || VAR === null) {}
对于 '', false, 0
而言,他们都属于Falsy类型 ,通过Boolean对象都会转换为假值,而通过==
判断三者的关系,他们总是相等的,因为在比较值时它们会因为类型不同而都被转换为false值:
console.log((false == 0) && (0 == '') && ('' == false)) // true
或者有时候我们希望利用强转特性比较字符串与数字:
console.log(11 == '11') // true
console.log(11 === '11') // false