JavaScript运算符

(“整理自网道项目”)

算数运算符

1. 概述

JavaScript 共提供10个算术运算符

  • 加法运算符:x + y
  • 减法运算符:x - y
  • 乘法运算符:x * y
  • 除法运算符:x / y
  • 指数运算符:x ** y
  • 余数运算符:x % y
  • 自增运算符:++x 或者 x++
  • 自减运算符:–x 或者 x–
  • 数值运算符:+x
  • 负数值运算符:-x

2. 加法运算符

2.1 基本规则

加法运算符(二元运算符)是在运行时决定执行相加,还是执行连接。运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)

true + false // 1 加法运算时,boolean转换为number,true为1,false为0
true + 1 // 2 
true + "a" // "truea"
1 + "1" // "11"

字符串的位置不同会导致不同的结果

1 + 2 + "1" // "31"
"1" + 1 + 2 // "112"

除了加法运算符,其他算术运算符都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算

1 - "2" // -1
1 * "2" // 2
1 / "2" // 0.5

2.2 对象的相加

如果运算子是对象,必须先转成原始类型的值,然后再相加

let obj = { p: 1};
obj + 2 // "[object Object]2"

上面代码中,对象obj转成原始类型的值是[object Object],再加2就得到了上面的结果。如下:

// 首先,对象转成原始类型的值,会自动调用对象的 valueOf方法
// 对象的valueOf方法总是返回对象自身
obj.valueOf() // { p: 1 } 
// 然后自动调用对象的toString方法,将其转为字符串
obj.valueOf().toString() // "[object Object]"
// 最后进行加法运算
"[object Object]" + 2 // "[object Object]2"

可以自己定义valueOf方法或toString方法,得到想要的结果

var obj = {
  valueOf: function () {
    return 1;
  }
};
obj + 2 // 3

上面代码中,由于valueOf方法直接返回一个原始类型的值,所以不再调用toString方法

下面是自定义toString方法的例子

var obj = {
  toString: function () {
    return 'hello';
  }
};
obj + 2 // "hello2"

这里有一个特例,如果运算子是一个Date对象的实例,那么会优先执行toString方法

var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"

上面代码中,对象obj是一个Date对象的实例,并且自定义了valueOf方法和toString方法,结果toString方法优先执行

3. 余数运算符

余数运算符(%)返回前一个运算子被后一个运算子除,所得的余数(可以运用在浮点数上,但精度不准确)

12 % 5 // 2

运算结果的正负号由第一个运算子的正负号决定(为了得到负数的正确余数值,可以先使用绝对值函数Math.abs()

-1 % 2 // -1
1 % -2 // 1

4. 自增和自减运算符

自增和自减运算符,是一元运算符。它们的作用是将运算子首先转为数值,然后加上1或者减去1。会修改原始变量

自增和自减运算符放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值

var x = 1;
var y = 1;
x++ // 1
++y // 2

5.数值运算符,负数值运算符

数值运算符(+)是一元运算符

作用在于可以将任何值转为数值(与Number函数的作用相同)

+true // 1
+[] // 0
+{} // NaN

负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符

var x = 1;
-x // -1
-(-x) // 1

数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值

6.指数运算符

指数运算符(**)完成指数运算,**后一个运算子是指数

多个指数运算符连用时,先进行最右边的计算

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2 // 512

7.赋值运算符

赋值运算符(=)用于给变量赋值

复合的赋值运算符先进行指定运算,再将得到的值返回给左边的变量

x += y // 等同于 x = x + y

x -= y // 等同于 x = x - y

比较运算符

1. 概述

比较运算符可以比较任意类型两个值的大小,然后返回一个布尔值,表示是否满足指定的条件

JavaScript 一共提供了8个比较运算符

  • >大于运算符
  • < 小于运算符
  • <= 小于或等于运算符
  • >= 大于或等于运算符
  • == 相等运算符
  • === 严格相等运算符
  • != 不相等运算符
  • !== 严格不相等运算符

2. 字符串的比较

字符串按照字典顺序进行比较

'大' > '小' // false

“大”的 Unicode 码点是22823,“小”是23567,因此返回false

3.非字符串的比较

原始类型值比较

如果两个运算子都是原始类型的值,则是先转成数值再比较

5 > '4' // true
// 等同于 5 > Number('4')

true > false // true
// 等同于 Number(true) > Number(false)

2 > true // true
// 等同于 2 > Number(true)

任何值(包括NaN本身)与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]'

4. 相等运算符

比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较

(1)原始类型值

原始类型的值会转换成数值再进行比较

1 == true // true
// 等同于 1 === Number(true)

'true' == true // false
// 等同于 NaN === 1

'\n  123  \t' == 123 // true
// 因为字符串转为数字时,省略前置和后置的空格

(2)对象与原始类型值比较

对象(包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较

// 数组与字符串的比较
[1, 2] == '1,2' // true

上面例子中,JavaScript 引擎会先对数组[1]调用数组的valueOf()方法,由于返回的还是一个数组,所以会接着调用数组的toString()方法,得到字符串形式,再进行比较

(3)undefined 和 null

undefinednull只有与自身比较,或者互相比较时,才会返回true;与其他类型的值比较时,结果都为false

5. 严格相等运算符

JavaScript 提供两种相等运算符:=====

相等运算符(==)比较两个值是否相等,严格相等运算符(===)比较是否为“同一个值”

以下为严格相等运算符的算法

(1)不同类型的值

如果两个值的类型不同,直接返回false

1 === "1" // false

(2)同一类的原始类型值

同一类型的原始类型的值比较时,值相同就返回true,值不同就返回false

1 === 0x1 // true

NaN与任何值都不相等(包括自身),正0等于负0

(3)复合类型值

两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址

{} === {} // false
[] === [] // false
(function () {} === function () {}) // false

如果两个变量引用同一个对象,则它们相等

var v1 = {};
var v2 = v1;
v1 === v2 // true

注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值

(4)undefined 和 null

undefinednull与自身严格相等,所有两个只声明未赋值的变量也是相等的

undefined === undefined // true
null === null // true

6. 不相等运算符和严格不相等运算符

相等运算符有一个对应的“不相等运算符”(!=),它的算法就是先求相等运算符的结果,然后返回相反值

1 != '1' // false
// 等同于 !(1 == '1')

严格相等运算符有一个对应的“严格不相等运算符”(!==),它的算法就是先求严格相等运算符的结果,然后返回相反值

1 !== '1' // true 
// 等同于 !(1 === '1')

布尔运算符

1. 概述

布尔运算符用于将表达式转为布尔值,一共包含四个运算符

  • 取反运算符!
  • 且运算符&&
  • 或运算符||
  • 三元运算符?:

2. 取反运算符(!)

取反运算符是一个感叹号,用于将布尔值变为相反值,即true变成falsefalse变成true

!true // false
!false // true

对于非布尔值,取反运算符会将其转为布尔值。可以这样记忆,以下六个值取反后为true,其他值都为false

  • undefined
  • null
  • false
  • 0
  • NaN
  • ``空字符串(''

不管什么类型的值,经过取反运算后,都变成了布尔值

如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同。这是一种常用的类型转换的写法

!!x // 等同于 Boolean(x)

3. 且运算符(&&)

且运算符(&&)往往用于多个表达式的求值

它的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值;如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值

true && false // false
false && true // false

var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1

最后这种跳过第二个运算子的机制,也被称为“短路”。可以用它取代if结构,如下

if (i) {
  alert("a");
}
// 等价于 i && alert("a");

运算符可以多个连用,这时返回第一个布尔值为false的表达式的值。如果所有表达式的布尔值都为true,则返回最后一个表达式的值

true && '' && 4 && true // ''

1 && 2 && 3 // 3

4. 或运算符(||)

或运算符(||)也用于多个表达式的求值。

它的运算规则是:如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,则返回第二个运算子的值

true || false // true
false || true // true

短路规则对这个运算符也适用

var x = 1;
true || (x = 2) // true
x // 1

或运算符也可以多个连用,这时返回第一个布尔值为true的表达式的值。如果所有表达式都为false,则返回最后一个表达式的值

false || 0 || '' || 4 || true // 4

false || 0 || '' // ''

或运算符常用于为一个变量设置默认值

function saveText(text) {
  text = text || '';
  // ...
}
// 或者写成
saveText(this.text || '')

上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串

5. 三元条件运算符(?:)

三元条件运算符由问号(?)和冒号(:)组成,分隔三个表达式。它是 JavaScript 语言唯一一个需要三个运算子的运算符。如果第一个表达式的布尔值为true,则返回第二个表达式的值,否则返回第三个表达式的值

true ? "a" : "b" // a
false ? "a" : "b" // b

三元条件表达式与if...else语句具有同样表达效果。最大的差别是if...else是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用if…else

console.log(true ? 'a' : 'b');

二进制位运算符

1.概述

二进制位运算符用于直接对二进制位进行计算,一共有7个

  • 二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1
  • 二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0
  • 二进制否运算符(not):符号为~,表示对一个二进制位取反
  • 异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0
  • 左移运算符(left shift):符号为<<
  • 右移运算符(right shift):符号为>>
  • 头部补零的右移运算符(zero filled right shift):符号为>>>

2. 二进制或运算符

二进制或运算符(|)逐位比较两个运算子,两个二进制位之中只要有一个为1,就返回1,否则返回0

0 | 3 // 3

位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分。所以,将一个小数与0进行二进制或运算,等同于对该数去除小数部分,即取整数位

2.9 | 0 // 2
-2.9 | 0 // -2

不过这种取整方法不适用超过32位整数最大值2147483647的数

2147483649.4 | 0;
// -2147483647

3. 二进制与运算符

二进制与运算符(&)的规则是逐位比较两个运算子,两个二进制位之中只要有一个位为0,就返回0,否则返回1

0 & 3 // 0

4. 二进制否运算符

二进制否运算符(~)将每个二进制位都变为相反值(0变为11变为0)简单理解为:一个数加1取反

~ 3 // -4
~ -3 // 2 

3的32位整数形式是00000000000000000000000000000011,二进制否运算以后得到11111111111111111111111111111100。(由于第一位(符号位)是1,所以这个数是一个负数)

所有的位运算都只对整数有效。二进制否运算遇到小数时,会将小数部分舍去,只保留整数部分。所以,对一个小数连续进行两次二进制否运算,能达到取整效果(使用二进制否运算取整,是所有取整方法中最快的一种)

~~1.9999 // 1

对于其他类型的值,二进制否运算先用Number转为数值,然后再进行处理

~'011'  // -12
// 相当于~Number('011')

~'d' // -1
// 相当于~Number('d')

~[] // -1
// 相当于 ~Number([])

~NaN // -1
// 相当于 ~Number(NaN)

~null // -1
// 相当于 ~Number(null)

5. 异或运算符

异或运算(^)在两个二进制位不同时返回1,相同时返回0

0 ^ 3 // 3
// 0和3每一个二进制位都不同,所以得到11(即3)

“异或运算”有一个特殊运用,连续对两个数ab进行三次异或运算,a^=b; b^=a; a^=b;,可以互换它们的值。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值(这是互换两个变量的值的最快方法)

var a = 10;
var b = 99;
a ^= b, b ^= a, a ^= b;

a // 99
b // 10

异或运算也可以用来取整

12.9 ^ 0 // 12

6. 左移运算符

左移运算符(<<)表示将一个数的二进制值向左移动指定的位数,尾部补0,即乘以2的指定次方。向左移动的时候,最高位的符号位是一起移动的

// 4 的二进制形式为100,
// 左移一位为1000(即十进制的8)
// 相当于乘以2的1次方
4 << 1 // 8
-4 << 1 // -8

上面代码中,-4左移一位得到-8,是因为-4的二进制形式是11111111111111111111111111111100,左移一位后得到11111111111111111111111111111000,该数转为十进制(减去1后取反,再加上负号)即为-8

如果左移0位,就相当于将该数值转为32位整数,等同于取整,对于正数和负数都有效

13.5 << 0 // 13
-13.5 << 0 // 13

左移运算符用于二进制数值非常方便
使用左移运算符,将颜色的 RGB 值转为 HEX 值

var color = {r: 186, g: 218, b: 85};

// RGB to HEX
// (1 << 24)的作用为保证结果是6位数
var rgb2hex = function(r, g, b) {
  return '#' + ((1 << 24) + (r << 16) + (g << 8) + b)
    .toString(16) // 先转成十六进制,然后返回字符串
    .substr(1);   // 去除字符串的最高位,返回后面六个字符串
}

rgb2hex(color.r, color.g, color.b)
// "#bada55"

7. 右移运算符

右移运算符(>>)表示将一个数的二进制值向右移动指定的位数。如果是正数,头部全部补0;如果是负数,头部全部补1。右移运算符基本上相当于除以2的指定次方(最高位即符号位参与移动)

4 >> 1 // 2
// 因为4的二进制形式为 00000000000000000000000000000100,
// 右移一位得到 00000000000000000000000000000010,
// 即为十进制的2

-4 >> 1 // -2
// 因为-4的二进制形式为 11111111111111111111111111111100,
// 右移一位,头部补1,得到 11111111111111111111111111111110,
// 即为十进制的-2

右移运算可以模拟 2 的整除运算

4 >> 1 // 2
// 相当于 4 / 2

8 >> 3 // 1
// 相当于 8 / 8

8. 头部补零的右移运算符

头部补零的右移运算符(>>>)与右移运算符(>>)只有一个差别,就是一个数的二进制形式向右移动时,头部一律补零,而不考虑符号位。所以,该运算总是得到正值。对于正数,该运算的结果与右移运算符(>>)完全一致,区别主要在于负数

4 >>> 1 // 2

-4 >>> 1 // 2147483646
// 因为-4的二进制形式为11111111111111111111111111111100,
// 带符号位的右移一位,得到01111111111111111111111111111110,
// 即为十进制的2147483646

这个运算实际上将一个值转为32位无符号整数

查看一个负整数在计算机内部的储存形式,最快的方法就是使用这个运算符

-1 >>> 0 // 4294967295

上面代码表示,-1作为32位整数时,内部的储存形式使用无符号整数格式解读,值为 4294967295(即(2^32)-1,等于11111111111111111111111111111111

9.开关作用

位运算符可以用作设置对象属性的开关

假定某个对象有四个开关,每个开关都是一个变量。那么,可以设置一个四位的二进制数,它的每个位对应一个开关

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000

上面代码设置 A、B、C、D 四个开关,每个开关分别占有一个二进制位

然后,就可以用二进制与运算,检查当前设置是否打开了指定开关

var flags = 5; // 二进制的0101

if (flags & FLAG_C) {
  // ...
}
// 0101 & 0100 => 0100 => true

上面代码检验是否打开了开关C。如果打开,会返回true,否则返回false

现在假设需要打开ABD三个开关,我们可以构造一个掩码变量

var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011

上面代码对ABD三个变量进行二进制或运算,得到掩码值为二进制的1011

有了掩码,二进制或运算可以确保打开指定的开关

flags = flags | mask;

上面代码中,计算后得到的flags变量,代表三个开关的二进制位都打开了

二进制与运算可以将当前设置中凡是与开关设置不一样的项,全部关闭

flags = flags & mask;

异或运算可以切换(toggle)当前设置,即第一次执行可以得到当前设置的相反值,再执行一次又得到原来的值

flags = flags ^ mask;

二进制否运算可以翻转当前设置,即原设置为0,运算后变为1;原设置为1,运算后变为0

flags = ~flags;

10.参考链接

参考链接

其他运算符,运算顺序

1. void 运算符

void运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined

void 0 // undefined
void(0) // undefined

上面是void运算符的两种写法,都正确。建议采用后一种形式,即总是使用圆括号。因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果。比如,void 4 + 7实际上等同于(void 4) + 7

下面是void运算符的一个例子

var x = 3;
void (x = 5) //undefined
x // 5

这个运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超级链接中插入代码防止网页跳转

下面代码中,点击链接后,会先执行onclick的代码,由于onclick返回false,所以浏览器不会跳转到 example.com

<script>
function f() {
  console.log('Hello World');
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>

void运算符可以取代上面的写法

<a href="javascript: void(f())">文字</a>

下面是一个更实际的例子,用户点击链接提交表单,但是不产生页面跳转

<a href="javascript: void(document.form.submit())">提交</a>

2. 逗号运算符

逗号运算符用于对两个表达式求值,并返回后一个表达式的值

'a', 'b' // "b"

var x = 0;
var y = (x++, 10);
x // 1
y // 10

逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作

var value = (console.log('Hi!'), true);
// Hi!
// 先执行逗号前的操作,然后返回逗号后的值
value // true

3. 运算顺序

3.1 优先级

  1. 小括号 ()
  2. 一元运算符 ++ -- !
  3. 算数运算符* /+ -
  4. 关系运算符 > >= < <=
  5. 相等运算符 == != === !==
  6. 逻辑运算符&&||
  7. 赋值运算符 =
  8. 逗号运算符 ,

3.2圆括号的作用
圆括号(())可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算

(1 + 2) * 3 // 9

函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数

function f() {
  return 1;
}
(f) // function f(){return 1;}
f() // 1

3.3 左结合与右结合

JavaScript 语言的大多数运算符是“左结合”

x + y + z
// 引擎解释如下
(x + y) + z

少数运算符是“右结合”,其中最主要的是赋值运算符(=)和三元条件运算符(?:)以及指数运算符(**

w = x = y = z;
q = a ? b : c ? d : e ? f : g;
2 ** 3 ** 2 
// 上面代码的解释方式如下:
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));
2 ** (3 ** 2)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值