ES5中部分操作类型转换小抄

JS 在执行过程中会发生类型隐式转换,这里抄了 ECMA 规范的『+』和『==』运算符的解释

ECMA 规范

加法运算符(+)

https://www.w3.org/html/ig/zh/wiki/ES5/%E8%A1%A8%E8%BE%BE%E5%BC%8F#.E5.8A.A0.E6.B3.95.E8.BF.90.E7.AE.97.E7.AC.A6.EF.BC.88.2B.EF.BC.89

加法运算符启动字符串相接或是数值相加。

产生式 AdditiveExpression : AdditiveExpression + MultiplicativeExpression 按照下面的过程执行:

  1. lref 为解释执行 AdditiveExpression 的结果。
  2. lvalGetValue(lref)
  3. rref 为解释执行 MultiplicativeExpression 的结果。
  4. rvalGetValue(rref)。
  5. lprimToPrimitive(lval)。
  6. rprimToPrimitive(rval)。
  7. 如果 Type(lprim) 为 String 或者 Type(rprim) 为 String,则:
    1. 返回由 ToString(lprim) 和 ToString(rprim) 连接而成的字符串。
  8. 返回将加法运算作用于 ToNumber(lprim) 和 ToNumber(rprim) 的结果。参见 11.6.3 后的注解。

注:在 第5步第6步ToPrimitive 的调用没有提供暗示类型。所有除了 Date 对象的 ECMAScript 原生对象会在没有提示的时候假设提示是 NumberDate 对象会假设提示是 String。宿主对象可以假设提示是任何东西。

抽象相等比较算法

https://www.w3.org/html/ig/zh/wiki/ES5/%E8%A1%A8%E8%BE%BE%E5%BC%8F#.E6.8A.BD.E8.B1.A1.E7.9B.B8.E7.AD.89.E6.AF.94.E8.BE.83.E7.AE.97.E6.B3.95

xy 为值进行 x == y 比较会产生的结果可为 truefalse。比较的执行步骤如下:

  1. 若 Type(x) 与 Type(y) 相同, 则
    1. 若 Type(x) 为 Undefined, 返回 true
    2. 若 Type(x)为 Null, 返回 true
    3. 若 Type(x)为 Number,则
      1. xNaN,返回 false
      2. yNaN,返回 false
      3. xy 为相等数值,返回 true
      4. x+0y−0,返回 true
      5. x−0y+0,返回 true
      6. 返回 false
    4. 若 Type(x) 为 String,则当 xy 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。否则,返回 false
    5. 若 Type(x) 为 Boolean,当 xy 为同为 true 或者同为 false 时返回 true。否则,返回 false
    6. xy 为引用同一对象时返回 true。否则,返回 false
  2. xnull 且 y 为 undefined,返回 true
  3. xundefined 且 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) 的结果。
  8. 若 Type(x) 为 StringNumber,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。
  9. 若 Type(x) 为 Object 且 Type(y) 为 StringNumber,返回比较 ToPrimitive(x) == y 的结果。
  10. 返回 false

注:根据上述等于的定义:

  • 字符串比较可以以:"" + a == "" + b 硬性触发。
  • 数值比较可以以:+a == +b 硬性触发。
  • 布尔比较可以以:!a == !b 硬性触发。

注:等于运算符有以下的不变量:

  • A != B!(A == B) 相等。
  • 除了 AB 的执行顺序以外,A == BB == A 相等。

注:等于运算符不总是可传递。举例来说,两个代表相同 String 值但是不同的 String 对象会分别与 String==,但是两个对象间不相等。

注:String 的比较使用代码单元序列的简单等号比较。这里不打算使用更复杂的、语义化的字符或字符串序列,和 Unicode 规范的整理序列进行比较。因此,在 Unicode 标准中相等的 String 值可能在本算法中不相等。也就是,这个算法假定了所有字符串已经正规化。


理解学习

字符串/数字的转换规则

在将目标 O 转换为 StringNumber 类型时,有以下操作:

如果期望类型是 String,优先尝试调用 OtoString 方法,如果调用结果是基本类型,则将其转换为 String 并返回结果;如果 toString 方法不存在,或者返回结果不是基本类型,则继续尝试调用 OvalueOf 方法,如果结果是基本类型,则将其转换为 String 类型并返回结果,如果结果不是基本类型,则抛出错误。

转换为 Number 类型的过程与转换为 String 的过程基本一致,区别在于转换为 Number 是优先调用 OvalueOf 方法。

这里 O 是一个对象,需要注意的是基本类型的情况,基本类型没有方法属性,但是 JS 存在自动包装机制,会自动转换成对应的类型对象实例,如:(1).toString() 相当于 new Number(1).toString(),所以你没有办法重写一个基本类型变量的toStringvalueOf方法,只能重写它的原型对象的方法,例 a=1; a.toString(); 内部逻辑是 (new Number(a)).toString(),每次都会生成一个新对象

// 对象默认继承了Object,其拥有默认的`valueOf`及`toString`方法
var obj1 = {};
String(obj1); // -> [object Object]
Number(obj1); // -> NaN (相当于Number('[object Object]')),因为valueOf返回的结果不是基本类型,所以使用 toString 方法的返回值

// 覆盖了默认的 valueOf、toString 方法
var obj2 = {valueOf: () => 1, toString: () => 'a'};
String(obj2); // -> a
Number(obj2); // -> 1

[Symbol.toPrimitive] 属性

ES6 添加了新的基本类型: Symbol,Symbol 的 toPrimitive 静态属性可以用来给对象部署类型转换的方法,其调用优先级高于 valueOftoString ,且如果存在 [Symbol.toPrimitive] 属性,不论其执行结果如何,不会再调用 valueOf 及 toString 方法(即覆盖)。其执行逻辑与 valueOf 及 toString 类似,都是返回基本类型再转换成目标类型,否则报错。

Symbol.toPrimitive 属性方法有一个参数 type 表示期望类型,取值有 default,number,string

var obj = {
  [Symbol.toPrimitive](type){
    console.log(type);
    return type === 'number' ? 1 : 'toPrimitive';
 },
 valueOf(){return 2;},
 toString(){return 'toString';}
}

Number(obj); // -> 1
String(obj); // -> toPrimitive

type 有一个特殊的取值: default,在执行 加法运算符(+) 操作的时候,会进行这个类型的转换,就是上面 加法运算符(+) 规范的第 5、6 条所涉及的 ToPrimitive 操作,且在下面的注释里提到了:『没有提供暗示类型』,没有即取 default
所以上面的 obj 在执行 obj+1 的时候打印的信息是:

obj+1
// default
// toPrimitive1

扩展理解

在『加法运算符』规范的注释里还解释道:

所有除了 Date 对象的 ECMAScript 原生对象会在没有提示的时候假设提示是 Number;Date 对象会假设提示是 String。宿主对象可以假设提示是任何东西。

也就是说 ECMAScript 原生对象在执行 + 操作的时候,Date 会转换为String基本类型,而其他原生对象会转换为Number基本类型,而根据之前总结的『字符串/数字的转换规则』,换句话说,如果没有部署[Symbol.toPrimitive]方法,Date 就是先执行的 toString 方法, 其它原生对象先执行的 valueOf 方法。

d = new Date;
console.log(d + 1);
// 输出: 'Mon Feb 01 2021 19:22:55 GMT+0800 (中国标准时间)1'

// 重写 toString 方法
d.valueOf= () => 1;
d.toString = () => 2;
console.log(d + 1);
// 输出 3

// ------------

// 重写数组的valueOf 与 toString 方法
arr = [];
arr.valueOf = () => 1;
arr.toString = () => 'a';
console.log(a+1);
// 输出 2

确实是这样。再来创建个对象试一下

obj = {
  toString(){return 'a'},
  valueOf(){return 1}
}
console.log(obj+1);
// 输出 2
console.log(obj + '1');
// 输出 "11"

也遵循这一规则。

所以简单总结的个人理解就是:在『+』、『==』运算中对象隐式转换为基本类型的规则为 除了原生 Date 对象是优先调用 toString,其它对象优先调用 valueOf,前一个返回了一个基本类型的值,就会停止调用下一个

注. 这里没有把 toPrmitive 方法放进来一起讨论,如果有 toPrimitive,都是执行对象的 Stmbol.toPrimitive('default') 方法

你可能感兴趣的:(ES5中部分操作类型转换小抄)