JavaScript类型显/隐式转换

183 阅读21分钟

JavaScript 的一个显著特点就是灵活,是一门弱语言。灵活的反面就是猝不及防的坑多,定义的变量可以改变类型,数据类型会进行隐式转换等一系列头皮发麻的操作,下面例子你知道答案是什么吗?

(!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]]  
// 答案是啥

由来

MDN介绍过JavaScript 的特点:JavaScript是一种弱类型,或者说是一种动态语言。这意味着你不用提前声明变量的数据类型,在程序运行过程中,变量的数据类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据。

let a = 123  // number类型
a = 'abc'    // string类型
a = () => {} // 对象类型
// a 在赋值中不断的切换类型且不报错

ECMAScript 标准定义了 8 种数据类型

7种基本类型(也就是原始值):

UndefinedNullBooleanNumberStringSymbol(es6)、BigInt(es10)(这里不讨论 SymbolBigInt 两种类型。)

1种对象类型:

Object

不同形式的值对应的类型不一样,如果我们想将值从一种类型转换为另一种类型就需要类型转换。

数据类型间的转换可分为:

  • 原始值间的转换:转换为Boolean、Number、String
  • 原始值转换为对象:转换为Object
  • 对象转换为原始值:有两种转换方式

JS的类型转换共有两种

  • 显示类型转换:指在原始值和包装对象或者运算符结合后,手动显式的改变了类型
  • 隐式类型转换:指在代码执行过程中,通过运算符运算或语句执行等操作,js 引擎会自动隐式的改变类型

显示转换

显示类型转换是通过 JS 提供的一些函数或运算符,可以直接将类型进行转换

  • 转换为字符串:toString() 或 String()
  • 转换为数值:Number()、parseInt()、parseFloat()
  • 转换为布尔值:Boolean()
  • 转换为对象:Object()
String(false) // "false" 布尔值转换为字符串
Number("123") // 123 字符串转换为数值 
Boolean([])   // true 对象类型转换为布尔值
Object(123)   // new Number(123) 原始值通过Number()构造函数转换为基本包装类型

(1)转数字类型

其他类型转换为数字类型的操作

  • null:转为0
  • undefined:转为NaN
  • 字符串:
    • 纯数字:转为对应的数字
    • 空字符串:转为0
    • 其他一律按转换失败处理,转为NaN
  • 布尔型:
    • true:转为1
    • false:转为0
  • 数组:数组首先会被转为原始类型,也就是ToPrimitive,然后再根据转换后的原始类型按照上面的规则处理。
  • 对象:同数组的处理
  Number(null) // 0
  Number(undefined) // NaN
  Number('10') // 10
  Number('10a') // NaN
  Number('') // 0 
  Number(true) // 1
  Number(false) // 0
  Number([]) // 0
  Number(['1']) // 1
  Number({}) // NaN
  
  // 数组
Number([]) // 0
Number([1]) // 1
Number([1, 2]) // NaN

// 日期时间函数
Number(new Date()) // 1607334260466时间戳

// 对象
Number({}) // NaN

// 正则
Number(new (RegExp(/\s/))) // NaN

对象转换为数字类型过程会调用对象.valueOf() 原始值

parseFloat()和parseInt()

parseInt() 解析字符串中的数字和Number()将字符串强制类型转换为数字的返回结果都是数字。但是解析和转换两者之间还是有明显的差别, 而且 parseInt() 第二个为可选参数可以转化为不同进制的数,默认是转化为十进制的数。

parseInt() 解析允许字符串中含有非数字字符,比如"42px",解析按从左到右的顺序,如果遇到非数字字符“p”就停止。而Number()转换不允许出现非数字字符,否则会失败返回NaN

var a = "42"
var b = "42px"

Number(a)   //42
parseInt(a) //42
parseInt(4.6) //4
parseFloat(3.67) //3.67

Number(b)   //NaN
parseInt(b) //42

parseInt("010101",2) // 21

解析字符串中的浮点数可以使用parseFloat()函数。从ES5开始 parseInt() 默认转换为十进制数,除非指定第二个参数作为基数

parseInt()针对的是字符串,向parseInt()传递数字和其他类型的参数是没有用的。非字符串会首先被强制类型转换为字符串,应该避免向parseInt()传递非字符串参数

parseInt(1/0,19)    //18
// 以上答案是不是很诡异

parseInt(1/0,19) 最后的结果是18,而非报错,因为parseInt(1/0,19)实际上是parseInt(“Infinity”,19)。基数19,它的有效数字字符范围是0-9和a-i(区分大小写),以19为基数时,第一个字符"I"值为18,而第二个字符"n"不是一个有效的数字字符,解析到此为止,和"42px"中"p"一样

(2)转字符串类型

其他类型转换位字符串类型的操作。

  • null:转为"null"
  • undefined:转为"undefined"
  • 布尔类型:
    • true:转为"true"
    • false:转位"false"
  • 数字类型:转为数字的字符串形式,如10转为"10"1e21转为1e+21
  • 数组:转为字符串是将所有元素按照","连接起来,相当于调用数组的Array.prototype.join()方法,如[1, 2, 3]转为"1,2,3",空数组[]转为空字符串,数组中的nullundefined,会被当做空字符串处理
  • 普通对象:转为字符串相当于直接使用Object.prototype.toString(),返回"[object Object]"
  String(null) // 'null'
  String(undefined) // 'undefined'
  String(true) // 'true'
  String(10) // '10'
  String(1e21) // '1e+21'
  String([1,2,3]) // '1,2,3'
  String([]) // ''
  String([null]) // ''
  String([1, undefined, 3]) // '1,,3'
  String({}) // '[object Objecr]'
  
String(01)  // '1'
String(011) // '9'
String(123) // '123'
String(999999999999999999999) // "1e+21"

String([1, 2]) // "1,2" String([1, 2]) === [1, 2].join()
String([1, undefined, 2]) // "1,,2"

// 函数
String(function() {}) // "function() {}"
String(class A{}) // "class A{}"

// 日期时间函数
String(new Date()) // "Mon Dec 07 2020 17:00:03 GMT+0800 (中国标准时间)"

// 正则表达式
String(/\s/) // "/\s/"
String(new RegExp(/\s+/)) // "/\s+/"

注意:上面所说的规则是在默认的情况下,如果修改默认的toString()方法,会导致不同的结果

注意:Number类定义的 toString() 方法可以接受表示转换基数的可选参数,如果不指定此参数,转换规则将是默认基于十进进制

任何类型与字符串相加都得到的是字符串

(3)转为Boolean类型

指其他类型转为布尔类型的操作

js中的假值只有falsenullundefined空字符+0-0NaN,其它值转为布尔型都为true

  Boolean(null) // false
  Boolean(undefined) // false
  Boolean('') // flase
  Boolean(NaN) // flase
  Boolean(0) // flase
  Boolean([]) // true
  Boolean({}) // true
  Boolean(Infinity) // true

隐式转换

当运算符在运算时,两边数据不统一,编译器会自动将两边数据进行数据类型转换成统一的再计算。

ToPrimitive规则

指对象类型类型(如:对象、数组)转换为原始类型的操作。即对象的隐式转换规则(ToPrimitive)

对于多数情况来说,对象隐式转换成字符串数字,其实调用了一个叫做ToPrimitive(obj,preferredType)的内部方法来干这件事情,此抽象方法将对象值转换为相应的基本类型值。

Js引擎内部的抽象操作ToPrimitive(转换为原始值)的方法大体如下:

/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)

type 表示期望对象转换为的原始数据类型,分为 NumberString

ToPrimitive的规则:

  • 当对象类型需要被转为原始类型时,它会先查找对象的valueOf方法(即preferredType值是number),如果valueOf方法返回原始类型的值,则ToPrimitive的结果就是这个值

  • 如果valueOf不存在或者valueOf方法返回的不是原始类型的值,就会尝试调用对象的toString方法(即preferredType值是string),也就是会遵循对象的ToString规则,然后使用toString的返回值作为ToPrimitive的结果。

注意:对于不同类型的对象来说,ToPrimitive的规则有所不同,比如Date对象会先调用toString,具体可以参考ECMA标准

如果valueOftoString都没有返回原始类型的值,则会抛出TypeError 异常。

  Number([]) // 0
  Number(['10']) //10

  const obj1 = {
    valueOf () {
      return 100
    },
    toString () {
      return 101
    }
  }
  Number(obj1) // 100

  const obj2 = {
    toString () {
      return 102
    }
  }
  Number(obj2) // 102

  const obj3 = {
    toString () {
      return {}
    }
  }
  Number(obj3) // TypeError

常用内置对象调用toString()和valueOf()的返回情况

image.png

注意: valueOf 及 toString方法是可以被重写的

const foo = {
  toString() {
    return 'sunshine'
 },
 valueOf() {
    return 1
  }
}
console.log(String(foo))
console.log(1 + foo) // 2

// 第一个console 打印结果为 sunshine。这里就涉及隐式转换,在console 打印的时候,倾向于使用 foo 对象的 toString 方法,将 foo 转换为基本数据类型
// 第二个console 打印结果为 2,这时候的隐式转换则倾向于使用 foo 对象的 valueOf 方法,将 foo 转换为基本数据类型,以执行相加操作

上面代码则对foo对象的 valueOf 和toString 进行了重写

解析:

前面说过,对象类型在ToNumber时会先ToPrimitive,再根据转换后的原始类型ToNumber

  • Number([]), 空数组会先调用valueOf,但返回的是数组本身,不是原始类型,所以会继续调用toString,得到空字符串,相当于Number(''),所以转换后的结果为"0"
  • 同理,Number(['10'])相当于Number('10'),得到结果10
  • obj1valueOf方法返回原始类型100,所以ToPrimitive的结果为100
  • obj2没有valueOf,但存在toString,并且返回一个原始类型,所以Number(obj2)结果为102
  • obj3toString方法返回的不是一个原始类型,无法ToPrimitive,所以会抛出错误

valueOf()

  • 基本包装类型直接返回原始值
  • Date 类型返回毫秒数
  • 其他都返回对象本身:由于大多对象是复合值,无法真正表示为一个原始值,因此返回对象本身。
// 1. 基本包装类型直接返回原始值

var num = new Number('123');
num.valueOf(); // 123

var str = new String('123abc');
str.valueOf(); // '123abc'

var bool = new Boolean('abc');
bool.valueOf(); // true

// 2. Date 类型返回一个内部表示:1970年1月1日以来的毫秒数

var date = new Date();
date.valueOf(); // 1608784980823

// 3. 返回对象本身

var obj = new Object({});
obj.valueOf() === obj; // true

var arr = new Array();
arr.valueOf() === arr; // true

var reg = new RegExp(/a/);
reg.valueOf() == reg; // true

var func = function() {};
func.valueOf() == func; // true

// 单体内置类型
global.valueOf() == global; // true
Math.valueOf() == Math; // true

toString()

作用是返回一个反应该对象的字符串。

  • 基本包装类型直接返回原始值
  • 默认的 toString() 并不会返回看起来有直观意义的值,例如 [object Object]
  • 但很多类都有实现各自版本的 toString(),例如日期、数组、正则表达式、函数。
Number.prototype.hasOwnProperty('toString');   // true
String.prototype.hasOwnProperty('toString');   // true 
Boolean.prototype.hasOwnProperty('toString');  // true
Date.prototype.hasOwnProperty('toString');     // true 返回可读的日期和时间字符串
Array.prototype.hasOwnProperty('toString');    // true 将每个元素转换为字符串
RegExp.prototype.hasOwnProperty('toString');   // true 返回表示正则表达式的字符串
Function.prototype.hasOwnProperty('toString'); // true 返回这个函数定义的 Javascript 源代码字符串

// 1. 基本包装类型返回原始值

var num = new Number('123abc');
num.toString(); // 'NaN'

var str = new String('123abc');
str.toString(); // '123abc'

var bool = new Boolean('abc');
bool.toString(); // 'true'

// 2. 默认的 toString()

var obj = new Object({});
obj.toString();   // "[object Object]"
global.toString() // "[object Window]"
Math.toString();  // "[object Math]"

// 3. 类自己定义的 toString()

// Date类型转换为可读的日期和时间字符串
var date = new Date();
date.toString(); // "Wed Oct 11 2017 08:00:00 GMT+0800 (中国标准时间)"

// 将每个元素转换为字符串
var arr = new Array(1,2);
arr.toString(); // '1,2'

// 返回表示正则表达式的字符串
var reg = new RegExp(/a/);
reg.toString(); // "/a/"

// 返回这个函数定义的 Javascript 源代码字符串
var func1 = function () {}
func1.toString(); // "function () {}"

function func2() {}
func2.toString(); // "function func2() {}"

对于日期类型,转换为日期的字符串形式比毫秒数来得有意义,因此 type 默认为 String。

在代码中,常用到的隐式转换:

x + ""  // 等价于 String(x)
+x      // 等价于 Number(x),也可以写成 x - 0
!!x     // 等价于Boolean(x)
!x      // 转换为布尔值,并取反

隐式转换情况

(1)逻辑语句

逻辑语句的类型转换:当使用if、while、for 时,隐式转换为布尔值;

      if ({}) {
        console.log(1);
      }
      //1
      if (null) {
        console.log(1);
      } else {
        console.log(2);
      }
      //2

(2)逻辑表达式

  • ! 逻辑非,隐式转换为布尔值,并取反,!!两次逻辑非,隐式转换为布尔值;
  • || 和 && 会将非布尔值操作数,隐式转换为布尔值,再判断;

非布尔类型会转为布尔类型

  • a&&b如果a为true,则会返回b;如果a为false,则会返回a
  • a||b如果a为true,则会返回a;如果a为false则会返回b
  • !a如果a为布尔值,则直接取反,如果a为非布尔值,则会转换为布尔值然后取反
  • 引用数据类型转换为布尔值后总会是true
1&&2
//2
[]&&2
//2
2&&{}
//{}
![]
//false
!{}
//false

(3)算术表达式

==运算符

宽松相等(==)严格相等(===)的区别在于宽松相等会在比较中进行隐式转换

不同类型间的转换规则:

  • null == undefinedtrue,和其他的比较都为false
    • null与undefined进行==比较时不会进行转换,总返回true。
  null == false // false
  undefined == false // false

为什么呢? 首先,false转为0,然后呢? 没有然后了,ECMAScript规范中规定nullundefined之间互相宽松相等(==),并且也与其自身相等,但和其他所有的值都不宽松相等(==)

Javascript规定nullundefined宽松相等(==),并且都与自身相等,但是与其他所有值都不宽松相等。

null == null   //true
undefined == undefined  //true
null == undefined  //true
null == 0  //false
null == false  //false
undefined == 0  //false
undefined == false  //false
null == []  //false
null == {}  //false
unfefined == []  //false
undefined == {}  //false
复制代码
  • 如果一个值是数字,另一个是字符串,先将字符串转换为数字,再进行比较
    • NaN 与所有值都不相等,包括它自己,可使用 isNaN() 来判断一个值是否是数字。
    • 数字间比较,以0开头是8进制
console.log(NaN=="dsdd");  // false 字符串"dsdd"转为数字为NaN,但是NaN != NaN
console.log(012==10);  // true
console.log(099==99);  // true 这种情况是因为八进制中不可能出现9,所以看成一个十进制
  • 如果一个值是布尔值,将布尔值转换为数字再比较;
true == '2'  // false, 先把 true 变成 1, '2' 变成 2
  • 对象类型原始类型做相等比较时,对象类型会依照ToPrimitive规则转换为原始类型
  '[object Object]' == {} // true
  '1,2,3' == [1, 2, 3] // true

例子解析

eg1:

  [null] == 0 // true
  [undefined] == 0 // true
  [] == 0 // true

根据上文中提到的数组ToString操作规则,数组元素为nullundefined时,该元素被当做空字符串处理,而空数组[]也被转为空字符串,所以上述代码相当于

  '' == 0 // true
  '' == 0 // true
  '' == 0 // true

空字符串会转换为数字0,所以结果为true

注意,同类型比较时,对象间除非指针一样,否则它就是两个不同的对象

  • 如果==左右都是引用数据类型,会进行地址比较
  • 其他不同类型比较均不相等
undefined==null
//true
0==[]
//true
[]==[]
//false 两个数组的地址指向不同

eg2

var a = {
  valueOf: function () {
     return 1;
  },
  toString: function () {
     return '123'
  }
}
true == a // true;
首先,x与y类型不同,x为boolean类型,则进行ToNumber转换为1,为number类型。
接着,x为number,y为object类型,对y进行原始转换,ToPrimitive(a, ?),没有指定转换类型,默认number类型。
而后,ToPrimitive(a, Number)首先调用valueOf方法,返回1,得到原始类型1。
最后 1 == 1, 返回true

eg3:

[] == !{}
//
1、! 运算符优先级高于==,故先进行!运算。
2、!{}运算结果为false,结果变成 [] == false比较。
3、根据上面第7条,等式右边y = ToNumber(false) = 0。结果变成 [] == 04、按照上面第9条,比较变成ToPrimitive([]) == 0。
    按照上面规则进行原始值转换,[]会先调用valueOf函数,返回this。
   不是原始值,继续调用toString方法,x = [].toString() = ''。
   故结果为 '' == 0比较。
5、根据上面第5条,等式左边x = ToNumber('') = 0。
   所以结果变为: 0 == 0,返回true,比较结束。

eg3面试题:

定义一个变量a,使得下面的表达式结果为true

a == 1 && a == 2 && a == 3

答案: 对象每次和原始类型做==比较时,都会进行一次ToPrimitive操作,那我们是不是可以定义一个包含valueOf方法的对象,然后通过某个值的累加来实现?

  const a = {
    // 定义一个属性来做累加
    i: 1,
    valueOf () {
      return this.i++
    }
  }
  a == 1 && a == 2 && a == 3 // true

当然,当没有定义valueOf方法时,用toString方法也是可以的

  const a = {
    // 定义一个属性来做累加
    i: 1,
    toString () {
      return this.i++
    }
  }
  a == 1 && a == 2 && a == 3 // true

eg4

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('hello world!');
}
  • 当执行a == 1 && a == 2 && a == 3 时,会从左到右一步一步解析,首先 a == 1,会进行上面第9步转换。ToPrimitive(a, Number) == 1。
  • ToPrimitive(a, Number),按照上面原始类型转换规则,会先调用valueOf方法,a的valueOf方法继承自Object.prototype。返回a本身,而非原始类型,故会调用toString方法。
  • 因为toString被重写,所以会调用重写的toString方法,故返回1,注意这里是i++,而不是++i,它会先返回i,在将i+1。故ToPrimitive(a, Number) = 1。也就是1 == 1,此时i = 1 + 1 = 2。
  • 执行完a == 1返回true,会执行a == 2,同理,会调用ToPrimitive(a, Number),同上先调用valueOf方法,在调用toString方法,由于第一步,i = 2此时,ToPrimitive(a, Number) = 2, 也就是2 == 2, 此时i = 2 + 1。
  • 同上可以推导 a == 3也返回true。故最终结果 a == 1 && a == 2 && a == 3返回true
+运算符

+号比较特殊,既可以当做算数运算符做加法,又可以当做字符串连接符

(1)基本类型

+ 运算计算 string 类型其他数据类型相加时,其他数据类型都会转换为 string 类型;而在其它情况下都会转换为 number 类型,但是 undefiend 类型会转换为 NaN,相加结果也是 NaN

eg1

1 + '1' // 11
1 + true // 2
1 + false // 1
1 + undefined // NaN
'sunshine' + true // sunshinetrue

eg2

1+null
//1
1+undefined
//NaN
1+true
//2
true + null
//1
true + undefined
//NaN
(2)引用类型

当使用 + 运算符时,如果存在引用数据类型,那么它将会被转换为基本类型之后再进行运算。这就涉及对象的转换规则

{} + true  // [Object Object] true

对于加法操作,+运算符在 JS 语法解析中存在二义性

  • 如果 +运算符两边存在 NaN,则结果为 NaN
  • 如果是 Infinity + (-Infinity),则结果为NaN
  • 如果 -Infinity + (-Infinity),则结果是 -Infinity
  • 如果 Infinity + (-Infinity),则结果是 NaN

如果 + 运算符两边有或至少一个是字符串,则其规则如下

  • 如果 +运算符两边都是字符串,则执行字符串拼接操作
  • 如果 +运算符两边只有一个是字符串,则将另外的值转换为字符串,再执行字符串拼接操
  • 如果 +运算符两边有一个是对象,则调用 valueOf 或 toString 方法取得值,将其转换为基本类型在进行字符串拼接
  • 字符串于任意类型最后得到的都是字符串

总结: 1.算数运算符 (除string类型外的原始数据类型进行加法运算时)非数字类型,会转为数字类型 2.字符串连接符(string类型以及引用数据类型时),非string类型会转为string类型

eg

[]+[]
//""
[]+{}
//"[object Object]"
true+[]
//"true"
true+{}
//"true[object Object]"
undefined+[]
//"undefined"
undefined + {}
//"undefined[object Object]"
大部分运算符

递增递减++ --,一元加减 +a、- a(结果的符号取反),二元 -,乘法,除法,求模,隐式转换为 Number;

  • 非数字类型会转为数字类型
  • 如果是原始数据类型会调用Number()方法进行转换
  • 如果是引用数据类型会调用自身valueOf方法进行转换,如果转换后不是原始值,则会调用toString方法进行转换,如果转换后不是数字,则会调用Number()进行转换,如果转换后不是数字则会返回NaN。

eg1

10-'1'
// 9
10-true
// 9
10-[]
//10
10-null
//10
10-undefined
//NaN
10-{}
//NaN

eg2

// + 运算符
({} + {}) = ?
两个对象的值进行+运算符,肯定要先进行隐式转换为原始类型才能进行计算。
1、进行ToPrimitive转换,由于没有指定PreferredType类型,{}会使默认值为Number,进行ToPrimitive(input, Number)运算。
2、所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
3、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
故得到最终的结果,"[object Object]" + "[object Object]" = "[object Object][object Object]"

// + 运算符
1 + {} = ? // "1[object Object]" 
对象(到原始值的转换)转换为字符串后进行连接
分析:
1. {} => "[object Object]" 规则1:如果其中一个操作符是对象,转换为原始值,此时为 1 + "[object Object]"
2. 1 => "1" 规则2:转换后,若其中一个操作数是字符串,另外一个也转换为字符串,再进行连接,此时为 "1[object Object]"

// == 运算符
"1" == true // true
分析:
1. true => 1 规则3:如果一个值是布尔值,转换为数字再比较。 此时转换为 "1" == 1
2. "1" => 1 规则2:如果一个值是数字,另一个值是字符串,则字符串转换数字再比较,此时 1 == 1

// * 运算符
["123"] * {} // NaN
分析:
1. 乘*:两边的操作符会隐式转换为 Number
2. ["123"] 执行对象到数字的转换,type 为 Number
   2.1 ["123"] 先执行 valueOf() 返回对象本身
   2.2 再通过 toString() 得到原始值(字符串) "123",进一步转换为数字 123
3. {} 执行对象到数字的转换,type 为 Number
   3.1 同理得到原始值(字符串) "[object Object]",进一步转换为数字 NaN
4. 结果 123 * NaN = NaN

// * 运算符 2 * {} = ?
1、首先*运算符只能对number类型进行运算,故第一步就是对{}进行ToNumber类型转换。
2、由于{}是对象类型,故先进行原始类型转换,ToPrimitive(input, Number)运算。
3、所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
4、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
5、转换为原始值后再进行ToNumber运算,"[object Object]"就转换为NaN。
故最终的结果为 2 * NaN = NaN
一元 + - 运算符
(1)字符串转数字
 + "3.14" // 3.14
 + "22"   // 22
 - "3.14" // -3.14
 - "22"   // -22

+"3.14"是+运算符的一元形式(即只有一个操作数)。+运算符显式地将 ”3.14“ 转换为数字,而非数字加法运算(也不是字符串拼接)

一元运算符 - 和 + 一样,会反转数字的符号位。由于 – 会被当作递减运算符来处理,所以我们不能使用 – 来撤销反转,而应该像 - -"3.14"这样,在中间加一个空格

尽量不要把一元运算符 + (还有 - )和其他运算符放在一起使用

(2)日期转数字
var time = new Date()
+time  

一元运算符 + 的另一个常见用途是将日期(Date)对象强制类型转换为数字,返回结果为Unix时间戳

~位运算符
(1)转数字

Javascript 按位取反位运算符 (位非)~ ,对一个表达式执行位非(求非)运算。如 ~1 = -2 ; ~2 = -3 ; ~99 = -100;

运算符查看表达式的二进制表示形式的值,并执行位非运算。

~5 // -6
// 5 二进制 101,补满 32位
00000000000000000000000000000101
// 按位取反
11111111111111111111111111111010
// 由于32位开头第一个是1,所以这是一个负数,将二进制转换成负数,需要先反码
00000000000000000000000000000101
// 之后,再+1
00000000000000000000000000000110
// 转换成十进制为6,加上符号变成负数 -6
(2)转布尔值

在 JavaScript 中有些函数用 -1 来代表执行失败,用大于等于0的值来代表函数执行成功。比如,indexOf()方法在字符串中搜索指定的字符串,如果找到就返回子字符串的位置,否则返回-1

var a = "Hello World"
if(a.indexOf("lo") != -1){
    // 找到匹配
}

if(a.indexOf("ol") == -1){
    // 没有找到匹配
}

~和 indexOf() 一起可以将结果强制类型转换为真/假值,如果indexOf()返回-1,~将其转换为假值0,其他情况一律转换为真值

var a = "Hello World"
~a.indexOf("lo")    // -4 ==>真值
if(~a.indexOf("lo")){
    // 找到匹配
}
~a.indexOf("ol")   // 0 ==>假值 
if(!~a.indexOf("ol")){
    // 没有找到匹配
}
(3)~~字位截除

~~x 能将值截除为一个整数,~~只适用于数字,更重要的是它对负数的处理与Math.floor()不同, 一般也是用于取整操作,性能比 Math.floor() 好

Math.floor(-49.6)   // -50
~~-49.6 //-49