JavaScript 对象转值的原理

求值的基础
JavaScript 中存在的六种值类型:Null, Undefined, Boolean, Number, String, Symbol。

既然是对象,就说明它存在,不能转为 Null 和 Undefined 类型。

同理,对象一定非空,转 Boolean 类型为 true

对象不会自动发生 Symbol 转换,除非使用强制类型转换函数 Symbol()

比较常见的情况是,对象转 Number 和 String。

根据 EMACScript 规范,对象转值(toPrimitive)类型会产生三种的 hint,分别是 number 、 string 、 default

我们可以根据 hint 来自定义转换的目标值。

number
显式地调用 Number() 函数,或者对象参与数学计算时:

let a = Number(obj); // NaN
let b = +obj; // NaN
let c = obj1 - obj2; // NaN

虽然默认的计算结果为 NaN ,但它属于 number 转换。

string
显式地调用 String() 函数,或者某些期望接受 String 类型的参数,当传入对象时,会发生对象到 string 的转换。

String(obj); // '[object Object]'
console.log( `${obj}` ); // [object Object]
anotherObj[obj] = 123; // { '[object Object]': 123 }

字符串模板接受字符串参数,传入非字符串类型,会发生隐式转换。

对象的属性名支持字符串和 Symbol 类型, 除这两种类型外,会默认转为字符串。

default
当不能确定某个操作期望得到什么类型时,例如二元运算符 + 的结果,可能是字符串也可能是数字。因此无法确定是把运算数转为 number 还是 string 。

这会产生一个 default 类型的 hint。
对象与 String, Number, Symbol 类型进行判等,也无法确定转换的目标类型。

// 产生 default 类型的 hint
obj1 + obj2;
obj == 1;

我们不必记忆哪些情况是 default 类型的转换,后面会讲到,它的转换处理流程和 number 相同。
特殊的是,比较操作符,例如: > 、 < ,虽然能用于比较 Number 和 String 类型,但它们的 hint 是 number ,这是历史遗留问题。

类型转换的过程
在实现上,对象到值类型的转换,会分三个步骤:
1、判断对象是否存在一个属性名为 Symbol.toPrimitive 的函数,如果存在调用它,并传入以上三种之一的 hint 作为参数。如果这个函数返回值类型,就是对象转值的结果;如果返回非值类型,会抛出异常。如果不存在这个属性,执行下一步。

2、hint 类型如果是 string ,会依次调用对象的 toString() 和 valueOf() 函数,直到某一个函数在该对象实现,并返回值类型为止。如果返回非值类型,会忽略这个方法。

3、hint 类型如果是 number 或者 default ,会依次调用对象的 valueOf() 和 toString() ,直到某个函数在该对象实现,并返回值类型为止。如果返回非值类型,会忽略这个方法。

Symbol.toPrimitive
一个实现 Symbol.toPrimitive 属性的 user 对象:

const user = {
  name: 'Smallfly',
  age: '27',
  [Symbol.toPrimitive](hint) {
    console.log( `[${hint}]` );
    if (hint === 'string') {
      console.log('name: ' + this.name);
      return this.name;
    } else {
      console.log('age: ' + this.age);
      return this.age;
    }
  }
}

console.log(`${user}`); // [string] name: Smallfly -- Smallfly
console.log(+user); // [number] age: 27 -- 27
console.log(user + 1); // [default] age: 27 -- 27

user 传入字符串模板触发了 string 转换; + 一元运算符触发了 number 转换; + 二元运算符触发 default 转换。
Symbol.toPrimitive 函数包含了 user 转简单值类型的所有情况。

toString/ValueOf
如果对象没有实现 Symbol.toPrimitive 方法,会继续尝试调用 toString 和 valueOf 方法。

const user = {
  name: 'Smallfly',
  age: '27',
  toString() {
    console.log('[toString]');
    console.log('name: ' + this.name);
    return this.name;
  },
  valueOf() {
    console.log('[valueOf]');
    console.log('age: ' + this.age);
    return this.age;
  }
}

console.log(`${user}`); // [toString] name: Smallfly
console.log(+user); // [valueOf] age: 27 -- 27
console.log(user + 1); // [valueOf] age: 27 -- 27

从结果上看, toString/valueOf 的组合功能和 Symbol.toPrimitive 完全一样。
Symbol.toPrimitive 是 ES6 引入的新功能,实现了对象转值类型方法的统一。
对象如果没有实现这两个方法,默认使用 Object 对象的 toString()/valueOf() 方法, toString() 方法返回 [object Object] , valueOf() 方法返回对象自身。

const obj = {};
obj.toString(); // [object Object]
obj.valueOf() === obj; // true

[ ] + [ ] = ''
[ ] + [ ] 表达式的结果为空字符串,我们通过这个结果观察一下对象转值的具体过程。

const a = [];

a.toString = function() {
  return 'array string';
}

a.valueOf = function() {
  return 123;
}

console.log( `${a}` ); // array string
console.log(+a); // 123

以上代码证明空数组 [] 没有实现 Symbol.toPrimitive 方法,不然 toString/valueOf 方法不会被调用。

const a = [];

a[Symbol.toPrimitive] = function(hint) {
  console.log(hint);
  return 123;
}

console.log(a + a);
// default
// default
// 246

以上代码证明 [ ] 参与加法运算产生的 hint 为 default。
根据前面提到的类型转换的规则,[ ] + [ ] 表达式触发 [ ] 转值类型会依次调用 valueOf() 和 toString() 方法。
然而,[ ].valueOf() 返回值是空数组自身,并不是值类型,因此继续尝试调用 toString() 方法。
[ ].toString() 的结果是空字符串 ‘’ 。因此,[ ] + [ ] 表达式的计算结果是 ‘’。

发布了83 篇原创文章 · 获赞 3 · 访问量 1613

猜你喜欢

转载自blog.csdn.net/weixin_45959525/article/details/104308187