在JavaScript里有
- 算术运算符(Arithmetic operators):
+、-、*、**(平方)、/、%(取余)、++(递增)、--(递减)
- 赋值运算符(Assignment operators):
=、+=、-=、*=、/=、%=
- 比较运算符(Comparison operators):
==、===、!=、>、>=、<、<=
- 逻辑运算符(Logical operators):
**&&、||、!**
- 位运算符(Bitwise operators):
**&(与)、|(或)、~(非)、^(异或)、<<(左移)、>>(右移)**
- 一元运算符(Unary operators)
- 其他运算符:比如字符串运算符(String operators)、条件(三元)运算符(Conditional operator)、逗号运算符(Comma operator)、关系运算符(Relational operator)。
三等于运算符
三等于运算符在比较时,会优先比较数据类型,数据类型相同才去判断值的大小,如果类型不同则直接返回“false”。
① 如果比较的值类型不相同,则直接返回“false”。
② 如果比较的值都是数值类型,则直接比较值的大小,相等则返回“true”,否则返回“false”。需要注意的是,如果参与比较的值中有任何一方为NaN,则返回“false”。
③ 如果比较的值都是字符串类型,则判断每个位置的字符是否一样,如果一样则返回“true”,否则返回“false”。
④如果比较的值都是Boolean类型,则两者同时为true或者false时,返回“true”,否则返回“false”。
⑤ 如果比较的值都是null或者undefined,则返回“true”;如果只有一方为null或者undefined,则返回“false”。
⑥ 如果比较的值都是引用类型,则比较的是引用类型的地址,当两个引用指向同一个地址时,则返回“true”,否则返回“false”。双等于运算符
双等于运算符在比较时,会将两端的变量进行隐式类型转换,转换成数值然后比较值的大小。
① 如果比较的值类型相同,则采用与三等于运算符一样的规则,内部使用equals方法进行比较。
② 如果比较的值类型不同,基本类型会调用内部toNumber方法,对象类型会调用内部方法toPrimitive进行值转换:false == false // true
[] == [] // false
{} == {} // false
值类型 | 结果 |
---|---|
undefined | toNumber(undefined) = NaN |
null | toNumber(null) = +0 // 实际比较不要用这个规则! |
布尔值 | toNumber(true) = 1 toNumber(false) = +0 |
字符串 | toNumber(‘xjp’) = NaN toNumber(‘true/false’) = NaN toNumber(‘’) = +0 toNumber(‘010’) = 10 toNumber(‘0x15’) = 21 |
对象 | 如果对象的valueOf()方法的结果是原始值,就返回原始值;否则再调用对象的toString()方法,若返回原始值就返回这个值,其它情况都返回一个错误。 |
有一个很SB的现象:虽然上面**toNumber(null) = +0**
,但实际在比较的时候要注意,只要出现和null相比较的,只能要么是null或者undefined,其它一律为false
null == undefined // true ⭐⭐⭐需要特别注意,如果根据转换规则NaN == +0 其实为false,但是null == undefined 实际为true
null == 0 // false ⭐⭐⭐懵逼,可能是因为null为对象??
null == false // false 反正谨记,null只有跟自己和undefined相等
undefined == 0 // false
undefined == false // false
字符串的转换需要特别注意:
- 如果字符串是十六进制的数据,会转换为十进制后再进行比较。
- 字符串并不支持八进制的数据,如果字符串以0开头,则0会直接省略,后面的值当作十进制返回。
'0x15' == 21 // true
'020' == 16 //false
'020' == 20 // true
'true' == true // false ==> NaN == 1
'xjp' === true // false 相当于 NaN
'1' == true // true
'0' == false // true
'0.0' == false // true
'true' == true // false 相当于 NaN == 1
'xjp' === true // false 相当于 NaN == 1
typeof运算符
作用:用于返回操作数的数据类型(返回值是字符串包裹的数据类型)
其中operand表示需要返回数据类型的操作数,可以是引用类型,也可以是基本数据类型。typeof operand
typeof(operand)
括号有的时候是必须的,如果不加上括号将会因为优先级的问题得不到我们想要的结果。
不同类型的返回值:
1、处理Undefined类型的值
虽然Undefined类型的值只有一个undefined,但是typeof运算符在处理以下3种值时都会返回“undefined”。
undefined本身。
- 未声明的变量。
已声明但未初始化的变量。
var a;
typeof undefined === 'undefined' // true
typeof a === 'undefined' // true 已声明未初始化的变量
typeof b === 'undefined' // true 未声明的变量
2、处理Boolean类型的值
Boolean类型的值只有两个,分别是true和false。
typeof运算符在处理这两个值以及它们的包装类型时都会返回“boolean”,但是不推荐使用包装类型的写法。typeof true === 'boolean' // true
typeof false === 'boolean' // true
typeof Boolean(true) === 'boolean' // true 不推荐这么写!
3、处理Number类型的值
数字,如1、123、3.145。
- Number类型的静态变量,如Number.MAX_VALUE、Number.EPSILON等。
- Math对象的静态变量值,如Math.PI、Math.LN2(以e为底,2的对数)。
- NaN,虽然NaN是Not a Number的缩写,但它是Number类型的值。
- Infinity和-Infinity,表示的是无穷大和无穷小的数。
数值类型的包装类型,如Number(1)、Number(123),虽然它们也会返回“number”,但是并不推荐这么写。
typeof 37 === 'number' // true
typeof 3.14 === 'number' // true
typeof Math.LN2 === 'number' // true
typeof Infinity === 'number' // true
typeof NaN === 'number' // true
typeof Number(1) === 'number' // true 不推荐这么写!
4、处理String类型的值
任何类型的字符串,包括空字符串和非空字符串。
- 返回值为字符串类型的表达式。
字符串类型的包装类型,例如String(‘hello’)、String(‘hello’ + ‘world’),虽然它们也会返回“String”,但是并不推荐这么写。
typeof '' === 'string' // true
typeof 'bla' === 'string' // true
typeof(typeof 1) === 'string' // true 因为typeof会返回一个字符串
typeof String('abc') === 'string' // true 不推荐这么写!
5、处理Symbol类型的值
typeof Symbol() === 'symbol' // true
typeof Symbol('foo') === 'symbol' // true
6、 处理Function类型的值
函数的定义,包括函数声明或者函数表达式两种形式。
- 使用class关键字定义的类,原理依旧是原型继承,本质上仍然是一个Function。
- 某些内置对象的特定函数,例如Math.sin()函数、Number.isNaN()函数等。
- Function类型对象的实例,一般通过new关键字得到。 ```javascript var foo = function(){} function foo2(){}
typeof foo === ‘function’ // true 函数表达式 typeof foo2 === ‘function’ // true typeof class c{} === ‘function’ // true typeof Math.sin === ‘function’ // true typeof new Function() === ‘function’ // true new操作符得到Function类型的实例
**7、处理Object类型的值**
- 对象字面量形式,例如{name: 'kingx'}。
- 数组,例如[1, 2, 3]和Array(1, 2, 3)。
- 所有构造函数通过new操作符实例化后得到的对象,例如new Date()、newfunction(){},但是new Function(){}除外。
- 通过new操作符得到的基本数据类型的包装类型对象,如new Boolean(true)、new Number(1),但不推荐这么写。因为涉及包装类型时,使用了new操作符与没有使用new操作符得到的值在通过typeof运算符处理后得到的结果是不一样的,很容易让人混淆。
```javascript
typeof {age:18} === 'object' // true 对象字面量
typeof [1,2,3] === 'object' // true 数组
typeof Array(1,2,3) === 'object' // true
typeof new Array(1,2,3) === 'object' // true
typeof new Date() === 'object' // true Date对象的实例
// 与基本数据类型的包装类型,都不要使用!
typeof new Boolean(true) === 'object' // true
typeof new Number(1) === 'object' // true
typeof new String('1') === 'object' // true
问题:
1. typeof运算符区分Object类型和Function类型
从技术角度讲,函数在ECMAScript中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过typeof运算符来区分函数和其他对象是有必要的。
另外,在实际使用过程中,有必要区分Object类型和Function类型,而typeof运算符就能帮我们实现。
2. typeof运算符对null的处理
使用typeof运算符对null进行处理,返回的是“object”,这是一个在JavaScript设计之初就存在的问题。在JavaScript中,每种数据类型都会使用3bit表示:
- 000表示Object类型的数据。
- 001表示Int类型的数据。
- 010表示Double类型的数据。
- 100表示String类型的数据。
- 110表示Boolean类型的数据。
由于null代表的是空指针,大多数平台中值为0x00,因此null的类型标签就成了0,所以使用typeof运算符时会判断为object类型,返回“object”。虽然在后面的提案中有提出修复方案,但是因为影响面太大,所以并没有被采纳,从而导致这个问题一直存在。
3. typeof运算符相关语法的括号
在前文中有讲到,括号有时是必须存在的,如果不加上括号则会因为优先级的问题得不到我们想要的结果
var number = 123
typeof(number + 'hello') // string typeof运算符的优先级低于小括号()
typeof number + 'hello' // 'numberhello' typeof运算符的优先级会高于字符串拼接运算符(+)
typeof 1 / 0 // 'NaN' 实际执行'number' / 0
typeof(1 / 0) // 'number'
因此在处理某些表达式时,需要将这些表达式用括号括起来以保证先运算表达式,再使用typeof运算符进行运算。
逗号运算符
使用场景
1. 在for循环中的使用场景是批量执行表达式。
如果一个for循环中有多个变量需要执行表达式,可以通过逗号运算符一次性执行。
for(var i=0,j=0; i<2,j<5; i++,j++){
console.log(i,j) // 0 0 1 1 2 2 3 3 4 4
}
一般在for循环的末尾处,只允许执行单个表达式。在这里我们通过逗号运算符,将i++和j++两个表达式视为同一个表达式,因此可以一次执行,处理i与j两个变量的递增。
2. 用于交换变量,无须额外变量
var a = 'a'
var b = 'b'
// 方案1
a=[b, b=a][0]
// 方案2
a=[b][b=a, 0]
在方案1中,前一部分[b, b = a]是一个一维数组,数组第二项值是b = a,实际会将a值赋给b,然后返回“’a’”,因此数组最终的值为[‘b’, ‘a’],然后取索引0的值为’b’,赋给变量a,最终实现a = ‘b’, b = ‘a’。
在方案2中,前一部分[b]是一个一维数组,后一部分[b = a, 0],实际会先执行b =a,将a值赋给b,然后返回“0”,因此后一部分实际是修改了b的值并返回索引“0”,最终是a = [b][0],即a = b,实现了a与b的交换。两个数组在一起,后一个数组不管多少项只会返回第一个合理的数字项作为索引
3. 用于简化代码
逗号运算符可以使多个表达式先后执行,并且返回最后一个表达式的值
if(x) {
foo();
return bar()
} else {
return 1
}
// 使用逗号简化
x?(foo(), bar()):1
用小括号保证逗号运算符的优先级
在所有的运算符中,逗号运算符的优先级是最低的,因此对于某些涉及优先级的问题,我们需要使用到小括号,将含有逗号运算符的表达式括起来。var a = 20;
var b = ++a,10;
console.log(b) // Uncaught SyntaxError: Unexpected number
在上面的代码中,同时出现了赋值运算符与逗号运算符,因为逗号运算符的优先级比较低,实际会先执行赋值运算符,即先执行var b = ++a语句,再去执行后面的10,它不是一个合法的语句,所以会抛出异常。
var a = 20;
var b = (++a,10); // 使用逗号运算符对变量a执行自增操作,同时返回“10”,并将其赋值给变量b。
console.log(b) // 10
解决方法就是使用小括号,保证逗号运算符的优先级,将赋值语句后面的内容括起来,执行完含有逗号运算符的表达式后,再执行赋值语句。
运算符优先级
| 优先级 | 运算类型 | 关联性 | 运算符 | | —- | —- | —- | —- | | 21 | 圆括号 | n/a(不相关) | ( … ) | | 20 | 成员访问 | 从左到右 | … . … | | | 需计算的成员访问 | 从左到右 | … [ … ] | | | new(带参数列表) | n/a | new … ( … ) | | | 函数调用 | 从左到右 | … ( … ) | | | 可选链(Optional chaining) | 从左到右 | ?. | | 19 | new(无参数列表) | 从右到左 | new … | | 18 | 后置递增(运算符在后) | n/a
| … ++ | | | 后置递减(运算符在后) | | … — | | 17 | 逻辑非 | 从右到左 | ! … | | | 按位非 | | ~ … | | | 一元加法 | | + … | | | 一元减法 | | - … | | | 前置递增 | | ++ … | | | 前置递减 | | — … | | | typeof | | typeof … | | | void | | void … | | | delete | | delete … | | | await | | await … | | 16 | 幂 | 从右到左 | … ** … | | 15 | 乘法 | 从左到右
| … * … | | | 除法 | | … / … | | | 取模 | | … % … | | 14 | 加法 | 从左到右
| … + … | | | 减法 | | … - … | | 13 | 按位左移 | 从左到右 | … << … | | | 按位右移 | | … >> … | | | 无符号右移 | | … >>> … | | 12 | 小于 | 从左到右 | … < … | | | 小于等于 | | … <= … | | | 大于 | | … > … | | | 大于等于 | | … >= … | | | in | | … in … | | | instanceof | | … instanceof … | | 11 | 等号 | 从左到右
| … == … | | | 非等号 | | … != … | | | 全等号 | | … === … | | | 非全等号 | | … !== … | | 10 | 按位与 | 从左到右 | … & … | | 9 | 按位异或 | 从左到右 | … ^ … | | 8 | 按位或 | 从左到右 | … | … | | 7 | 逻辑与 | 从左到右 | … && … | | 6 | 逻辑或 | 从左到右 | … || … | | 5 | 空值合并 | 从左到右 | … ?? … | | 4 | 条件运算符 | 从右到左 | … ? … : … | | 3 | 赋值 | 从右到左 | … = … | | | | | … += … | | | | | … -= … | | | | | … *= … | | | | | … = … | | | | | … /= … | | | | | … %= … | | | | | … <<= … | | | | | … >>= … | | | | | … >>>= … | | | | | … &= … | | | | | … ^= … | | | | | … |= … | | | | | … &&= … | | | | | … ||= … | | | | | … ??= … | | 2 | yield | 从右到左 | yield … | | | yield* | | yield* … | | 1 | 展开运算符 | n/a | … … | | 0 | 逗号 | 从左到右 | … , … |