JavaScript运算符与类型
1.运算符优先级
首先讲一下运算符的优先级,它决定了表达式中运算执行的先后顺序,优先级高的运算符最先被执行。
下面的表将所有运算符按照优先级的不同从高到低排列:
优先级 | 运算类型 | 关联性 | 运算符 |
---|---|---|---|
19 | 圆括号 |
n/a | ( … ) |
18 | 成员访问 |
从左到右 | … . … |
需计算的成员访问 |
从左到右 | … [ … ] |
|
new (带参数列表) |
n/a | new … ( … ) |
|
17 | 函数调用 | 从左到右 | … ( … ) |
new (无参数列表) | 从右到左 | new … |
|
16 | 后置递增(运算符在后) | n/a | … ++ |
后置递减(运算符在后) | n/a | … -- |
|
15 | 逻辑非 | 从右到左 | ! … |
按位非 | 从右到左 | ~ … |
|
一元加法 | 从右到左 | + … |
|
一元减法 | 从右到左 | - … |
|
前置递增 | 从右到左 | ++ … |
|
前置递减 | 从右到左 | -- … |
|
typeof | 从右到左 | typeof … |
|
void | 从右到左 | void … |
|
delete | 从右到左 | delete … |
|
14 | 乘法 | 从左到右 | … * … |
除法 | 从左到右 | … / … |
|
取模 | 从左到右 | … % … |
|
13 | 加法 | 从左到右 | … + … |
减法 | 从左到右 | … - … |
|
12 | 按位左移 | 从左到右 | … << … |
按位右移 | 从左到右 | … >> … |
|
无符号右移 | 从左到右 | … >>> … |
|
11 | 小于 | 从左到右 | … < … |
小于等于 | 从左到右 | … <= … |
|
大于 | 从左到右 | … > … |
|
大于等于 | 从左到右 | … >= … |
|
in | 从左到右 | … in … |
|
instanceof | 从左到右 | … instanceof … |
|
10 | 等号 | 从左到右 | … == … |
非等号 | 从左到右 | … != … |
|
全等号 | 从左到右 | … === … |
|
非全等号 | 从左到右 | … !== … |
|
9 | 按位与 | 从左到右 | … & … |
8 | 按位异或 | 从左到右 | … ^ … |
7 | 按位或 | 从左到右 | … | … |
6 | 逻辑与 | 从左到右 | … && … |
5 | 逻辑或 | 从左到右 | … || … |
4 | 条件运算符 | 从右到左 | … ? … : … |
3 | 赋值 | 从右到左 | … = … |
… += … |
|||
… -= … |
|||
… *= … |
|||
… /= … |
|||
… %= … |
|||
… <<= … |
|||
… >>= … |
|||
… >>>= … |
|||
… &= … |
|||
… ^= … |
|||
… |= … |
|||
2 | yield | 从右到左 | yield … |
yield* | 从右到左 | yield* … | |
1 | 展开运算符 | n/a | ... … |
0 | 逗号 | 从左到右 | … , … |
2.运算符的结合性
结合性决定了拥有相同优先级的运算符的执行顺序。
3.类型
JavaScript是一种无类型语言(更为精确的说,是一种松散类型,动态类型的语言)。这就是说,再声明变量时无需指定数据类型,这使JavaScript具有灵活性和简单性。在代码执行过程中,JavaScript会根据需要自动进行类型转换。例如,如果传递给方法document.write()的是一个数字,JavaScript会自动将其转换成与之等价的字符串表示。
javascript类型主要包括了primitive和object类型,其中primitive类型包括了:null、undefined、boolean、number、string和symbol(es6)。
说到类型检测主要包括了:typeof、instanceof和Object.prototype.toString.call(xxx)或{}.prototype.toString.call(xxx)。类型判断
typeof
一般适用于判断primitive
类型的数据,在判断object
类型的数据时有时会有意想不到的结果,例如:typeof null
结果为object
。下面的表是typeof
元素符的一个结果:
val 类型 | 结果 |
---|---|
Undefined | “undefined” |
Null | “object” |
Boolean | “boolean” |
Number | “number” |
String | “string” |
Object(原生,且没有实现 [[Call]]) | “object” |
Object(原生或者宿主且实现了 [[Call]]) | “function” |
Object(宿主且没实现 [[Call]]) | 由实现定义,但不能是 “undefined”、”boolean”、”number” 或 “string”。 |
instanceof
运算符是用于判断一个实例是否属于某一类型,例如:a instanceof Person
,其内部原理实际上是判断Person.prototype
是否在a
实例的原型链中
Object.prototype.toString.call(xxx)或{}.prototype.toString.call(xxx),使用Object.prototype.toString会节省创建一个对象。
4.类型转换
JavaScript的数据类型有两类:原始类型(数字、字符串、布尔值)和对象类型,还有null、undefined(两个特殊的原始值)。
- 空数组在比较中转换成0;一个元素的数组a转换为a[0],若a[0]为数值或纯数字字符串(不包含布尔值)按数值比较原则,非纯数字字符串与布尔值转化为字符串;多个元素,数组会将方括号内的内容转换为字符串如[1,2] -> '1,2',不能比较,所以返回false。
- 大小比较:不能参与比较的比较结果都是false,而不是报错;参与比较的纯数字字符串都会转换为数值型;布尔值与数值、布尔值与字符串(非纯数字字符串不能转换,返回false)、字符串与数值(同上)、字符串与字符串(不进行转换,即使是纯数字字符)。
- 相等性比较:布尔值:true 等于(==)其他形式的 1,这包含‘1’、1、[1]、['1'];false 等于其他形式的 0 ,包含‘0’、0、['0']、[0];数值与对应的字符串是相等的。
- 四则运算:'+'运算符:布尔+数值、布尔+布尔、数值+数值会转换为数值;有一个字符串,另一个会转换为字符串;其他类型,不能转换为数值的都会转为字符串。其他运算符:参与运算的非数值类型都会试图转换为为数值类型进行运算,但只有布尔类型、纯数字字符串与1个元素(该元素必须是数值型或纯数字字符串,不能是布尔型)或者符合数组转换原则的数组可以实现转换。不能实现转换的参与运算后等到数值型的NaN(用函数isNaN()检测)。
- 条件判断:if条件语句,while 循环语句中判断的‘假’包含”0、''(空字符串)、null、undefined、false。
- 逻辑运算:逻辑运算遵循了条件判断的原则,此处应注意取值方法:"||"运算:最基本的当然是两个操作数一真一假,必然返回真的操作数的值。如果判断第一个操作数为真,则不进行第二个操作数的运算直接返回第一操作数的值。如果两个都为假返回的是第二个操作数的值;"&&"运算:最基本的当然是两个操作数一真一假,必然返回假的操作数的值。如果第一个操作数的值为假,则不进行第二个操作数的运算直接返回第一个操作数的值。如果两个都为真则返回第二个操作数的值。"!"运算:返回值必然为布尔类型,true 或者 false。
(1)隐形转换
"0" == 0 //true 在比较之前字符串转换成数字 0 == false //true 在比较之前布尔值转换成数字 "0" == false //true 在比较之前字符串和布尔值都转换成数字
(2)显性转换
使用Boolean()、Number()、String()或Object()函数。
(3)对象转换为原始值
对象到布尔值:所有的对象(包括数组和函数)都转换为true。对于包装对象亦是如此:new Boolean(false)是一个对象而不是原始值,它将转换为true。
JavaScript中对象到字符串的转换经过了如下这些步骤:
- 如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。需要注意的是,原始值到字符串的转换在表3-2中已经有了详尽的说明。
- 如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JavaScript会调用valueOf()方法。如果存在这个方法,则JavaScript调用它。如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
- 否则,JavaScript无法从toString()或valueOf()获得一个原始值,因此这时它将抛出一个类型错误异常。
在对象到数字的转换过程中,JavaScript做了同样的事情,只是它会首先尝试使用valueOf()方法:
- 如果对象具有valueOf()方法,后者返回一个原始值,则JavaScript将这个原始值转换为数字(如果需要的话)并返回这个数字。
- 否则,如果对象具有toString()方法,后者返回一个原始值,则JavaScript将其转换并返回。
- 否则,JavaScript抛出一个类型错误异常。
(4)加法运算符会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作:ToPrimitive(),ToNumber(),ToString()
ToNumber()是如何将原始值转换成数字的:
参数 | 结果 |
undefined | NaN |
null | +0 |
布尔值 | true被转换为1,false转换为+0 |
数字 | 无需转换 |
字符串 | 由字符串解析为数字.例如,"324"被转换为324 |
ToString()是如何将原始值转换成字符串的:
参数 | 结果 |
undefined | "undefined" |
null | "null" |
布尔值 | "true" 或者 "false" |
数字 | 数字作为字符串,比如. "1.765" |
字符串 | 无需转换 |
5.示例
运行一下代码:
(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
结果为:"sb"
这里涉及到运算及的优先级以及类型转换。
将上述代码拆分,得到:
运算符用红色标出,其实中括号[]也是一个运算符,用来通过索引访问数组项,另外也可以访问字符串的子字符,而且中括号的优先级还是最高的。
什么情况下需要进行类型转化。当操作符两边的操作数类型不一致或者不是基本类型(也叫原始类型)时,需要进行类型转化:
- 减号-,乘号*,肯定是进行数学运算,所以操作数需转化为number类型。
- 加号+,可能是字符串拼接,也可能是数学运算,所以可能会转化为number或string
- 一元运算,如+[],只有一个操作数的,转化为number类型
执行代码:
子表达式16:
+[] //输出0
只有一个操作数[],肯定是转化为number了。[]是个数组,object类型,即对象。所以得先调用toPrimitive转化为原始类型。首先调用数组的valueOf方法,会返回自身;接下来调用数组的toString()方法,返回一个空字符串"";在调用引擎方法toNumber(),返回0。所以结果就是0。
子表达式15:
[~+""] //结果为[-1]
空串""前面有两个一元操作符,但是操作数还是只有一个,所以,最终要转化为的类型是number。根据结合性,先计算+"",调用引擎方法toNumber(),结果为0;接下来是~,它是位运算符,作用可以记为把数字取负然后减一,所以~0就是-1 。加上中括号,所以结果为[-1]。
子表达式13:
--[~+""][+[]] //根据表达式15、16,结果为--[-1][0]
根据优先级,--[-1][0],取数组的第0个元素,然后自减,结果为-2。
子表达式14:
[~+[]] //根据表达式15、16,结果为[-1]
子表达式9:
此刻它已变成:-2*[-1]。
运算符是乘号*,都得转化为number。先将[-1]对象转化为原始类型"-1",再通过引擎方法toNumber()转为Number为-1。
所以结果为2.
子表达式10:
~~!+[] //从右往左计算,结果为1
+[]为0,所以!+[]就为true,一元运算符需要转化为number,所以~true等于-2,~-2等于1,。所以结果为1。
子表达式4:
结合子表达式9和10,结果为3。
表达式7:
!(~+[]) //有前面表达式得出结果为!-1
感叹号会把表达式转化为布尔类型,转化规则和js的Truthy和Falsy原则是一样的,后面跟数字的,除0以外都为true,后面跟字符串的,除空串以外都为true。这里的!-1当然就是false了。
表达式3:
false+{}。一个布尔加一个对象,那这个{}应该先转化为原始类型。调用引擎方法ToPrimitive(),结果为"[object Object]"。false与"[object Object]"相加,false先转化为字符串"false",相加得结果"false[object Object]"。
表达式1:
此时它是这样的:"false[object Object]"[3],因为这个[]可以取字符串的子字符,所以得到了结果"s"。
表达式11:
[~!+[]] //结果为[-2],见表达式10
表达式12:
~+[] //结果为-1
表达式6:表达式11和12 相乘,结果为2.
表达式5:
({}+[]) //结果为"[object Object]"
{}和[]都是对象,调用引擎方法ToPrimitive(),{}转化为原始值为"[object Object]",[]转化为原始值为""。所以结果为"[object Object]"。
子表达式2:
得到表达式"[object Object]"[2],所以结果为"b"。
问题:
为什么表达式{}+[]和表达式({}+[])结果不一样呢?
是因为在console中,如果你不用括号包起来的话,不认为你是在进行运算。此时{}会被解析为上一个语句的结束标记。所以{}+[]就相当于是+[]。
[]+{} //"[object Object]" {}+0 //0 ({}+0) //"[object Object]0" 0+{} //"0[object Object]"
示例:
++[[]][+[]]+[+[]] ++[0][0]+[0] 1+[0] "10"
示例:
[] + [] //"" var arr = [];arr.valueOf() === arr //true String({}) //"[object Object]" 6 + { valueOf: function () { return 2 } } //8 "abc" + { toString: function () { return "def" } } //abcdef {} + {} //NaN 一元运算符需要转为number ({} + {}) //"[object Object][object Object]"
示例:
[] == ![] //true
首先看右边的![]
,空数组转换为boolean是true
的,再进行!
,可以知道右边的![]
为false
,当一个对象和boolean进行equal
时,[]
会进行ToPrimitive
,这里就会首先调用Array.prototype.valueOf
,调用后返回的是[]
,不是原始类型,再进行Array.prototype.toString
,这里返回了""
空字符,空字符和false
进行相等比较这里就是true
了。