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) //"[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 } } //
"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 类型与类型转换

JavaScript运算符与类型的更多相关文章

  1. javascript运算符语法概述

    × 目录 [1]个数 [2]优先级 [3]结合性[4]类型[5]规则表 前面的话 javascript中的运算符大多由标点符号表示,少数由关键字表示,它们的语法言简意赅,它们的数量却着实不少.运算符始 ...

  2. javascript运算符与表达式

    表达式 表达式是关键字.运算符.变量以及文字的组合,用来生成字符串.数字或对象.一个表达式可以完成计算.处理字符.调用函数.或者验证数据等操作. 表达式的值是表达式运算的结果,常量表达式的值就是常量本 ...

  3. javascript运算符——关系运算符

    × 目录 [1]恒等 [2]相等 [3]大于[4]小于 前面的话 关系运算符用于测试两个值之间的关系,根据关系是否存在而返回true或false,关系表达式总是返回一个布尔值,通常在if.while或 ...

  4. JavaScript 运算符

    JavaScript 运算符 JavaScript 运算符用于赋值,比较值,执行算术运算等. JavaScript 算术运算符 算术运算符用于执行两个变量或值的运算. 赋值 y = 5, 以下表格将向 ...

  5. JavaScript运算符

    JavaScript运算符 1.算数运算符 设定a = 5. 运算符 描述 例子 结果 + 加 b=a+2 b=7 - 减 b=a-2 b=3 * 乘 b=a*2 b=10 / 除 b=a/2 b=2 ...

  6. JavaScript运算符有哪些

    JavaScript中的运算符有很多,主要分为算术运算符,等同全同运算符,比较运算符,字符串运算符,逻辑运算符,赋值运算符等.这些运算符都有一些属于自己的运算规则,下面就为大家介绍一下JavaScri ...

  7. Javascript学习1 - Javascript中的类型对象

    原文:Javascript学习1 - Javascript中的类型对象 1.1关于Numbers对象. 常用的方法:number.toString() 不用具体介绍,把数字转换为字符串,相应的还有一个 ...

  8. 第一百零七节,JavaScript基本包装类型,数据类型的方法

    JavaScript基本包装类型,数据类型的方法 学习要点: 1.基本包装类型概述 2.Boolean类型 3.Number类型 4.String类型 为了便于操作基本类型值,ECMAScript提供 ...

  9. java基础59 JavaScript运算符与控制流程语句(网页知识)

    1.JavaScript运算符 1.1.加减乘除法 加法:+(加法,连接符,正数)          true是1,false是0    减法:-    乘法:*    除法:/ 1.2.比较运算符 ...

随机推荐

  1. kendo datetimepicker

    @(Html.Kendo().DatePicker() .Name("yearCalendar") .Value(DateTime.Now) .Start(CalendarView ...

  2. Java应用开发的一条重要经验:先建立基础设施

    一旦为应用建立良好的基础设施, 后续的开发就会变得容易而快速.这些基础设施包括: 1.    线程池的建立与配置: 在 JDK 并发库的基础上建立适合于应用的多任务接口和框架: 2.   外部系统服务 ...

  3. yum hosts

    67.219.148.138 mirrorlist.centos.org202.38.97.230 ftp.sjtu.edu.cn202.121.199.235 mirrors.shu.edu.cn2 ...

  4. php mysqli query 查询数据库后读取内容的方法

    php mysqli query 查询数据库后读取内容的方法 <?php$mysqli = new mysqli("localhost", "my_user&quo ...

  5. centos 安装 jdk8和comcat8

    1.去官网下载Linux版本的jdk8 2.进入ags,把tar.gz的压缩包拷贝到用户下指定目录java,然后解压 mkdir /home/ags/java tar -zxv -f jdk-8u11 ...

  6. 利用arcgis处理遥感栅格数据,得到省平均值数据

    1.准备全国省级行政区数据,需要有省级行政区信息,如下所示: 2.生成渔网数据,操作完成会生成一个面数据和一个点数据,我们主要用点数据进行后面的操作. 3.提取栅格数据的值到渔网点数据中. 4.将区域 ...

  7. WindowsServer-性能计数器

    https://jingyan.baidu.com/article/59703552e764e48fc00740dd.html

  8. 20145324王嘉澜《网络对抗技术》web安全基础实践

    实验内容 •使用webgoat进行XSS攻击.CSRF攻击.SQL注入 实验问答 •SQL注入攻击原理,如何防御 ①SQL注入攻击是攻击者在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL ...

  9. MVC5 一套Action的登录控制流程

    流程: 用拦截器控制每一个页面请求和ajax请求,根据请求体的cookie里面是否有token判断是否登录,还必须判断该token在redis里面的缓存是否存在 组成部分: 拦截器: using Sy ...

  10. hdu1866 A + B forever!(面积并)题解

    A + B forever! Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T ...