欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

javascript类型转换(上篇)

程序员文章站 2022-05-08 08:44:02
...

前言

JavaScript中有6种数据类型:数字(number)、字符串(string)、布尔值(boolean)、undefined、null、对象(Object)。其中对象类型包括:数组(Array)、函数(Function)、还有两个特殊的对象:正则(RegExp)和日期(Date)。

其中简单数据类型为:number、string、boolean、undefined、null

引用(复杂)数据类型为:object

但是Javascript是一门弱类型的语言,它在声明时只有一种类型,只有在运行期间才能确定当前变量的类型。

而在运行期间,因为JS其弱类型的特性,没有对类型做严格限制,因此会发生许多不同类型之间的运算,而这样的运算就需要一个类型之间的转换来处理了

因此这个转换也就是类型转换了

类型转换规范

js中的类型转换分为两种:

  • 显式类型转换
  • 隐式类型转换

我觉得吧,无论显式还是隐式,它都是得遵循类型转换的规范的,显式和隐式只不过是一种说法而已

我觉得真正意义上的显式和隐式取决于你知不知道这个操作会不会触发类型转换,如果你能清晰的知道这个操作会触发类型转换,那么这个转换可以看做显式的

也就是说不同的人,认知的显示隐式类型转换是不同的

那么我们先了解类型转换的规范

类型转换主要是基本类型转基本类型、复杂类型转基本类型两种。

转换的目标类型主要分为以下几种:(一些toInt这里不涉及)

  • 转换为 string (toString)
  • 转换为 number (toNumber)
  • 转换为 boolean (toBoolean)
  • 对象转换为基本类型 (toPrimitive)

toString

toString的es5规范如下图

javascript类型转换(上篇)
ToString会按照表格将其参数转换为String类型的值

参数类型 结果
undefined “undefined”
Null “null”
Boolean 如果参数为true,则结果为"true"
如果参数为fasle,则结果为"false"
Number 参考ES5规范9.8.1
String 返回输入参数(不转换)
Object 应用以下步骤:
1.令primValue为ToPrimitive(input参数, hint 字符串)
2.返回ToString(primValue)

这里提及到了规范9.8.1,如图,这是4个重要的点,其他点感兴趣可以通过我提供的链接去看一看

javascript类型转换(上篇)
ToString将Number m转换为String格式,如下所示:

  1. 如果m为NaN,返回String “NaN”
  2. 如果m为+0或-0,返回String “0”
  3. 如果m<0,返回String “-” 和 ToString(-m) 的 String连接
  4. 如果m为Infinity,返回String “Infinity”

当然,转为string类型,还可以使用ES6的模板字符串

`${undefined}` // "undefined"
`${null}` // "null"
`${true}` // "true"
`${false}` // "false"
`${18}` // "18"
`${NaN}` // "NaN"
`${+0}` // "0"
`${-0}` // "0"
`${-3}` // "-3"
`${Infinity}` // "Infinity"
`${-Infinity}` // "-Infinity"
`${'小鹿'}` // "小鹿"
`${{}}` // "[object Object]"

toNumber

toNumber的es5规范如下图

javascript类型转换(上篇)
ToNumber会按照表格将其参数转换为Number类型的值

参数类型 结果
Undefined NaN
NULL +0
Boolean 如果参数为true,则结果为1
如果参数为false,则结果为+0
Number 结果等于输入参数(不转换)
String 参考ES5规范9.3.1
Object 应用以下步骤:
1.令primValue为ToPrimitive(input参数, hint 字符串)
2.返回ToNumber(primValue)

注意:这里的toNumber是不能像toString那样直接使用的,因为它没在Object.prototype上定义,所以这里使用的是Number,而Number会调用其内置方法toNumber,所以规则还是如上

这里提及到了规范9.3.1,因为太长了,我直接给总结一下吧!(同样给出了链接,可以自己去看一看)

应用于字符串的ToNumber将一下语法应用于输入字符串。如果语法无法将String解释为StringNumericLiteral的扩展,则ToNumber的结果为NaN

从String到Number值的转换总体上类似于确定数字文字的Number值,该值分为两步确定:

  1. 首先,从String数值文字中得出数学值(MV)
  2. 然后,将得出数学值的取整

取整如下规则:

  • 空的MV为0
  • " "的MV 为0
  • “0"与”+0"的MV 为0
  • "-0"的MV 为-0
  • “Infinity"与”+Infinity"的MV 为 Infinity
  • "-Infinity"的MV 为 Infinity
  • “10e3"与”+10e3"的MV为 10000
  • "-10e3"的MV为-10000
  • “1.3e5"与”+1.3e5"的MV为130000
  • "-1.3e5"的MV为-130000
  • "0到9"的MV为0-9
  • "0xa"与"0xA"的MV为10
  • "0xb"与"0xB"的MV为11
  • "0xc"与"0xC"的MV为12
  • "0xd"与"0xD"的MV为13
  • "0xe"与"0xE"的MV为14
  • "0xf"与"0xF"的MV为15
  • "-0xf"这些的MV为NaN,并不是-10那些

转为Number类型,如下

Number(undefined) // NaN
Number(null) // 0
Number(true) // 1
Number(false) // 0
Number(2) // 2
Number() // 0
Number(" ") // 0
Number("0") Number("+0") // 0
Number("-0") //-0
Number("Infinity") Number("+Infinity") // Infinity
Number("-Infinity") // -Infinity
Number("10e3") Number("+10e3") // 10000
Number("-10e3") // -1000
Number("1.3e5") Number("+1.3e5") // 130000
Number("-1.3e5") // -130000
Number("9") // 9
Number("0xA") // 10
Number("+0xA") Number("-0xA") // NaN

toBoolean

toBoolean的ES5规范如下图

javascript类型转换(上篇)

ToBoolean会按照表格将其参数转换为Boolean类型的值

参数类型 结果
Undefined false
Null false
Boolean 结果等于输入参数(不转换)
Number 如果参数为+0、-0或NaN,结果为false
否则结果为true
String 如果参数为空字符串(长度为0),结果为false
否则结果为true
Object true

注意:这里的ToBoolean是不能像toString那样直接使用的,因为它没在Object.prototype上定义,所以这里使用的是Boolean,而Boolean会调用其内置方法ToBoolean,所以规则还是如上

转为Boolean类型,如下

Boolean(undefined) // false
Boolean(null) // false
Boolean(true) // true
Boolean(false) // false
Boolean(+0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(1) // true
Boolean("") // false
Boolean(" ") // true
Boolean({}) // true

toPrimitive

toPrimitive是对象转为基本类型时会触发的内部方法,ES5规范如下图

javascript类型转换(上篇)
ToPrimitive会按照表格将其参数转换为基本类型的值

输入类型 结果
Undefined 结果等于输入参数(不转换)
Null 结果等于输入参数(不转换)
Boolean 结果等于输入参数(不转换)
Number 结果等于输入参数(不转换)
String 结果等于输入参数(不转换)
Object 返回对象的默认值。通过调用对象的[[DefaultValue]]内部方法并传递可选hint PreferredType来检索对象的默认值。本规范为8.12.8中的所有本机ECMAScript对象定义了[[DefaultValue]]内部方法的行为

此时又有来到8.12.8的[[DefaultValue]] (hint)了

[[DefaultValue]]

DefaultValue的ES5规范如下图

javascript类型转换(上篇)

复杂类型转基本类型的规则如下:


当参数hint值为string调用O的[[DefaultValue]]内部方法时,将执行以下步骤:

  1. 让toString是使用参数“toString”调用对象O的[[Get]]内部方法的结果。
  2. 如果IsCallable(toString)为真,则
    • 让str是调用toString的[[Call]]内部方法的结果,其中O是this值,参数列表为空。
    • 如果str是基本类型值,则返回str。
  3. 让valueOf是使用参数“valueOf”调用对象O的[[Get]]内部方法的结果。
  4. 如果IsCallable(valueOf)为true,则
    • 让val是调用valueOf的[[Call]]内部方法的结果,其中O为this值,参数列表为空。
    • 如果val是基本类型值,则返回val。
  5. 抛出类型异常。

当参数hint值为number调用O的[[DefaultValue]]内部方法时,将执行以下步骤:

  1. 让valueOf是使用参数“valueOf”调用对象O的[[Get]]内部方法的结果。
  2. 如果IsCallable(valueOf)为真,
    • 让val是调用valueOf的[[Call]]内部方法的结果,其中O为this值,参数列表为空。
    • 如果val是基本类型值,则返回val。
  3. 让toString是使用参数“toString”调用对象O的[[Get]]内部方法的结果。
  4. 如果IsCallable(toString)为真,
    • 让str是调用toString的[[Call]]内部方法的结果,其中O是this值,参数列表为空。
    • 如果str是基本类型值,则返回str。
  5. 抛出类型异常。

IsCallable是判断对象内是否有相应的方法的,如果有,就返回true

总结:
假设obj是对象类型,想要转为number类型时:

  1. 如果obj为基本类型值,直接返回
  2. 否则调用obj.valueOf(),如果执行结果是基本类型值,返回
  3. 否则调用obj.toString(),如果执行结果是基本类型值,返回
  4. 否则抛异常。

假设obj是对象类型,想要转为string类型时:

  1. 如果obj为基本类型值,直接返回
  2. 否则调用obj.toString(),如果执行结果是基本类型值,返回
  3. 否则调用obj.valueOf(),如果执行结果是基本类型值,返回
  4. 否则抛异常。

当O的[[DefaultValue]]内部方法在没有hint的情况下(default)被调用时,它的行为就像hint是number一样,除非O是Date对象,在这种情况下,它的行为就像hint是string一样。

前面提到过的toNumber和toString,在处理Object类型时,都会执行toPrimitive,因此让我们来分析分析toPrimitive中的这两个方法吧,也算是对前面的补充

Object.prototype.valueOf()

object.valueOf() 返回值为该对象的基本类型值。

JavaScript调用valueOf方法将对象转换为基本类型值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。

默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身。

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同。

// Array:返回数组对象本身
var array = ["xiaolu", true, false, 66, -2]
console.log(array.valueOf() === array)   // true

// Date:当前时间距1970年1月1日午夜的毫秒数
var date = new Date(2020)
console.log(date.valueOf())  // 2020

// Number:返回数字值
var num =  1999.9
console.log(num.valueOf())   // 1999.9

// 布尔:返回布尔值true或false
var bool = true
console.log(bool.valueOf() === bool)   // true

// new一个Boolean对象
var newBool = new Boolean(true)
// valueOf()返回的是true,两者的值相等
console.log(newBool.valueOf() == newBool)   // true
// 但是不全等,两者类型不相等,前者是boolean类型,后者是object类型
console.log(newBool.valueOf() === newBool)  // false

// Function:返回函数本身
function foo(){}
console.log( foo.valueOf() === foo )   // true
var foo2 =  new Function("x", "y", "return x + y;");
console.log( foo2.valueOf() )

// Object:返回对象本身
var obj = {name: "小鹿", age: 18}
console.log( obj.valueOf() === obj )   // true

// String:返回字符串值
var str = "https://xiaolu.blog.csdn.net/"
console.log( str.valueOf() === str )   // true

// new一个字符串对象
var str2 = new String("https://xiaolu.blog.csdn.net/")
// 两者的值相等,但不全等,因为类型不同,前者为string类型,后者为object类型
console.log( str2.valueOf() === str2 )   // false

Object.prototype.toString()

Object类型转换为string是通过ToPrimitive抽象操作来完成的

数组的默认toString()方法经过了重新定义,是将所有单元字符串化以后再用 ","连接起来

var a = [1, 2, 3];
console.log(a.toString()) //"1, 2, 3"

每个对象都有一个toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString()返回"[object type]",其中type是对象的类型。以下代码说明了这一点:

var o = new Object()
console.log(o.toString()) //[object Object]

可以通过 toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg。

var toString = Object.prototype.toString;

toString.call(new Date) // [object Date]
toString.call(new String) // [object String]
toString.call(Math) // [object Math]
toString.call(undefined) // [object Undefined]
toString.call(null) // [object Null]

显式类型转换

显式类型转换就是手动地将一种值转换为另一种值。

就像之前提及的Number,Boolean之类的,都是会触发相应的toNumber和toBoolean方法,如果是对象,触发toPrimitive

Number('1') // 1 这就是显式类型转换了,触发toNumber 
Number({}) // NaN 

像这样你通过手动调用Number或Boolean等这些方法主动调用toNumber那些规范内置方法时,就可以称之为显式类型转换

隐式类型转换

隐式类型转换,是个很难琢磨透的东西。它会在不经意的操作中触发。

如果程序员能清楚的知道自己的代码中哪些操作会引发类型转换,那么这个隐式类型转换就不是问题了

但是大多数情况下,很多人都不能完全清楚的知道什么操作会触发隐式类型转换,因此会出现很多难以定位的错误,特别麻烦

来看看这张图,就能感觉到隐式类型转换的恶意了

javascript类型转换(上篇)

你看看Brendan Eich他那善意的眼神!多可爱!
javascript类型转换(上篇)

接下来我将列出一些会触发隐式转换的运算符,并给出其规范!

一元+运算符

+运算符的规范如下图

javascript类型转换(上篇)
只需要从第5步开始看,+运算符会将左右两边经过GetValue后得到的值,进行ToPrimitive的hint值为default(也就是空)的调用,而这个调用相当于hint为number,也就是先执行valueOf后执行toString,之后将值赋给lprim和rprim

之后判断lprim和rprim,如果其中有一个类型是string类型,那么就返回lprim和rprim经过toString后拼接起来的值,其实这就是+运算符的拼接字符串功能

经过上面判断后,两者都不为字符串时,对lprim和rprim经过toNumber后执行加法运算,这就是+运算符的加法功能

1 + '2' // 12 触发第五步判断的有一个为string类型,那么拼接
1 + 2 // 3  没有string类型,那么相加

二元-运算符

-运算符的规范如下图

javascript类型转换(上篇)
这里也是从第5步开始看,-运算符会将左右两边经过GetValue后得到的值,进行toNumber后,将值赋给lnum和rnum

之后再对lnum和rnum进行减法运算

'3' - '2' // 1 会触发toNumber后进行减法运算
'xiaolu' - 1 // NaN string执行toNumber会变成NaN 

*、/、%运算符

*、/、%这三个运算符的规范是一起的,如下图

javascript类型转换(上篇)
同样从第五步开始,*、/、%运算符会将左右两边经过GetValue后得到的值,进行toNumber后,将值赋给leftNum和rightNum

然后对leftNum和rightNum执行相应的(*、/、%)运算

'2' * '3' // 6 触发toNumber
'xiaolu' / 'lu' // NaN

!运算符

!运算符的规范如下图

javascript类型转换(上篇)
!取反运算符,其实就是将GetValue得到的值进行ToBoolean操作后赋值给oldValue

然后判断oldValue,如果是ture,就返回false,否则返回true

!1 // false
!'true' // flase
!0 // true // 都是执行了toBoolean后判断

一元+和一元-运算符

一元+的规范如下图
javascript类型转换(上篇)
一元+运算符会将GetValue得到的值经过ToNumber后的值返回

+'3' // 3
+'xiaolu' // NaN

一元-的规范如下图
javascript类型转换(上篇)
第二步,一元-运算符会将GetValue得到的值经过ToNumber后的值赋给oldValue

-'2' // -2
-'xiaolu' // NaN

++、- -运算符

前缀++运算符规范如下图
javascript类型转换(上篇)
第三步,++运算符会将值经过GetValue后得到的值,经过ToNumber后赋值给oldValue,之后再执行+1的操作

而无论是前缀还是后缀的++还是–运算符,都是会在那一步进行toNumber的

属性描述符

都知道有两种属性描述符:

  1. a.b
  2. a[b]

而其实内部对其进行了处理,点属性描述符会转换成[]描述符,规范如下图

javascript类型转换(上篇)
这里其实就是执行将点的描述符转换成[]描述符的操作,在内部第6步时会执行一个toString,其他这里不多提,感兴趣可以去看规范

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

记住转为数字时,true转为1,false转为0即可

function onlyOne() {
      var sum = 0
      for(var i = 0; i < arguments.length; i++) {
        // 跳过假值,避免了NaN
          if(arguments[i]) { 
              sum += arguments[i]; //这里进行了隐式类型转换,
                          //将true转为1,然后进行加法运算
          }
      }
      return sum == 1
    }

    var a = true
    var b = false

    console.log(onlyOne(b, a))// true
    console.log(onlyOne(b, a, b, b, b)) //true
    console.log(onlyOne(b, a, a)) // false

非布尔值到布尔值的隐式类型转换

下面的情况会发生布尔值隐式类型转换:

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

以上情况中,非布尔值都会执行ToBoolean转换为布尔值。

注意:|| 和 &&

它们两的返回值是两个操作数中的一个(且仅一个)。选择两个操作数中的一个,然后返回它的值。

引述ES5规范11.11节:
&&和||运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。

var a = 42
var b = "abc"
var c = null

console.log(a || b) // 42
console.log(a && b) // "abc"

console.log(c || b) // "abc"
console.log(c && b) // null

||和&&首先会对第一个操作数执行条件判断,如果其不是布尔值,就先进行ToBoolean强制类型转换。

对于||来说,如果第一个操作数条件判断结果为true,就返回第一个操作数的值,如果为false,就返回第二个操作数的值。

&&则相反,如果第一个操作数条件判断结果为true,就返回第二个操作数的值,如果为false,就返回第一个操作数的值。

在if()中,最后会将||和&&返回的值再进行布尔值隐式类型转换。

==和=== 宽松相等和严格相等

常见的误区是"==检查值是否相等,===检查值和类型是否相等"。

正确的解释:“==允许在相等比较中进行强制类型转换,而===不允许”。

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

==宽松相等

如果两个值的类型相同,就仅比较它们是否相等

==在比较两个不同类型的值时会发生隐式强制类型转换,会将其中之一或两者都转换为相同的类型后再比较。

javascript类型转换(上篇)
比较x==y,其中x和y是值,产生true或false。这样的比较如下:

  1. 如果类型(x)与类型(y)相同,则
    • 如果类型(x)为undefined,则返回true
    • 如果类型(x)为Null,则返回true
    • 如果类型(x)是Number,则
      • 如果x是NaN,则返回false
      • 如果y是NaN,则返回false
      • 如果x与y的数值相同,则返回true
      • 如果x是+0,y是−0,则返回true
      • 如果x是−0,y是+0,则返回true
      • 返回false。
    • 如果类型(x)是String,那么如果x和y是完全相同的字符序列(长度相同,相应位置的字符相同),则返回true。否则,返回false
    • 如果类型(x)是Boolean,如果x和y都为true或都为false,则返回true。否则,返回false
    • 如果x和y引用同一对象,则返回true。否则,返回false
  2. 如果x为null,y是undefined,则返回true
  3. 如果x为undefined,y为null,则返回true
  4. 如果类型(x)是Number,类型(y)是String
    • 返回比较结果x==ToNumber(y)。
  5. 如果类型(x)是String,类型(y)是Number,
    • 返回比较结果ToNumber(x)==y。
  6. 如果类型(x)是Boolean,则返回ToNumber(x)==y的比较结果。
  7. 如果类型(y)是Boolean,则返回比较结果x==ToNumber(y)。
  8. 如果类型(x)是String或Number,而类型(y)是对象,
    • 返回比较结果x==ToPrimitive(y)。
  9. 如果类型(x)是对象,类型(y)是StringNumber
    • 返回 ToPrimitive(x)==y的比较结果。
  10. 返回false。

整理成表格后,如下

输入的X/Y类型 Undefined Null Number String Boolean Objcet
Undefined true true false false false false
Null true true false false false false
Number false false 如果X是NaN,返回false
如果Y是NaN,返回false
如果值相同,返回true
返回X==ToNumber(Y)的结果 返回X==ToNumber(Y)的结果 返回X==ToPrimitive(Y)的结果
Srting false false 返回ToNumber(X)==Y的结果 如果X和Y是完全相同的字符序列(长度相同,相应位置的字符相同),则返回true
否则,返回false
返回ToNumber(X)==ToNumber(Y)的结果 返回X==ToPrimitive(Y)的结果
Boolean false false 返回ToNumber(X)==Y的结果 返回ToNumber(X)==ToNumber(Y)的结果 如果X和Y都为true或都为false,则返回true
否则,返回false
返回ToNumber(X)==ToPrimitive(Y)的结果
Object false false 返回ToPrimitive(X)==Y的结果 返回ToPrimitive(X)==Y的结果 返回ToPrimitive(X)==ToNumber(Y)的结果 如果X和Y引用同一对象,返回true
否则,返回false

示例如下

undefined == undefined // true
undefined == null // true
undefined == 1 // false
undefined == '1' // false
undefined == true // false
undefined == false // false
undefined == {} // false
null == undfined // true
null == null // true
null == 1 // false
null == '1' // false
null == true // false
null == false // false
null == {} // false
1 == undefined // false
1 == null // false
1 == 1 // true
1 == NaN // false
1 == '1' // true
1 == 'xiaolu' // false
1 == true // true
2 == true // false
1 == false // false
1 == ['1'] // true
1 == {a: 1} // false
'xiaolu' == undefined // false
'xiaolu' == null // false
'xiaolu' == 1 // false
'1' == 1 // true
'xiaolu' == '1' // false
'xiaolu' == 'xiaolu' // true
'xiaolu' == true // false
'1' == true // true
'0' == false // true
'xiaolu' == ['xiaolu'] // true
'xiaolu' == {a: 1} // false
true == undefined // false
true == null // false 
true == 1 // true
true == 2 // false
false == 0  // true
true == '1' // true
true == '2' // false
true == 'true' // false
false == '0' // true
true == true // true
false == false // true
true == false // false
true == ['1'] // true
true == ['true'] // false
false == ['0'] // true
{} == undefined // false
{} == null // false
['1'] == 1 // true
['xiaolu'] == 2 // false
['xiaolu'] == 'xiaolu' // true
['1'] == true // true
['true'] == true // false
{} == {} // false
var a = {} b = a  a === b // true

=== 严格相等

严格相等规范如下图

javascript类型转换(上篇)
比较x===y,其中x和y是值,产生true或false。这样的比较如下:

  1. 如果类型(x)与类型(y)不同,则返回false。
  2. 如果类型(x)为undefined,则返回true
  3. 如果类型(x)为Null,则返回true
  4. 如果类型(x)是Number,则
    • 如果x是NaN,则返回false
    • 如果y是NaN,则返回false
    • 如果x与y的数值相同,则返回true
    • 如果x是+0,y是−0,则返回true
    • 如果x是−0,y是+0,则返回true
    • 返回false
  5. 如果类型(x)是String,那么如果x和y是完全相同的字符序列(相同的长度和相应位置的字符),则返回true;否则,返回false
  6. 如果类型(x)是Boolean,如果x和y都为true或都为false,则返回true;否则,返回false
  7. 如果x和y引用同一对象,则返回true。否则,返回false

===其实就是首先判断两边类型,如果不同,返回false,类型相同则进入跟上面==类型相同的时的判断

比较 > <

抽象关系比较算法规范如下图

javascript类型转换(上篇)

这里就不全部翻译了,给总结一下:

首先算法中会有一个LeftFirst标志,为true代表从左向右,否则相反(用于>= <=的转换)

在判断完这个LeftFirst标志后,会对x和y的值进行ToPrimitive操作,hint值为number,然后分别将结果记在px和py中

如果是px和py的类型中不都是字符串的情况时

  • 让nx为ToNumber(px)的结果,让ny为ToNumber(py)的结果
  • 如果nx或ny其中一个为NaN,返回undefined
  • 如果nx和ny值相同,返回false
  • 如果nx为+0,ny为-0,或nx为-0,ny为+0,都返回false
  • 如果nx为+∞,则返回false。如果ny是+∞,则返回true。如果ny为−∞,则返回false。如果nx为−∞,则返回true。
  • 如果nx的数学值小于ny的数学值,返回true。否则,返回false。

如果是px和py类型都为字符串的情况时

  • 如果py是px的前缀,则返回false。(如果q可以是p和其他字符串r连接的结果,则字符串值p是字符串值q的前缀。请注意,任何字符串都是其自身的前缀,因为r可能是空字符串。)如果px是py的前缀,则返回true。
  • 设k是最小的非负整数,使得px中位置k处的字符与py中位置k处的字符不同。(必须有这样一个k,因为两个字符串都不是另一个字符串的前缀。)设m为整数,即px中位置k处字符的代码单位值。设n为整数,即py中k位置字符的代码单位值。如果m<n,则返回true。否则,返回false。

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

var a = [19]
var b = ["20"]

console.log(a < b) // true

如果比较双方都是字符串,则按字母顺序来进行比较:

var a = ["20"]
var b = ["020"]

console.log(a < b) // false

如果toPrimitive后都是字符串,可以通过Number然他们进行数字比较

var a = [42]
var b = "043"

console.log(a < b)                //false 字符串比较
console.log(Number(a) < Number(b)) //true  数字比较

但是还有一点是需要注意的,那就是<=和>=

注意:>= <=

来看看这一个栗子

var a = {age: 19}
var b = {age: 20}
 
console.log(a < b)  //false
console.log(a == b) //false
console.log(a > b)  //false

console.log(a <= b) //true
console.log(a >= b) //true

这里的前三个比较打印为false,我们是能分析出来的,两个对象比较,判断两者是否引用的同一对象

为什么a<b和a==b结果为false,但a <= b 和 a >= b结果会是true呢

这时候,就又要看规范了,规范里做了一个处理,如图

javascript类型转换(上篇)

可以看到,<=的第五步其实是让r变成rval < lval并且LeftFirst为false的结果,其实也就是反向执行 <的判断,也就是执行 >
而 >= 的第五步,就是让结果变成 lval < rval的结果

而两者的第六步,就是对第五步取到的结果进行取反

总结一下,JS中的<=和>=其实是将>和<的结果拿到并取反而已

知道了这个之后,再去看这个例子,因为之前 a < b 和 a > b都为false,所以a >= b和 a <= b都为true

而到了这里,应该隐式类型转换已经说得差不多了

隐式类型转换总结

在给出了这些规范后,按照这些规范,去执行比较和判等,是不是发现隐式类型也没那么可怕了?无疑就是那么几个方法!

总结

其实写了那么多,无疑都是看规范,记规范!

显式类型转换都是手动调用函数,相应触发toNumber,toBoolean,toString,toPrimitive这四个方法

而隐式类型转换也就常发生于运算符中,和一些判断相等和比较中,也会相应触发toNumber,toBoolean,toString,toPrimitive这四个方法

而这四个方法,以及相等和比较以及运算符的规范,我已经大多都给出了,只要了解完了这些,再碰到类型转换方面的面试题以及bug,都很容易找到问题的答案

当然规范里不止这四个方法,还有一些toInt的位运算的类型转换,我这里没提,这是极少数会出现的状况,大家感兴趣的话,可以自行去了解,附上规范链接

下篇,将带来一系列关于类型转换的问题,以及解决方法!