JS 运算符
算数运算符
概述
- 加减乘除:
+ - * / - 自增自减
++ -- - 指数运算符:
x ** y - 取余运算符:
%
特殊地
加法重载
JS的相加运算在遇到字符串时会发生重载,变为字符串拼接。其他的- * / 都是将字符串转数值,然后再运算
// 非数字转数字后,再计算
console.log(true + true === 1 + true, 1 + true === 2, false + 2 === 2) // true, true, true
// 遇到字符串时,发生加法重载
console.log('3' + 4 + 5, 3 + '4' + 5, 3 + 4 + '5') // 345 345 75
console.log(true + '1', false + '1') // 'true1' 'false1'
// 其他的减乘除运算都不会发生重载,都是转为数值,再计算
console.log(true - true === 0, true - false === 1, true * 2 - 3 === -1) // true, true, true
console.log('3' - 4 - 5, 3 - '4' - 5, 3 - 4 - 5) // -6 -6 -6
console.log('123' - 2 === 121, '62' % 4 === 2) // true, true对象类型的运算
{a: 1} + 1 = ?
对象会先把自己转为原始类型的值,通过先valueOf()拿到返回值(通常是自身),如果不是原始值,就执行toString(),然后如果是加法的话,自然会发生加法重载。
valueOf() 和 toString() 是 Object.prototype 上的方法, 可通过重写对象的valueOf方法,实现自定义类型转换逻辑。
console.log({ a: 1 } - 1) // NaN
console.log({ a: 1 } + 1) // [object Object]1
const obj = { a: 1 }
// 同理,重写toString也能实现同样的效果,
// 如果都重写了的话,先看valueOf的结果是否是原始类型,如果是那就拿去进行后序计算,如果不是,再调用toString
obj.valueOf = function(){
return 100
}
console.log(obj - 1) // 99
console.log(obj + 1) // 101取余的结果正负
当取余运算涉及负数时,结果取决于第一个数字的正负性。(吐槽一下,真有人?会对负数取余吗??)
console.log(-1 % 2) // -1
console.log(1 % -2) // 1
// 避免正负数的影响,余数应该总是一个整数
function isOdd(n) {
return Math.abs(n % 2) === 1;
}指数运算符是右结合
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512比较运算符
概述
>大于<小于<=小于或等于符>=大于或等于符==相等===严格相等符!=不相等!==严格不相等
特殊地
字符串逐字符按照字典序(Unicode码点)比较
console.log('cat' > 'dog') // false
console.log('cat' > 'catalog') // false
console.log('cat' > 'Cat') // true'
console.log('大' > '小') // false原始类型转为数值再比较
console.log(5 > '4') // true
console.log(true > false )// true
console.log(2 > true) // true对象先 valueOf 再 toString
与前一节同理, 如有特殊需要,可重写方法
NaN与任何值比较都返回false
console.log(1 > NaN ) // false
console.log(1 <= NaN ) // false
console.log('1' > NaN ) // false
console.log('1' <= NaN ) // false
console.log(NaN > NaN ) // false
console.log(NaN <= NaN ) // false严格相等与相等
==比较两个值经过转换后是否相等,===比较两个是否为"同一个"值
严格相等
不同类型的值,直接false
同一类型的原始值,比较其值是否相等,NaN与任何值(包括自身)都不相等
对象类型,比较两个是否指向同一地址
undefined 和 null 与自身严格相等
普通相等
原始类型,转换为数值再比较
注意:字符串转数字时,只有纯数字,或纯数字+转义字符,才能被转为正确数字
console.log(Number('123')) // 123
console.log(Number(' \t 123 \n')) // 123
console.log(Number(' \t 1 23 \n')) // NaN
console.log(Number('$123')) // NaN
console.log(Number('123$')) // NaN
console.log(Number('hello')) // NaN对象类型,转为原始类型再比较
- 通常都是转为数值再比较
- 如果遇到字符串,比较字符串
注意:单个元素的数组可转为单个数字
console.log(Number([1]) == 1) // truenull与undefined
- 与其他类型比较时,总是返回false
- 他俩比较时,返回true
- 如果强行转数字,那么null会是0, undefine会是NaN
// 与其他类型比较时,总是返回false
console.log(null == 0, undefined == 0) // false, false
console.log(null == false, undefined == false) // false, false
// 他俩比较时,返回true
console.log(undefined == null, undefined === null) // true, false
// 如果强行转数字,那么null会是0, undefine会是NaN
console.log(Number(null), Number(undefined)) // 0, NaNfalse, 0 与空字符串
// 不同的两个原始类型转为数字比较
console.log(0 == false, 1 == true) // true, true
console.log(0 == '', 0 == '0') // true, true
console.log(false == '', false == '0') // true, true
console.log(false == 'false', Number(false), Number('false')) // false 0 NaN
// 同类型的直接比较,字符串的比较规则是逐字符比较
console.log('' == '0') // 所以false
console.log(' \t\r\n ' == 0, ' \t\r\n ' == false, ' \t\r\n ' == '') // true, true, false布尔运算符
概述
- 取反:!
- 且:&&
- 或:||
- 三元运算符:? :
特殊的
六个取反为true的
以下几个取反为true,其他都是false
- undefined
- null
- false
- 0
- NaN
- ''
console.log(!'') // true
console.log(!' /t /d ', !' \t\r\n ') // false, false
console.log(!{}, ![]) // false, false&&运算,当第一个为false时直接短路
||运算,当第一个为true时直接短路
位运算符
关于二进制位
正数不用说,二进制1对应十进制1, 二进制11对应十进制3,二进制101对应十进制5
补码表示法
补码表示法在计算机底层的标准实现,补码保证了+0和-0具备同一的表示,且使得加法能算减法 正数的补码 = 本身,负数的补码 = 对应正数的补码取反 + 1
下面以八位有符号整数进行举例:
| 十进制数字 | 二进制补码 | | ......... | ......... | | 1 | 00000001 | | -1 | 11111111 | | 2 | 00000010 | | -2 | 11111110 | | 3 | 00000011 | | -3 | 11111101 |
如果是八位无符整数,那11111111就是 255 了,但是现在却变成了 -1 ?
总结分析:
因为补码表示法的存在,负数在正数的基础上取反加一,这个加一导致了进位,所以:以 0 作为区分,最终正数总是表现为首位是 0 ,负数的首位总是 1 ,这就使得最大能表示正整数是
01111111 => 127, 同样的,最大能表示的负整数就是-127。所以在有符二进制数里,首位通常被成为符号位。同时, 这也提醒我们相同的 bit 位,用不同的解释方式得到的结果也不同,如果是 UInt8 ,那最大就是255,如果是 Int8 ,那最大就是 127 ,这其实也就是 ES6+ 中
TypedArray的机理,即:同一个arrayBuffer,用不同的arrayView,往往可以解析得到不同的数值。
概述
位运算直接处理每一个位(bit),速度快,但不太直观。位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。
- 二进制或(or):符号为
|,表示若两个二进制位都为0,则结果为0,否则为1, 有一个1就是1。 - 二进制与(and):符号为
&,表示若两个二进制位都为1,则结果为1,否则为0, 有一个0就是0。 - 二进制否(not):符号为
~,表示对一个二进制位取反。 - 异或(xor):符号为
^,表示若两个二进制位不相同则为1,相同为0。 - 左移(left shift):符号为
<<,整个二进制数位向右移,右移一位相当于十进制数/ 2 - 右移符(right shift):符号为
>>,整个二进制数位向左移,左移一位相当于十进制数* 2 - 头部补零的右移(zero filled right shift):符号为
>>>。
应用
快速取整
在 JS 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数, 基于此可以实现一个快捷的靠0取整方法。相当于直接抛弃小数部分,但是有限制,只能处理 2^32 - 1 量级的数
console.log(2.9 | 0, Math.floor(2.9)) // 2, 2
console.log(-2.9 | 0, Math.floor(-2.9)) // -2 , -3快速开关
其实就是跳过了比较时的数据类型转换,利用底层的位运算来进行直接比较,并且以位为单位,通过一些位运算来获取信息,以此实现极致的优化。
// define
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
// runtime
var flags = 5; // 二进制的0101, 表示 C 开了, A开了只出现一次的数
leetcode 136. 只出现一次的数字
- 描述:给一个nums,其中元素要么出现一次,要么两次,找出只出现一次的数字
- 实现:不用哈希表,从0开始,遍历字符,并异或运算,最后剩下的就是只出现一次的数字
- 0 ^ 123 = 123
- 123 ^ 123 = 0
其他运算符
void 运算符
void 运算符的作用是执行一个表达式,忽视返回值(返回 undefined),因为优先级比较高,建议带上括号。
var x = 3;
console.log(void (x = 5)) //undefined
console.log(x) // 5感觉应用场景并不多,早期的时候,用来简化组织页面跳转
<a href="javascript: void(submit())">文字</a>逗号运算符
逗号运算符总是返回最后一个表达式的值
var name = 'bob'
var foo = (console.log('Hi!'), name = 'jk', 'byebye !'); // 'Hi!'
console.log(name) // jk
console.log(foo) // byebye !