算术运算符
加法运算符
- 基本规则
1 + 1 // 2 true + true // 2 1 + true // 2 'a' + 'bc' // "abc" 1 + 'a' // "1a" false + 'a' // "falsea" '3' + 4 + 5 // "345" 3 + 4 + '5' // "75"
- 对象相加,如果运算子是对象,必须先转成原始类型的值,然后再相加(先调用 valueOf,再调用 toString);
var obj = { p: 1 }; obj + 2 // "[object Object]2"
- 对象相加,如果运算子是 Date 对象,并且自定义了 valueOf 方法和 toString 方法,结果 toString 方法优先执行;
var obj = new Date(); obj.valueOf = function () { return 1 }; obj.toString = function () { return 'hello' }; obj + 2 // "hello2"
余数运算符
-
余数运算符返回前一个运算子被后一个运算子除,所得的余数;
12 % 5 // 2
-
需要注意的是,运算结果的正负号由第一个运算子的正负号决定;
-1 % 2 // -1 1 % -2 // 1
-
所以,为了得到负数的正确余数值,可以先使用绝对值函数;
function isOdd(n) { return Math.abs(n % 2) === 1; } isOdd(-5) // true isOdd(-4) // false
-
余数运算符还可以用于浮点数的运算,但是,由于浮点数不是精确的值,无法得到完全准确的结果;
6.5 % 2.1 // 0.19999999999999973
自增和自减运算符
-
自增和自减运算符,是一元运算符,只需要一个运算子,它们的作用是将运算子首先转为数值,然后加上1或者减去1,它们会修改原始变量;
var x = 1; ++x // 2 x // 2 --x // 1 x // 1
-
运算之后,变量的值发生变化,这种效应叫做运算的副作用(side effect),自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值;
-
自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值;
数值运算符,负数值运算符
-
数值运算符同样使用加号,但它是一元运算符(只需要一个操作数),而加法运算符是二元运算符(需要两个操作数);
-
数值运算符的作用在于可以将任何值转为数值(与 Number 函数的作用相同);
+true // 1 +[] // 0 +{} // NaN
-
负数值运算符,也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符;
var x = 1; -x // -1 -(-x) // 1
指数运算符
-
指数运算符完成指数运算,前一个运算子是底数,后一个运算子是指数;
2 ** 4 // 16
-
注意,指数运算符是右结合,而不是左结合(即多个指数运算符连用时,先进行最右边的计算);
// 相当于 2 ** (3 ** 2) 2 ** 3 ** 2 // 512
赋值运算符
-
赋值运算符用于给变量赋值,最常见的赋值运算符;
// 将 1 赋值给变量 x var x = 1; // 将变量 y 的值赋值给变量 x var x = y;
-
赋值运算符还可以与其他运算符结合,形成变体;
// 等同于 x = x + y x += y // 等同于 x = x - y x -= y // 等同于 x = x * y x *= y // 等同于 x = x / y x /= y // 等同于 x = x % y x %= y // 等同于 x = x ** y x **= y
-
下面是与位运算符的结合;
// 等同于 x = x >> y x >>= y // 等同于 x = x << y x <<= y // 等同于 x = x >>> y x >>>= y // 等同于 x = x & y x &= y // 等同于 x = x | y x |= y // 等同于 x = x ^ y x ^= y
比较运算符
非相等运算符:字符串的比较
-
字符串按照字典顺序进行比较;
'cat' > 'dog' // false 'cat' > 'catalog' // false
-
JavaScript 引擎内部首先比较首字符的 Unicode 码点,如果相等,再比较第二个字符的 Unicode 码点,以此类推;
'cat' > 'Cat' // true'
-
由于所有字符都有 Unicode 码点,因此汉字也可以比较;
'大' > '小' // false
非相等运算符:非字符串的比较
-
如果两个运算子都是 原始类型的值 ,则是先转成数值再比较;
5 > '4' // true // 等同于 5 > Number('4') // 即 5 > 4 true > false // true // 等同于 Number(true) > Number(false) // 即 1 > 0 2 > true // true // 等同于 2 > Number(true) // 即 2 > 1
-
任何值(包括 NaN 本身)与 NaN 使用非相等运算符进行比较,返回的都是 false;
1 > NaN // false 1 <= NaN // false '1' > NaN // false '1' <= NaN // false NaN > NaN // false NaN <= NaN // false
-
如果运算子是对象,会转为原始类型的值,再进行比较,算法是先调用 valueOf 方法;如果返回的还是对象,再接着调用 toString 方法;
var x = [2]; x > '11' // true // 等同于 [2].valueOf().toString() > '11' // 即 '2' > '11' x.valueOf = function () { return '1' }; x > '11' // false // 等同于 [2].valueOf() > '11' // 即 '1' > '11'
-
两个对象之间的比较也是如此;
[2] > [1] // true // 等同于 [2].valueOf().toString() > [1].valueOf().toString() // 即 '2' > '1' [2] > [11] // true // 等同于 [2].valueOf().toString() > [11].valueOf().toString() // 即 '2' > '11' { x: 2 } >= { x: 1 } // true // 等同于 { x: 2 }.valueOf().toString() >= { x: 1 }.valueOf().toString() // 即 '[object Object]' >= '[object Object]'
严格相等运算符
-
严格相等运算符比较它们是否为 同一个值
-
不同类型的值,直接返回 false
1 === "1" // false true === "true" // false
-
同一类的原始类型值(数值、字符串、布尔值)比较时,值相同就返回 true ,值不同就返回 false;
1 === 0x1 // true
-
需要注意的是, NaN 与任何值都不相等(包括自身),另外,正 0 等于负 0;
NaN === NaN // false +0 === -0 // true
-
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址;
{} === {} // false [] === [] // false (function () {} === function () {}) // false
-
undefined 和 null 与自身严格相等;
undefined === undefined // true null === null // true var v1; var v2; v1 === v2 // true
相等运算符
-
相等运算符比较两个值是否 相等
-
原始类型的值会转换成数值再进行比较;
1 == true // true // 等同于 1 === Number(true) 0 == false // true // 等同于 0 === Number(false) 2 == true // false // 等同于 2 === Number(true) 2 == false // false // 等同于 2 === Number(false) 'true' == true // false // 等同于 Number('true') === Number(true) // 等同于 NaN === 1 '' == 0 // true // 等同于 Number('') === 0 // 等同于 0 === 0 '' == false // true // 等同于 Number('') === Number(false) // 等同于 0 === 0 '1' == true // true // 等同于 Number('1') === Number(true) // 等同于 1 === 1 '\n 123 \t' == 123 // true // 因为字符串转为数字时,省略前置和后置的空格
-
对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较,算法是先调用 valueOf 方法;如果返回的还是对象,再接着调用 toString 方法;
// 数组与数值的比较 [1] == 1 // true // 数组与字符串的比较 [1] == '1' // true [1, 2] == '1,2' // true // 对象与布尔值的比较 [1] == true // true [2] == true // false // 两个对象比较引用地址一定是 false {} == {} // false
-
undefined 和 null 只有与自身比较,或者互相比较时,才会返回 true;与其他类型的值比较时,结果都为 false;
undefined == undefined // true null == null // true undefined == null // true false == null // false false == undefined // false 0 == null // false 0 == undefined // false
-
相等运算符隐藏的类型转换,会带来一些违反直觉的结果;
0 == '' // true 0 == '0' // true 2 == true // false 2 == false // false false == 'false' // false false == '0' // true false == undefined // false false == null // false null == undefined // true ' \t\r\n ' == 0 // true
位运算符
按位非
-
按位非运算符会把数字转为 32 位二进制整数,然后反转每一位,按位非,实质上是对操作数求负,然后减去 1
-
例如:
// 5 的 32 位为 00000000000000000000000000000101 // ~5 的 32 位为:-6 11111111111111111111111111111010
按位与
-
按位或运算符会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位与运算,按位与的规则如下表:
第一个数字 第二个数字 结果 1 1 1 1 0 0 0 1 0 0 0 0 -
具体示例:
// 12 的 32 位二进制表示为:1100 // 10 的 32 位二进制表示为:1010 console.log(12 & 10); // 8
按位或
-
按位或运算符会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位或运算,按位或的规则如下表:
第一个数字 第二个数字 结果 1 1 1 1 0 1 0 1 1 0 0 0 -
具体示例:
// 12 的 32 位二进制表示为:1100 // 10 的 32 位二进制表示为:1010 // 按位或的结果为:1110 console.log(12 | 10); // 14
按位异或
-
按位或运算符会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位异或运算,运算规则为两位不同返回 1,两位相同返回 0,如下表:
第一个数字 第二个数字 结果 1 1 0 1 0 1 0 1 1 0 0 0 -
具体示例:
// 12 的 32 位二进制表示为:1100 // 10 的 32 位二进制表示为:1010 // 按位异或的结果为:0110 console.log(12 ^ 10); // 6
-
按位异或如果是非整数值,如果两个操作数中只有一个为真,就返回 1,如果两个操作数都是真,或者都是假,就返回 0,示例如下:
console.log(true ^ "Hello"); // 1 console.log(false ^ "Hello"); // 0 console.log(true ^ true); // 0 console.log("Hello" ^ "Hello"); // 0 console.log(false ^ false); // 0 console.log(true ^ false); // 1
按位移位
-
按位移位运算符 << 和 >> 会将所有位向左或者向右移动指定的数量,实际上就是高效率地将数字乘以或者除以 2 的指定数的次方;
-
具体示例:
// `<<`:乘以 2 的指定数次方 console.log(2<<2); // 8 // `>>`:除以 2 的指定数次方 console.log(16>>1); // 8
其他运算符
void 运算符
-
void 运算符的作用是执行一个表达式,然后不返回任何值,或者说返回 undefined;
var x = 3; void (x = 5) //undefined x // 5
-
下面是 void 运算符的两种写法都正确,建议采用后一种形式,即总是使用圆括号,因为 void 运算符的优先性很高,如果不使用括号,容易造成错误的结果,比如 “void 4 + 7” 实际上等同于 “(void 4) + 7”;
void 0 // undefined void(0) // undefined
逗号运算符
-
逗号运算符用于对两个表达式求值,并返回后一个表达式的值;
'a', 'b' // "b" var x = 0; var y = (x++, 10); x // 1 y // 10
-
逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作;
var value = (console.log('Hi!'), true); // Hi! value // true
运算顺序
优先级
-
JavaScript 各种运算符的优先级别是不一样的,优先级高的运算符先执行,优先级低的运算符后执行;
4 + 5 * 6 // 34
-
如果多个运算符混写在一起,常常会导致令人困惑的代码,记住所有运算符的优先级,是非常难的,也是没有必要的;
var x = 1; var arr = []; // 这五个运算符的优先级从高到低依次为:小于等于( <= )、严格相等( === )、或( || )、三元( ?: )、等号( = ) var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0]; // 等价 var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];
圆括号的作用
-
圆括号可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算;
-
运算符的优先级别十分繁杂,且都是硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要;
面试题
a 等于什么值会让「a == 1 && a == 2 && a == 3 」条件成立;
// 方式一:数值和对象比较,先执行 valueOf,如果还是对象则再执行 toString
var a = {
i: 1,
toString() {
return a.i++;
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
// 方式二:数值和对象比较,先执行 valueOf,如果还是对象则再执行 toString
var a = {
i: 1,
valueOf() {
return a.i++;
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
// 方式三:
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
// 方式四:
var a = [1, 2, 3];
a.valueOf = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
// 方式五:
var i = 0;
Object.defineProperty(window, 'a', {
get() {
// 获取 window.a 的时候触发
return ++i;
},
set() {
// 给 window.a 设置属性值的时候触发
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
[] == ![] 的结果是 true 吗
console.log([] == ![]); // true
// 1. 单目运算符优先级更高,则 ![] => fasle,[] == false
// 2. []先执行 valueOf,若返回值为对象继续执行 toString
// [].valueOf() = [],[] == false
// [].toString() = '','' == false
// 3. '' == false
// Number('') => 0
// false => 0
// 4. 0 == 0 => true
第 1️⃣ 座大山:数据类型的转换
上一篇