JS基础篇之强制类型转换与操做符

若是你完全掌握了隐式类型转换,那么对你来讲,它就是“显式”类型转换了。javascript

小纲老师

类型转换规则

JS 是弱类型语言,不一样数据类型之间能够发生转换。java

JS 中类型转换只有三种状况:toStringtoNumbertoBoolean 。正常状况下转换规则以下:segmentfault

原始值/类型 目标类型:number 结果
null number 0
symbol number 抛错
string number '1'=>1 '1a'=>NaN ,含非数字则为NaN
数组 number []=>0 ['1']=>1 ['1', '2']=>NaN
object/function/undefined number NaN
原始值/类型 目标类型:string 结果
number string 1=>'1'
array string [1, 2]=>'1,2'
布尔值/函数/symbol string 原始值加上引号,如:'true' 'Sumbol()'
object string {}=>'[object Object]'
原始值/类型 目标类型:boolean 结果
number boolean 除了0NaNfalse,其余都是true
string boolean 除了空字符串为false,其余都为true
null/undefined boolean false
引用类型 boolean true

强制类型转换在实现上是靠如下几种抽象操做实现的(理解便可):数组

ToString

抽象操做 ToString,负责处理非字符串到字符串的强制类型转换。当须要一个值的字符串形式,就会进行 ToString 类型转换。函数

String()函数就会执行抽象操做 ToString,遵循下列转换规则:ui

  1. 若是值是基本类型,则直接转为字符串。若是是引用类型,则执行ToPrimitive抽象操做;
  2. 若是值是 null,则返回"null";
  3. 若是值是 undefined,则返回"undefined"。
String()  // ''
String(0)   // '0'
String(true)  // 'true'
String(null)  // 'null'
String(undefined)  // 'undefined'
String(Symbol('asdf'))  // "Symbol('asdf')"
String({})  // '[Object object]'
// 数组的默认 toString() 方法通过了从新定义,将全部单元字符串化之后再用 "," 链接起来
String([])  // ''
String([1,2,3,4,'asdf'])  // '1,2,3,4,asdf'
复制代码

ToNumber

抽象操做 ToNumber,负责处理非数字到数字的强制类型转换。编码

Number()执行抽象操做 ToNumber,函数的转换规则以下。spa

  1. 若是是 Boolean 值,true 和 false 将分别被转换为 1 和 0。
  2. 若是是数字值,只是简单的传入和返回。
  3. 若是是 null 值,返回 0。
  4. 若是是 undefined,返回 NaN。
  5. 若是是字符串:若是字符串是空的(不包含任何字符),则将其转换为0;若是含非数字,则将其转换为 NaN。
  6. 若是是对象,则执行ToPrimitive抽象操做,返回基本类型再按照以上规则处理。
Number()  // 0
Number('')  // 0
Number(' ')  // 0
Number('0')  // 0
Number('asdf')  // NaN
Number(true)  // 1
Number(false)  // 0
Number(null)  // 0
Number(undefined)  // NaN 与null不一样,须要注意
// 对象会先经过抽象操做ToPrimitive转为基本类型,而后再转数字
Number({})  // NaN
Number([])  // 0
Number([''])  // 0
Number([' '])  // 0
Number(['0'])  // 0
Number([1,2])  // NaN
复制代码

ToBoolean

抽象操做 ToBoolean,负责处理非布尔值到布尔值的强制类型转换。code

转换为 boolean 类型是最为简单的一个。转换规则以下:对象

(1) 能够被强制类型转换为 false 的值

  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""

(2) 其余值会被被强制类型转换为 true

ToPrimitive

抽象操做 ToPrimitive 用于将引用类型转为原始类型。js 内置的Symbol.toPrimitive函数用来实现引用类型转原始类型,也能够给对象自定义以下。实现细节比较复杂,有兴趣的童鞋能够参考这里

//模拟一个对象的转基本类型操做 ToPrimitive
var o = {};
o[Symbol.toPrimitive] = function(hint) {
  console.log(hint) //hint字符串至为 string number default 中的一个
  if (hint == "default" || hint == "number") {
    if (o.valueOf && typeof(o.valueof()) != 'object') {
      return o.valueOf()
    } else {
      return o.toString()
    }
  } else {
    if (o.toString && typeof(o.toString()) != 'object') {
      return o.toString()
    } else {
      return o.valueOf()
    }  
  }
}
String(o) // string
Number(o) // number
1+o // default
1-o // number
o++ // number
++o // number
复制代码

ToPrimitive 转换规则以下:

  1. 若是传入参数是string(目前只有调用String()函数是执行这个顺序):首先检查该值是否有toString()方法。若是有而且返回基本类型值,就使用该值进行强制类型转换。若是没有就检查该值是否有valueOf()方法。若是有而且返回基本类型值就使用该回值来进行强制类型转换,若是没有或者返回的不是基本类型值,就抛出错误。
  2. 若是传入参数是number/default(常见强制类型转换都是这个顺序):首先检查该值是否有valueOf()方法。若是有而且返回基本类型值,就使用该值进行强制类型转换。若是没有就检查该值是否有toString()方法。若是有而且返回基本类型值就使用该回值来进行强制类型转换,若是没有或者返回的不是基本类型值,就抛出错误。

一元操做符

这里有个概念须要了解一下: js 的操做符和操做数组成了表达式,表达式一定会返回一个值。不管是一元操做++a,仍是布尔操做[] || false,都会返回一个值。另外关于 js 运算符优先级请参阅 MDN 的:运算符优先级

一元操做符指的是只能操做一个值的操做符,区别与加性操做符能够操做两个值(如a + b)。

// 假设存在变量a
+a // 一元加操做符
-a // 一元减操做符
++a // 前置递增操做符
--a // 前置递减操做符
a++ // 后置递增操做符
a-- // 后置递减操做符
复制代码

1. 一元加减操做符

一元加操做符+用于非数字的强制类型转换,做用等同于Number()。如:

+'1.1' // 1.1
+'asdf' // NaN
+true // 1
+false // 0
+null // 0
+undefined // NaN
+{} // NaN
+[] // 0
+new Date() // 1556258367546
复制代码

一元减操做符-行为与+相似,只不过最终运算结果是负数。如-true结果是-1

2. 递增递减操做符

不一样于一元加减操做符,递增递减操做符只能做用于number类型。若用于其余类型会直接抛错。

//前置递增
var a = 57;
var b = ++a;
console.log(b); // 58
//后置递增
var a = 57;
var b = a++;
console.log(b); // 57
复制代码

前置递增和后置递增的区别在于,前置递增++a的返回值是增长1的,然后置递增a++的返回值是不增长的。 递减和递增规则同样,再也不废话。

加性操做符

1. 加法操做符+

+ 操做符经常使用于数学的计算和字符串的拼接,规则以下:

  1. 若是两个操做符都是数值,执行常规的加法计算
  2. 若是有一个操做数是 NaN,则结果是 NaN;
  3. 若是两个操做数都是字符串,则将第二个操做数与第一个操做数拼接起来;
  4. 若是只有一个操做数是字符串,则将另外一个操做数转换为字符串,而后再将两个字符串拼接起来。
  5. 若是有一个操做数是对象,则执行抽象操做ToPrimitive(先valueOf再toString)取的返回值,而后再应用前面关于字符串的规则。
  6. 对于 undefined 和 null,则分别调用 String()函数并取得字符串"undefined"和"null"。
1+1 // 2
NaN+1 // NaN
'asdf'+'ghjk' // 'asdfghjk'
1+1+'1' // '21'
[]+1 // 1
null+undefined // 'nullundefined'
[]+{}+1 // '[Object object]1' 实际执行:''+'[Object object]'+1
{}+[]+1 // 1 {}位于行首会被解释为代码块,此处代码块被忽略,所以实际执行:+[]+1,结果为数字1
复制代码

2. 减法操做符-

  1. 若是两个操做符都是数值,执行常规的加法计算
  2. 若是有一个操做数是 NaN,则结果是 NaN;
  3. 若是有一个操做数是字符串、布尔值、null或undefined,则先在后台调用Number()函数将其转换为数值,而后再根据前面的规则执行减法计算。若是转换的结果是 NaN,则减法的结果就是 NaN;
  4. 若是有一个操做数是对象,则执行抽象操做ToPrimitive,先调用对象的valueOf()方法以取得表示该对象的数值。若是获得的值是NaN,则减法的结果就是NaN。若是对象没有 valueOf()方法或者返回的不是基本类型值,则调用其 toString()方法并将获得的字符串转换为数值。
1-1 // 0
NaN-1 // NaN
10-true-null // 9
10-true-undefined // NaN
[]-1 // 0
['11']-11 // 0
11-{} // NaN
复制代码

乘性操做符

乘性操做符包括乘法*、除法/、除余(求模)%。规则以下:

  1. 若是两个操做符都是数值,执行常规乘除求模;
  2. 若是有一个操做数是 NaN,则结果是 NaN;
  3. 若是有一个操做数不是数值,则在后台调用 Number()将其转换为数值,而后再应用上面的规则。

数值计算较为特殊的以下:

Infinity*0 // NaN
Infinity/Infinity // NaN
0/0 // NaN
Infinity%a // NaN a为任意数值
a%0 // NaN a为任意数值
复制代码

布尔操做符

1. 逻辑非 !

逻辑非操做符会将任意值转换为一个布尔值,转换规则和Boolean()函数相反。连续使用两个逻辑非操做符,等同于调用了Boolean()。常见有大牛写代码用!!isTrue来代替Boolean(isTrue)函数。

!undefined // true
!!undefined // false
!NaN // true
!!NaN // false
!1234 // false
!!1234 // true
!'' // true
!!'' // false
复制代码

2. 逻辑或 ||

短路操做:若是第一个操做数可以决定结果,那么就不会再对第二个操做数求值。

逻辑或操做符是短路操做,若是第一个操做数的求值结果(布尔求值,下同)为true,则直接返回第一个操做数,再也不对第二个操做数求值。若是第一个操做符求职结果为false,则返回第二个操做数。所以,常见大神写代码isExist || getIsExist(),就是利用的短路操做,若是isExist求值结果为true,就再也不执行getExist()函数。

[] || 0 // [] 对象(包括数组、函数等)的求值结果永远为`true`,直接返回这个对象
0 || [] // []
1 || [] // 1
NaN || 0 // 0
复制代码

3. 逻辑与 &&

逻辑与操做属于短路操做,即若是第一个操做数求值结果为false,则直接返回第一个操做数,那么就不会再对第二个操做数求值。若是第一个操做数求值为true,则返回第二个操做数。能够用来作条件限制obj && obj.value。只有obj对象存在了,才会取obj.value值。

0 && true // 0
null && [] // null
NaN && null // NaN
[] && {} // {}
复制代码

须要注意布尔操做符存在优先级:! > && > ||

null || !2 || 3 && 4 // ??????你知道结果吗?实际上,代码至关于下面一行
null || (!2) || (3 && 4) // 4
复制代码

相等操做符

相等操做符有== != === !==四个,其中相等和不相等实行先转换类型再比较,全等和不全等实行仅比较而不转换类型。相等操做符返回布尔值truefalse

1. 相等和不相等

不一样类型操做数比较规则以下:

  • 先判断是否在对比 null 和 undefined,是的话就会返回 true。null和undefined不相等于其余任何值。
  • 判断二者类型是否为 string 和 number,是的话就会将字符串转换为 number;
  • 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断;
  • 判断其中一方是否为 object 且另外一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。
[] == ![] // true
/* * 首先,布尔操做符!优先级更高,因此被转变为:[] == false * 其次,操做数存在布尔值false,将布尔值转为数字:[] == 0 * 再次,操做数[]是对象,转为原始类型(先调用valueOf(),获得的仍是[],再调用toString(),获得空字符串''):'' == 0 * 最后,字符串和数字比较,转为数字:0 == 0 */
NaN == NaN // false NaN不等于任何值
null == undefined // true
null == 0 // false
undefined == 0 // false
复制代码

全等和不全等

全等和不全等在比较以前不转换类型,因此相对简单:

null === undefined // false
'1' === 1 // false
0 === false // false
[] === [] // false 引用类型比较相等性还要看是否指向同一个内存地址
NaN === NaN // false NaN比较特殊,不等于自身
复制代码

关系操做符

关系操做符小于(<)、大于(>)、小于等于(<=)和大于等于(>=)比较两个值的大小,返回一个布尔值。当有一个操做数是非数值时,就会发生类型转换:

  1. 若是两个操做数都是数值,则执行数值比较。
  2. 若是两个操做数都是字符串,则比较两个字符串对应的字符编码值。
  3. 若是一个操做数是数值,则将另外一个操做数转换为一个数值,而后执行数值比较。
  4. 若是一个操做数是对象,则执行ToPrimitive转为基本类型(先valueOf再toString)。
  5. 若是一个操做数是布尔值,则先将其转换为数值,而后再执行比较。
'23' <'3' // true 比较的是字符编码值
'23' < 3 // false 执行规则3
NaN > 0 // false NaN比较总会返回false
null >= 0 // true 执行规则3,注意null相等性比较和关系比较不同
undefined >= 0  //false undefined执行关系比较会转化为NaN,老是返回false

复制代码

条件操做符

1. 条件操做符

三元表达式就是由条件操做符? :组成:

a > b ? a : b;  // 若是 ? 前的操做求值为 true ,则返回 a ,不然返回 b
复制代码

2. 赋值操做符

js中等号 = 用于赋值操做,var a = 1就是把值1赋值给变量a。能够和 + - * / % 构成复合赋值:

a += b // 等同于 a = a + b
a -= b // 等同于 a = a - b
a *= b // 等同于 a = a * b
a /= b // 等同于 a = a / b
a %= b // 等同于 a = a % b
复制代码

3. 逗号操做符

逗号操做符经常使用于一条语句声明多个变量:var a = 1, b = 2, c;

4. 位操做符

js中数值是以64位格式储存的,前32位是整数,后32位是小数。位操做符会将64位的值转为32位的,因此位操做符会强制将浮点数转为整数。下面说几个经常使用的位操做符:

  1. 位操做符 ~~x至关于-(x+1),能够用来代替indexOf做为判断条件。~str.indexOf('asdf')至关于str.indexOf('asdf')>-1
  2. 位操做符 |:可用于将值截除为一个 32 位整数。1.11 | 0执行结果是1

难以想象的实例

  1. 请听题:
var a = { n: 1 }
var b = a
a.x = a = { n: 2 } // 此时的变量b是什么?
复制代码

我猜你也晕了。此例考察了 运算符优先级 / 引用类型赋值 两个知识点:

首先在第二句中, a 和 b 指向了同一个内存地址 1 ;

而后关键在第三句,根据运算符优先级能够加个括号理解此表达式:a.x = (a = { n: 2 })。因此此时 a.x =在内存地址 1 中开辟了新属性 x ,而这个属性 x 指向了后面括号中表达式的返回值 { n: 2 }

最后括号中的表达式 (a = { n: 2 }) 把变量 a 从新赋值为 { n: 2 } 。此时变量 a 和变量 b 已经割裂,变量 b 仍然指向原地址 1 ,而变量 a 却建立了新对象 { n: 2 } 并做为表达式结果被赋值给了地址 1 中的属性 x ,也就是 b.x 。因此最终对象 b 的值为 { n: 1, x: { n: 2 } } ,而且 b.x === atrue

  1. 请听题:
('b'+'a'+ + 'a'+'a').toLowerCase() === 'banana' // ?
复制代码

这是刚看见的 yck 大佬的文章里的题,很汗颜我居然答错了,我觉得答案是会抛错~~

个人解题思路是 + + 'a'会将字符串a转为 NaN ,而NaN和字符串(把字符串和数值的加性操做搞混淆了)拼接的时候结果仍是NaN,而后调用toLowerCase()方法的时候会抛错~~

错了两处,第一是NaN和字符串拼接结果是'baNaNa',而不是NaN;第二是就算NaN直接调用toLowerCase()方法也不会抛错,而是获得字符串'NaN'~~

结语

JS 的类型转换虽然很让人头疼,但并非无迹可寻。只要掌握了转换规则并多加练习,你就可以判断出来类型到底会如何转换和转换成什么。

相关文章
相关标签/搜索