JavaScript中的类型转换

正文以前,先抛出几组问题:数组

// 第一组
[] == [] //false

// 第二组
[] == ![] //true

{} == !{} //false

{} == ![] // false

[] == !{} //true

{} == 1 // false

// 第三组
{} < 1 // false

{} > 1 // false

看到这几个问题,是否是一脸懵逼?函数

稍微有点基础的同窗,应该一眼就能看出 [] == [] 输出 false,由于 Object 是引用类型,两个引用类型作 == 比较,若是它们引用的是同一个地址,输出 true,不然输出 false。可是后面几道题可能会有一点点麻烦。this

后面几道题都涉及到 JavaScript 中的一个难点:隐式转换。本文将会带领你们深刻了解 JavaScript 中的类型转换机制。spa

1,JavaScript 数据类型

js数据类型分为两大类:prototype

  • 基本类型(原始值):Undefined,Null,Boolean,Number,String, Symbol
  • 对象类型:Object

2,ECMAScript 规范中的抽象操做

2.1 ToPrimitive ( input [, PreferredType] ) 转换为原始值code

抽象操做 ToPrimitive(input[, PreferredType]),将input参数转换为一个非对象类型的值,即原始值类型。转换规则以下:对象

  • Undefined:返回原始值,不转换
  • Null:返回原始值,不转换
  • Boolean:返回原始值,不转换
  • Number:返回原始值,不转换
  • String:返回原始值,不转换
  • Symbol:返回原始值,不转换
  • Object:见下文

Type(input)Object 时,能够将抽象操做 ToPrimitive(input[, PreferredType]) 的执行过程用以下代码解释:blog

// 仿抽象操做 ToPrimitive(input[, PreferredType]) 的执行过程
function ToPrimitive(input: Object, PreferredType: undefined | 'String' | 'Number') {
  let hint;
  // 若是没有送 PreferredType ,hint 为 'default'
  // 若是 PreferredType 为 'String', hint 为 'string'
  // 若是 PreferredType 为 'Number', hint 为 'number'
  if(!PreferredType) {
    hint = 'default';
  } else if (PreferredType === 'String') {
    hint = 'string';
  } else if(PreferredType === 'Number') {
    hint = 'number';
  }

  // 获取对象的 @@toPrimitive 方法,若是对象自身没有,会一直顺着原型链查找
  let exoticToPrim = GetMethod(input, Symbol.toPrimitive);

  // 若是该方法不为undefined,用对象调用该方法,赋值给result
  // 若是 result 不是 Object 类型,返回 result;不然抛出 TypeError 异常
  if(exoticToPrim !== undefined) {
    let result = exoticToPrim.call(input, hint);
    if (typeof result !== 'object') {
      return  result;
    }
    throw new TypeError();
  }

  // 若是该方法为undefined
  // 若是 hint 为 'default',令 hint 为 'number'
  // 返回 抽象操做 OrdinaryToPrimitive 的调用结果
  if(hint === 'default'){
    hint = 'number';
  }
  return OrdinaryToPrimitive(input,hint);
}

这里涉及到另外一个抽象操做 GetMethod,咱们先看一下ECMAScript 6 规范中对 GetMethod 的定义:排序

图片描述

简单说来就是: 抽象操做 GetMethod(O,P) 获取对象 O 的 P 属性,若是 该属性是 undefined,返回 undefined;若是该属性是一个函数,返回此函数;不然抛出一个 TypeError 异常。图片

下面咱们看一下 抽象操做 OrdinaryToPrimitive 的过程:

// 仿抽象操做 OrdinaryToPrimitive(O, hint) 的执行过程
function OrdinaryToPrimitive(O: Object, hint: 'string' | 'number') {
  // 假定 hint 是一个字符串,而且其值只能是'string' 或 'number'
  // 若是 hint === 'string',令 methodNames = ['toString', 'valueOf']
  // 若是 hint === 'number',令 methodNames = ['valueOf', 'toString']
  let methodNames;
  if(hint === 'string') {
    methodNames = ['toString', 'valueOf'];
  } else {
    methodNames = ['valueOf', 'toString'];
  }

  // 遍历 methodNames,获取对象的方法,赋值给 result
  // 若是 result 不是 Object,终止遍历,并返回 result
  // 抛出一个 TypeError 异常
  for (let item of methodNames) {
    let method = O[item];
    let result = method.call(O);
    if(typeof(result) !== 'object') {
      return result;
    }
  }

  throw new TypeError();
}

Date 对象和 Symbol 对象的原型上已经部署了 [@@toPrimitive] 方法,这个方法是不可枚举(enumerable: false),不可改写的(writable: false)。对于Date对象原型上的[@@toPrimitive] 方法,若是没有送hint,会将hint看成'string'

咱们可使用 Symbol.toPrimitive 来给 Object 添加 [@@toPrimitive] 方法:

Object.prototype[Symbol.toPrimitive] = function(hint) {
  if(hint === 'default') {
    let thisType = Object.prototype.toString.call(this);
    if(thisType === '[object Date]') {
      hint = 'string';
    } else {
      hint = 'number';
    }
  }

  let methodNames;
  if(hint === 'string') {
    methodNames = ['toString', 'valueOf'];
  } else if(hint === 'number') {
    methodNames = ['valueOf', 'toString'];
  } else {
    throw new TypeError('Invalid hint: ' + hint);
  }

  for (let key of methodNames) {
    let method = this[key];
    let result = method.call(this);
    if(typeof(result) !== 'object') {
      return result;
    }
  }

  throw new TypeError();
}

2.1.1 对象的 valueOf() 方法和 toString() 方法

对象在执行 ToPrimitive 转换时,须要用到对象的valueOf()toString()方法。咱们能够在Object.prototype上找到这两个方法。在JavaScript中,Object.prototype是全部对象原型链的顶层原型,所以,任何对象都有valueOf()toString()方法。

JavaScript的许多内置对象都重写了这两个方法,以实现更适合自身的功能须要。

不一样类型对象的valueOf()方法的返回值:

  • Array: 返回数组对象自己。
  • Boolean: 布尔值。
  • Date: 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
  • Function: 函数自己。
  • Number: 数字值。
  • Object: 对象自己。这是默认状况。
  • String: 字符串值。
  • Symbol:Symbol值自己。

不一样类型对象的toString()方法的返回值:

  • Array:链接数组并返回一个字符串,其中包含用逗号分隔的每一个数组元素。
  • Boolean:返回字符串 "true""false"
  • Date:返回一个美式英语日期格式的字符串。
  • Function:返回一个字符串,其中包含用于定义函数的源文本段。
  • Number: 返回指定 Number 对象的字符串表示形式。
  • Object: 返回 "[object type]",其中 type 是对象的类型。
  • String: 字符串值。
  • Symbol:返回当前 Symbol 对象的字符串表示。

2.2 ToBoolean 转换为布尔值类型

抽象操做 ToBoolean 根据下列规则将其参数转换为布尔值类型的值:

  • Undefinedfalse
  • Nullfalse
  • Boolean:结果等于输入的参数(不转换)。
  • Number:若是参数是 +0, -0,NaN,结果为 false ;不然结果为 true
  • String:若是参数是空字符串(其长度为零),结果为 false,不然结果为 true
  • Symboltrue
  • Objecttrue

2.3 ToNumber 转换为数值类型

抽象操做 ToNumber 根据下列规则将其参数转换为数值类型的值:

  • Undefined:NaN
  • Null:+0
  • Boolean:若是参数是 true,结果为 1。若是参数是 false,此结果为 +0
  • Number:结果等于输入的参数(不转换)。
  • String:参见下文
  • Symbol:抛出 TypeError 异常
  • Object:先进行 ToPrimitive 转换,获得原始值,再进行 ToNumber 转换

2.3.1 对字符串类型应用 ToNumber

对字符串应用 ToNumber 时,若是符合以下规则,转为数值:

  • 十进制的字符串数值常量,可有任意位数的0在前面,如 '000123''123' 都会被转为 123
  • 指数形式的字符串数值常量,如 '1e2' 转为 100
  • 带符号的十进制字符串数值常量或指数字符串数值常量,如'-100', '-1e2' 都会转为 -100
  • 二进制,八进制,十六进制的字符串数值常量,如'0b11', '0o11', '0x11' 分别转为 3, 9, 17
  • 符合上述条件的字符串数值常量开头或结尾,能够包含任意多个空格。如' 0b11 ' 转为 3
  • 空字符串(长度为零的字符串)或只有空格的字符串,转为 0

若是字符串不符合上述规则,将转为NaN

2.4 ToString 转为字符串类型

抽象操做 ToString 根据下列规则将其参数转换为字符串类型的值:

  • Undefined"undefined"
  • Null"null"
  • Boolean:若是参数是 true,那么结果为 "true"。 若是参数是 false,那么结果为 "false"
  • String:结果等于输入的参数(不转换)。
  • Number:参见下文。
  • Symbol:抛出 TypeError 异常
  • Object:先进行 ToPrimitive 转换,hint'string',获得原始值,再进行 ToString 转换

2.4.1 对数值类型应用 ToString

抽象操做 ToString 运算符将数字 m 转换为字符串格式的给出以下所示:

  1. 若是 m 是 NaN,返回字符串 "NaN"
  2. 若是 m 是 +0-0,返回字符串 "0"
  3. 若是 m 小于零,返回 "-m"
  4. 若是 m 正无限大,返回字符串 "Infinity"。若是 m 负无限大,返回字符串 "-Infinity"
  5. 不然,返回 "m" 或 m 的指数形式的字符串数值常量

2.5,抽象操做 GetValue

先看一下 ECMAScript 规范中定义的 GetValue 方法:

图片描述

注意区分这一句:2. If Type(V) is not Reference, return V. 中的 Reference 和咱们平时说的 引用类型 的区别。

咱们平时说的 引用类型 指的是 ECMAScript 规范中 语言类型的 Object 类型(例如 Object, Array, Date 等);而这里的 Reference 指的是ECMAScript 规范中 规范类型的 Reference 类型 ,是一个抽象的概念。

按规范的描述,Reference 是一个 name binding,由三部分组成:

  • base:一个 undefined,Object, Boolean, String, Symbol, Number 或者 环境记录(Environment Record)
  • referreference name:一个字符串 或 Symbol 值
  • strict mode flag:严格引用标志

举个例子:赋值语句 let obj.a = 1 中的 obj.a 产生的 Reference,base 是 obj,referreference name 是 'b',至于 strict mode flag 是用来检测是否处于严格模式。

Reference 和 环境记录(Environment Record) 这些概念是为了更好地描述语言的底层行为逻辑才存在的,并不存在于咱们实际的 js 代码中。

4,逻辑非运算符(!)

逻辑非运算符(!) 按下列过程将表达式转换为布尔值

  1. expr 为表达式求值的结果
  2. oldValue = ToBoolean(GetValue(expr))
  3. 若是 oldValue === true,返回 false;不然返回 true

所以,逻辑非运算符(!)能够看成是:对 ToBoolean 操做的结果取反

5,== 运算符

比较运算 x==y,按以下规则进行:

1,若 Type(x) 与 Type(y) 相同, 则

    1) 若 Type(x) 为 Undefined, 返回 true。
    2) 若 Type(x) 为 Null, 返回 true。
    3) 若 Type(x) 为 Number, 则
  
        (1)、若 x 为 NaN, 返回 false。
        (2)、若 y 为 NaN, 返回 false。
        (3)、若 x 与 y 为相等数值, 返回 true。
        (4)、若 x 为 +0 且 y 为 −0, 返回 true。
        (5)、若 x 为 −0 且 y 为 +0, 返回 true。
        (6)、返回 false。
        
    4) 若 Type(x) 为 String,则当 x 和 y 为彻底相同的字符序列时返回 true。 不然,返回 false。
    5) 若 Type(x) 为 Boolean,当 x 和 y 为同为 true 或者同为 false 时返回 true。 不然, 返回 false。
    6) 若 Type(x) 为 Symbol,若是 x 和 y 是同一个 Symbol,返回true。不然,返回 false。
    7) 若 Type(x) 为 Object,当 x 和 y 是对同一对象的引用时返回 true。不然,返回 false。
     
2,若 x 为 null 且 y 为 undefined,返回 true。
3,若 x 为 undefined 且 y 为 null,返回 true。

4,若 Type(x) 为 Number 且 Type(y) 为 String,返回 x == ToNumber(y)的结果。
5,若 Type(x) 为 String 且 Type(y) 为 Number,返回 ToNumber(x) == y的结果。

6,若 Type(x) 为 Boolean, 返回 ToNumber(x) == y 的结果。
7,若 Type(y) 为 Boolean, 返回 x == ToNumber(y) 的结果。

八、若 Type(x) 为 String 或 Number 或 Symbol,且 Type(y) 为 Object,返回 x == ToPrimitive(y) 的结果。
九、若 Type(x) 为 Object 且 Type(y) 为 String 或 Number 或 Symbol, 返回 ToPrimitive(x) == y 的结果。

十、返回 false。

如今,咱们来分析一下文章开头提出的问题:[] == ![] // true

  1. 根据 上面 逻辑非运算符 和 ToPrimitive 的规则,![] 返回 false,所以,咱们接下来须要比较的是 [] == false
  2. [] == false 符合上面规则中的第 7 条,须要对 false 执行 ToNumber 转换,获得 0,接下来要比较 [] == 0
  3. [] == 0 符合上面规则中的第 9 条,对 [] 进行 ToPrimitive 转换,获得空字符串 '',接下来要比较 '' == 0
  4. '' == 0 符合上面规则中的第 5 条,对 '' 进行 ToNumber 转换,获得 0
  5. 接下来比较 0 == 0,获得true

其余几道题我就不一一分析了,有兴趣的同窗们能够本身分析验证。提示一下,须要注意 Object.prototype.toStringArray.prototype.toString 的区别

6,比较运算符

比较运算 x < y,按照以下规则执行

1,令 px = ToPrimitive(x),令 py = ToPrimitive(y)。

2,若是 Type(px) 和 Type(py) 都是 String,则

    1)、若是 py 是 px 的前缀,返回 false。
    2)、若是 px 是 py 的前缀,返回 true。
    3)、找出 px 和 py 中 相同下标处第一个不一样的字符串单元,将其 词典排序 分别记为 m 和 n。
    4)、若是 m < n,返回 true,不然,返回 false。
    
3,令 nx = ToNumber(px),令 ny = ToNumber(py)

    1)、若是 Type(nx) 是 NaN,返回 false。
    2)、若是 Type(ny) 是 NaN,返回 false。
    3)、若是 nx 是 +0,ny 是 -0,返回 true。
    4)、若是 nx 是 -0,ny 是 +0,返回 true。
    5)、若是 nx 是 +Infinity,返回 false。
    6)、若是 ny 是 +Infinity,返回 true 。
    7)、若是 ny 是 -Infinity,返回 false。
    8)、若是 nx 是 -Infinity,返回 true。
    9)、若是 nx 的数学值小于 ny 的数学值,返回 true,不然返回 false。
相关文章
相关标签/搜索