强制类型转换

强制类型转换

1、值类型转换

  • 将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。
  • 类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。

2、抽象值操作

2.1、ToString

抽象操作ToString,它负责处理非字符串到字符串的强制类型转换。

  • 基本类型值的字符串化规则为:null转换为"null", undefined转换为"undefined", true转换为"true"。数字的字符串化则遵循通用规则,不过极小和极大的数字使用指数形式。1.07e21

  • 普通对象来说,除非自行定义,否则toString()(Object.prototype.toString())返回内部属性[[Class]]的值,如"[object Object]"。

  • 数组的默认toString()方法经过了重新定义,将所有单元字符串化以后再用", "连接起来。[1,2,3].toString() // "1,2,3"

  • JSON字符串化

    • 所有安全的JSON值(JSON-safe)都可以使用JSON.stringify(..)字符串化。安全的JSON值是指能够呈现为有效JSON格式的值。

    • 不安全的JSON值。undefined、function、symbol(ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合JSON结构标准,其他支持JSON的语言无法处理它们。JSON.stringify(..)在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)

    • 如果要对含有非法JSON值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义toJSON()方法来返回一个安全的JSON值。toJSON()应该“返回一个能够被字符串化的安全的JSON值”,而不是“返回一个JSON字符串”。

      var a = {
          val: [1,2,3],
          toJSON: function(){
              return this.val.slice(1)
          }
      }
      JSON.stringify(a) // "[2,3]"
      

2.2 ToNumber

  • true转换为1, false转换为0。undefined转换为NaN, null转换为0。
  • ToNumber对字符串的处理基本遵循数字常量的相关规则/语法。处理失败时返回NaN(处理数字常量失败时会产生语法错误)。不同之处是ToNumber对以0开头的十六进制数并不按十六进制处理(而是按十进制)
  • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。为了将值转换为相应的基本类型值,抽象操作ToPrimitive(参见ES5规范9.1节)会首先(通过内部操作DefaultValue,参见ES5规范8.12.8节)检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用toString()的返回值(如果存在)来进行强制类型转换。如果valueOf()和toString()均不返回基本类型值,会产生TypeError错误。
  • 从ES5开始,使用Object.create(null)创建的对象[[Prototype]]属性为null,并且没有valueOf()和toString()方法,因此无法进行强制类型转换。

2.3 ToBoolean

  • 假值(falsy value)• undefined• null• false• +0、-0和NaN• ""
  • 从逻辑上说,假值列表以外的都应该是真值(truthy)。
  • 假值对象(falsy object):浏览器在某些特定情况下,在常规JavaScript语法基础上自己创建了一些外来(exotic)值,这些就是“假值对象”。最常见的例子是document.all,它是一个类数组对象,包含了页面上的所有元素,由DOM(而不是JavaScript引擎)提供给JavaScript程序使用。它以前曾是一个真正意义上的对象,布尔强制类型转换结果为true,不过现在它是一个假值对象。document.all并不是一个标准用法,早就被废止了。

3、显式强制类型转换

3.1 字符串和数字之间的显式转换

字符串和数字之间的转换是通过String(..)和Number(..)这两个内建函数(原生构造函数)来实现的,请注意它们前面没有new关键字,并不创建封装对象。

  • 一元运算符+会将操作数显式强制类型转换为数字

    • 日期显式转换为数字 var timestamp = +new Date (获取时间戳 更推荐getTime()Date.now())
  • 一元运算符-和+一样,并且它还会反转数字的符号位。由于--会被当作递减运算符来处理,所以我们不能使用--来撤销反转,而应该像- -"3.14"这样,在中间加一个空格,才能得到正确结果3.14。

  • 奇特的~运算符 它首先将值强制类型转换为32位数字,然后执行字位操作“非”(对每一个字位进行反转)。

    • ~x大致等同于-(x+1)。在-(x+1)中唯一能够得到0(或者严格说是-0)的x值是-1。也就是说如果x为-1时,~和一些数字值在一起会返回假值0,其他情况则返回真值。-1是一个“哨位值”,哨位值是那些在各个类型中(这里是数字)被赋予了特殊含义的值。例如indexOf(..)哨位值-1。
    • 如果indexOf(..)返回-1, ~将其转换为假值0,其他情况一律转换为真值。
  • 字位截除

    • 值为正数时直接截取整数部分,Math.floor()与~~一致。

    • 值为负数时,小数位不为0,Math.floor()会为1,~~还是直接截取

    • Math.floor(-52.3) // -53
      ~~-52.3 // -52
      

3.2 显式解析数字字符串

解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。

  • 解析非字符串

  • parseInt( 1/0, 19) // 18

  • parseInt(1/0, 19)实际上是parseInt("Infinity", 19)。第一个字符是"I",以19为基数时值为18。第二个字符"n"不是一个有效的数字字符,解析到此为止,和"42px"中的"p"一样。

    // 题目
    // [1,"2","3",10,11].map(parseInt)相当于  [1,"2","3",10,11].map(function(string,radix){})
    // radix进制; radix 为(0、undefined、null)时默认十进制;radix超出2~36范围均输出 NaN;
    parseInt(1,0)   // radix 为(0、undefined、null)时默认十进制  ===>1
    parseInt('2',1)  // radix超出2~36范围均输出 NaN  ===> NaN
    parseInt('3',2)  // radix为2 string只能为0或1 或1开头 ===> NaN
    parseInt(10,3) // radix为3 使用3进制 (个人三转十计算法:27 9 3 1;五转十计算法:125 25 5 1以此类推)===>3
    parseInt(11,4) // radix为4 使用4进制(64 16 4 1) ===> 5 

3.3 显式转换为布尔值

  • 一元运算符!显式地将值强制类型转换为布尔值。但是它同时还将真值反转为假值(或者将假值反转为真值)。所以显式强制类型转换为布尔值最常用的方法是!!,因为第二个!会将结果反转回原值。建议使用Boolean(..)!!来进行显式转换以便让代码更清晰易读。
  • 显式ToBoolean的另外一个用处,是在JSON序列化过程中将值强制类型转换为true或false

4、隐式强制类型转换

4.1 字符串和数字之间的隐式强制类型转换

  • 根据ES5规范11.6.1节,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用ToPrimitive抽象操作(规范9.1节),该抽象操作再调用[DefaultValue],以数字作为上下文。(简单来说就是,如果+的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法)
    • var a = 42 a + ""(隐式)和前面的String(a)(显式)之间有一个细微的差别需要注意。根据ToPrimitive抽象操作规则,a + ""会对a调用valueOf()方法,然后通过ToString抽象操作将返回值转换为字符串。而String(a)则是直接调用ToString()。
  • 字符串强制类型转换为数字
    • -是数字减法运算符,因此a -0会将a强制类型转换为数字。也可以使用a * 1和a /1,因为这两个运算符也只适用于数字,只不过这样的用法不太常见。

4.2 布尔值到数字的隐式强制类型转换

  • 所有传参只允许一个真值.png
  • 通过sum += arguments[i]中的隐式强制类型转换,将真值(true/truthy)转换为1并进行累加
  • 无论使用隐式还是显式,我们都能通过修改onlyTwo(..)或者onlyFive(..)来处理更复杂的情况,只需要将最后的条件判断从1改为2或5。这比加入一大堆&&和||表达式简洁得多。所以强制类型转换在这里还是很有用的。

4.3 隐式强制类型转换为布尔值

  • (1) if (..)语句中的条件判断表达式。
  • (2) for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。
  • (3) while (..)和do..while(..)循环中的条件判断表达式。
  • (4) ? :中的条件判断表达式。
  • (5) 逻辑运算符||(逻辑或)和&&(逻辑与)左边的操作数(作为条件判断表达式)。

4.4 ||和&& (“操作数选择器”)

  • &&和||运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。

4.5 符号Symbol的强制类型转换

  • 允许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误
  • 符号不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是true)。

5、宽松相等和严格相等

==允许在相等比较中进行强制类型转换,而===不允许

  • ==和===都会检查操作数的类型。区别在于操作数类型不同时它们的处理方式不同。

  • NaN不等于NaN +0等于-0

  • 字符串与数字比较 ==> 字符串转数字

  • **其他类型和布尔类型比较 **==> 布尔值转数字。注意:true == "42" // false (判断 1 == "42")

  • null和undefined之间 除null和undefined以外的其他值均无法得到假阳(false positive)结果。(条件判断a == null仅在a为null和undefined时才成立,除此之外其他值都不成立,包括0、false和""这样的假值。)

  • 对象和非对象之间 对象执行ToPrimitive "拆封" (不包括布尔值,布尔值首先toNumber)

    之前介绍过的ToPrimitive抽象操作的所有特性(如toString()、valueOf())在这里都适用。如果我们需要自定义valueOf()以便从复杂的数据结构返回一个简单值进行相等比较,这些特性会很有帮助。

    var a = "abc"
    var b = Object(a)
    a == b // true b会进行拆封,得到"abc"
    // 但如果a是null undefinded NaN则是false,因为没有对应的封装对象,所以null和undefined不能够被封装(boxed), Object(null)和Object()均返回一个常规对象。NaN能够被封装为数字封装对象,但拆封之后NaN == NaN返回false,因为NaN不等于NaN
    
  • 比较少见的情况

    • 修改原生内置(害)

      Number.prototype.valueOf = function(){
          return 3
      }
      
      new Number(2) == 3 // true
      // 2 == 3不会有这种问题,因为2和3都是数字基本类型值,不会调用Number.prototype.valueOf()方法。而Number(2)涉及ToPrimitive强制类型转换,因此会调用valueOf()。
      
      if(a == 2 && a == 3) // 这种也是会成立的,在valueOf里进行一个值++,而a==2在a==3之前执行
      
    • 假值的相等比较

      image.png
    • 极端情况 [] == ![] // true根据ToBoolean规则,它会进行布尔值的显式强制类型转换(同时反转奇偶校验位)。所以[] == ! []变成了[] == false

    • "" == [null] // true [null]会直接转换为""

    • 0 == "\n" // true ""、"\n"(或者" "等其他空格组合)等空字符串被ToNumber强制类型转换为0

    • 0 "" [] 互相相等的情况

    • • 如果两边的值中有true或者false,千万不要使用==。• 如果两边的值中有[]、""或者0,尽量不要使用==。

6、抽象关系比较

ES5规范11.8.5节定义了“抽象关系比较”(abstract relational comparison),分为两个部分:比较双方都是字符串(后半部分)和其他情况(前半部分)。

  • 比较双方首先调用ToPrimitive,如果结果出现非字符串,就根据ToNumber规则将双方强制类型转换为数字来进行比较。

    • var a = [42]
      var b = ["43"]
      a < b // true
      b < a // false
      
  • 如果比较双方都是字符串,则按字母顺序来进行比较

    • var a = ["42"]
      var b = ["043"]
      a < b // false 它们分别以"4"和"0"开头。因为"0"在字母顺序上小于"4",所以最后结果为false。
      
      var c = [4,2]
      var d = [0,4,3]
      a < b // false 比较"4,2" "0,4,3"
      
  • 根据规范a <= b被处理为b < a,然后将结果反转。

    • var a = { b: 42 }
      var b = { b: 43 }
      
      // 同为"[object Object]" 因为b < a的结果是false,所以a <= b的结果是true
      a < b // false
      a == b // false
      a > b // false
      
      a <= b // true
      a >= b // true
      // 相等比较有严格相等,关系比较却没有“严格关系比较”(strict relational comparison)。也就是说如果要避免a < b中发生隐式强制类型转换,我们只能确保a和b为相同的类型,除此之外别无他法。
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容