JS隐式类型转换二之核心原理

979 阅读3分钟

ECMAScript7规范中的ToPrimitive抽象操作

引用类型转化为原始类型最核心的其实就是这个内部抽象方法,这一块理解了,后面在做题目巩固的时候思路就很清晰了

1、Symbol --- @@toPrimitive

Symbol有很多有名的符号,@@toPrimitive,也就是Symbol.toPrimitive,这是定义在Symbol对象上的一个属性。

2、ToPrimitive(input [, PreferredType])

该抽象操作接受一个参数input和一个可选的参数PreferredType。该抽象操作的目的是把参数input转化为非对象数据类型,也就是原始数据类型。如果input可以同时转化为多个原始数据,那么会优先参考PreferredType的值。转化过程参照下表:

参数input的数据类型结果
Undefined返回input自身
Null返回input自身
Boolean返回input自身
Number返回input自身
String返回input自身
Symbol返回input自身
Object执行下面的步骤
如果input的数据类型是对象,执行下述步骤:
  1. 如果没有传入PreferredType参数,让hint等于"default"
  2. 如果PreferredTypehint String,让hint等于"string"
  3. 如果PreferredTypehint Number,让hint等于"number"
  4. exoticToPrim等于GetMethod(input, @@toPrimitive),大概语义就是获取参数input@@toPrimitive方法;
  5. 如果exoticToPrim不是Undefined,那么:
    1. result等于Call(exoticToPrim, input, « hint »),大概语义就是执行exoticToPrim(hint)
    2. 如果result是原始数据类型,返回result
    3. 抛出类型错误的异常;
  6. 如果hint"default",让hint等于"number"
  7. 返回OrdinaryToPrimitive(input, hint)抽象操作的结果。

总结:

  1. 判断PreferredType,只要传的值不是string,最后统一算作number`
  2. @@toPrimitive方法优先级高于OrdinaryToPrimitive(input, hint),如果前者能返回原始类型那就不需要执行后面的操作了
  3. 如果@@toPrimitive方法不存在或者存在返回的值不是原始类型,继续执行OrdinaryToPrimitive(input, hint)操作

疑问:hint是什么?怎么传的?(继续往下看)

3、OrdinaryToPrimitive(O, hint)

O的数据类型是对象,hint的数据类型是字符串,并且hint的值要么是"string",要么是"number"。该抽象操作的步骤如下:

  1. 如果hint"string",让methodNames等于« "toString", "valueOf" »
  2. 如果hint"number",让methodNames等于« "valueOf", "toString" »
  3. 按顺序迭代列表methodNames,对于每一个迭代值name
    1. method等于Get(O, name),大概语义就是获取对象Oname值对应的属性;
    2. 如果method可以调用,那么:
      1. method等于Call(method, O),大概语义就是执行method()
      2. 如果result的类型不是对象,返回result
  4. 抛出类型错误的异常。 由上述操作步骤可知:
  • 通过ToPrimitive的步骤6可知,当没有提供可选参数PreferredType的时候,hint会默认为"number"
  • 通过ToPrimitive的步骤4可知,可以通过定义@@toPrimitive方法来覆盖默认行为,比如规范中定义的Date日期对象和Symbol符号对象都在原型上定义了@@toPrimitive方法。

总结:

  1. 如果hint"string",先执行toString,如果没有返回原始值继续执行valueOf方法
  2. 如果hint"number",先执行valueOf,如果没有返回原始值继续执行toString方法

4、hint

hint可以理解为根据场景自动判断更倾向于转换成string还是number,如果无法判断倾向于转化哪一个,就是default。下面我们来模拟一下转化过程中获取hint值的过程

const obj = { 
    [Symbol.toPrimitive](hint) { 
        console.log(`hint: ${hint}`); 
    } 
}; 
const arr = []
arr[obj]
// hint: string 
const obj = { 
    [Symbol.toPrimitive](hint) { 
        console.log(`hint: ${hint}`); 
    } 
}; 
-obj
// hint: number
var a = [1, 2, 3]
a[Symbol.toPrimitive] = function (hint) {
    console.log(`hint: ${hint}`);
}
'' + a
// hint: default

5、总结:

1、引用类型转化的时候,先确定是否有@@toPrimitive方法,优先级高于OrdinaryToPrimitive(input, hint),如果前者能返回原始类型那就不需要执行后面的操作了

2、判断hint值

3、如果hint"string",先执行toString,如果没有返回原始值继续执行valueOf方法

4、如果hint"number",先执行valueOf,如果没有返回原始值继续执行toString方法

还有最后一篇文章,结合大量的题目映正前两节所掌握的内容,会更加有感觉

参考:segmentfault.com/a/119000001…