JS数据类型的强制转换

2,152 阅读9分钟

想要搞明白数据类型的转换,首先得清楚js有哪几种数据类型

1.数据类型

ES最新标准现共有8种数据类型,其中基本数据类型7种,引用数据类型1种

基本数据类型

  • Boolean 布尔值,只含有两个关键字(true,false)
  • String 字符串
  • Undefined 一个声明的但未定义的值,拥有全局属性undefined来表示,之所以说undefined为全局属性,而非关键字,是因为在非严格模式下,函数作用域内可以对undefined进行重新赋值,因此会产生bug。建议使用void 0来安全获取undefined值。
  • Null 定义了但为空,只有一个值,关键字null。该类型有个历史遗留问题,那就是typeof null === 'object',使用该判断条件时,要注意。
  • Symbol es6新增,可用于定义唯一的值
  • Number 数字类型,代表双精度64位浮点数,其中还包含了+Infinity,-Infinity 和 NaN的值
  • BigInt 代表数值比较大的整数,甚至可以超过数字的安全整数限制。BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。

引用数据类型

  • Object 拥有数据属性和访问器属性

2.强制类型转换

js中的强制类型转换主要指的是基本数据类型之间值的转换,而基本数据类型转成引用数据类型,则是一种封装,并非实际意义的类型转换。

2.1 ToString (其它数据类型转换成String)

规则:

类型 原值 string值
Boolean true | false 'true' | 'false'
Undefined undefined 'undefined'
Symbol Symbol('name') 'Symbol(name)'
Number 22 '22'
Null null 'null'
Object {name:'bob'} '[object Object]'

方法:(显式或隐式,只是相对的说法,如果你很清楚这里提到的隐式方法,那么它对你来说就是显式的)

  • 显式方法

    原生函数String(),如String(true)

  • 隐式方法

    1. 利用+号运算符,其中只要运算符两侧有一个操作数为字符串,该运算符将是字符串拼接功能,那么另一个操作数就会进行字符串的类型转换
      var a = 'string' + 234 // 'string234'

      var b = 'string' + null // 'stringnull'

      var c = 'string' + {}   //'string[object Object]'

      //实际应用

      var d = true + ''   'true'

    1. 除了Undefined、Null数据类型外,其它都可以使用toString()方法,如true.toString(),这里涉及到基本数据类型的封装,后面会详细介绍。

2.2 ToNumber(其它数据类型转成Number)

规则:

类型 原值 number值
Boolean true | false 1 | 0
Undefined undefined NaN
Symbol Symbol('name') 不能转换
string '22' | '23fds' | '' 22 | NaN | 0
Null null 0
Object {name:'bob'} NaN

方法:

  • 显式方法

    原生函数Number(),如Number('234'),这个需要注意一下StringToNumber,如果我们传入的不是只包含数字的字符串,如'234fasd',那么Number函数会将其转成NaN。但还有个函数却是另一种行为。

      parseInt('234fasd',10)  // 234
      Number('234fasd')    //NaN

    parseInt是全局对象的一个属性,在浏览器中也就是window对象的方法。它看似也实现了数据类型的转换,但实则不是,它是一种数据的解析,并且只能正常解析数字和字符串的数据类型。当传入上述中既包含数字也包含字母的字符串时,它会判断,当遇到第一个非数字的字符时,那么就会将后面的字符全抛弃掉,然后转化之前全为数字的字符串,该函数第二个参数为进制数,默认以十进制,来解析传入的值。

  • 隐式方法

    1. 在所要转换的数据前加上~~,~是一个取反运算法,这种方式的感觉就像负负得正一样。
      ~~'2434'  // 2434
      ~~null    // 0
    1. +运算符,这个并不是用来计算数字加减或字符串拼接用的那个,而是直接放在被转换数据的前面
      +'123' // 123
      +true  // 1
    1. -、 * 、 / 、 %运算符,和转字符串的+号运算符,道理类型,若两侧的操作数中含有非numbe类型时,会先转换成number,再运算
      var a = '123' - '3'   // 120
      var b = '45' - 5      // 40
      var c = 60 - undefined     // NaN
      
      //实际使用
      var c = '123' - 0     // 123

2.2 ToBoolean(其它数据类型转成Boolean)

规则:

类型 原值 boolean值
Number 非0 | 0 true | false
Undefined undefined false
Symbol Symbol('name') true
string '22sd' | '' true | false
Null null false
Object {name:'bob'} | {} | [] true | true | true

方法:

  • 显式方法

    原生函数Boolean()。如Boolean('test'),

  • 隐式方法

    1. 在转换的数据前加上!!,如!!'test'
      !'test'  // false
      !!'test' // true
      
    1. 各种条件判断语句。
    • if()
    • else if()
    • while()
    • ?:, 左侧操作数
    • && 、 || 左侧的操作数

    这些都会对传入的数据进行转Boolean操作

2.3 ToObject (其它数据类型转成Object)

这个一开始提到过,其它基本数据类型转成引用数据类型,并不是真正的数据转换,而是一种封装。

什么是封装

举个例子我们通常会写这么一句代码 ' thisString '.trim() 来清除字符串左右两侧的空格,但你有没有想过,.运算符明明是作用在Object上,用来访问该对象的属性和方法的,为什么字符串这种基本数据类型也可以这么用,答案就是封装对象。

除了普通的对象外,其实还有四种基本包装对象,分别对应着基本数据类型中的Number,String,Boolean,Symbol,除了Symbol,另外三种我们可以通过new 来获取到相应的实例对象。

例如: let num = new Number(234)

那么上面那句代码,真实执行是这样的

  ' thisString '.trim();
  
  // 内部实际
  var str = new String(' thisString ');
  str.trim();
  str = null;
  

也就是它会创建一个基本包装对象,然后调用该对象上的方法,执行完成,销毁这个包装对象。那么这种基本包装对象的生命周期只存在于代码执行的那么一瞬间,之后我们不可以在运行的时候为其添加方法与属性。例如:

var s = 'this String';
s.trim();
// 这样是没什么用的
s.name = 'job'
console.log(s.name) //undefined

封装方法

直接使用js提供的Object原生函数,当传入的是上面那四种基本数据类型时,它会转成对应的基本包装对象

2.4 ObjectToOther (引用类型转基本数据类型)

与前面封装的操作相反,对象转成基本数据类型,则是拆封。在JavaScript 标准中,规定了 ToPrimitive 函数,来实现拆封。

拆封的方法就是Number()、String()、Boolean这些用来转类型的原生函数

具体的拆封过程,其实是涉及到对象valueOftoSting函数的调用,当将一个对象转换基本数据类型时,会先调用该对象的valueOf,如果能返回基本数据类型,则进行后续的数据类型转换,否则,就继续调用toString,拿到字符串,然后进行数据类型转换。如果这两个方法都没返回基本数据类型,则抛出异常,写段代码,就能清晰明白了。

先定义一个返回基本数据类型的valueOf方法的对象

// 先定义一个对象,自定义valueOf和toString方法
var a = {
    valueOffunction(){
      console.log('调用了valueOf方法')
      return '123'
    },
    toStringfunction(){
      console.log('调用了toString方法')
      return '123'
    }
}

// 使用之前转number的原生函数来拆箱
Number(a)  //输出  调用了valueOf方法  123

// 只执行了valueOf方法

定义一个返回引用数据类型的valueOf方法的对象

// 先定义一个对象,自定义valueOf和toString方法
var a = {
    valueOffunction(){
      console.log('调用了valueOf方法')
      return {}
    },
    toStringfunction(){
      console.log('调用了toString方法')
      return '123'
    }
}

// 使用之前转number的原生函数来拆箱
Number(a)  //输出  调用了valueOf方法 调用了toString方法  123

// 执行了valueOf方法后,又执行了toString方法

定义一个valueOf,toString都不返回基本数据类型的对象

// 先定义一个对象,自定义valueOf和toString方法
var a = {
    valueOffunction(){
      console.log('调用了valueOf方法')
      return {}
    },
    toStringfunction(){
      console.log('调用了toString方法')
      return {}
    }
}

// 使用之前转number的原生函数来拆箱
Number(a)  //输出  调用了valueOf方法 调用了toString方法  

/* Uncaught TypeError: Cannot convert object to primitive value
    at Number (<anonymous>)
    at <anonymous>:13:1 */

    
// // 执行了valueOf方法后,又执行了toString方法,然后抛出了错误

这就是一个拆箱,ToPrimitive 函数的过程。

有一种特殊情况,如果是使用String()函数,它会直接调用对象的toString方法,而不是先valueOf()

在封装对象那节里说到的4种基本包装对象,它们都实现了自己的valueOf和toString方法,因此在拆箱时,能正常返回其基本数据的值。

还有一个知识点,当我们对普通对象转String时,会看到这个东西

   var  a = {}
   a.toString()   //"[object Object]"
   
   var b = new Set()
   b.toString()   //"[object Set]"
   

其中第二个Object和Set,实际是对象的一个私有属性[[Class]]的值,这个值并不能直接访问并且修改,只能通过Object.prototype.toString() 来获取

  Object.prototype.toString.call({})  //"[object Object]"
  
  Object.prototype.toString.call([])  //"[object Array]"
  
  Object.prototype.toString.call(new Set())  //"[object Set]"
  
  Object.prototype.toString.call(123)  //"[object Number]"
  

这样看来这个Class应该是代表了一个数据的详细种类

最后

如果该文对您有帮助的话,请点个赞哦😯

文中若有错误,欢迎指正;若您有补充,欢迎留言。