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()

http://es5.github.io/#x9.1

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了。

 

 

运算符优先级

运算符优先级 (JavaScript)

一行神奇的javascript代码

[译]JavaScript中,{}+{}等于多少?

JavaScript 类型转换

javascript 类型与类型转换

posted @ 2017-04-12 07:33  chenxj  阅读(442)  评论(0编辑  收藏  举报