JavaScript 做为一门弱类型的语言,在类型转换上很是的灵活。它在提供便利的同时,也带来了很多困惑。javascript
[] + []
, [] + {}
, ${obj}
,这些表达式计算结果是什么,又是如何得出这个结果的?java
参与数学计算的运算数必须是值类型,而且类型相同。在强类型的语言中,类型不一样的值参与计算会致使编译错误。数组
对于 js 来讲,它会在运行时「智能」的进行类型转换,获得一个使用者可能指望的结果。函数
但若是使用者对这个结果不满意,但愿自定义,就须要了解运行时类型转换的原理。ui
类型转换会发生在值类型与值类型,值类型与对象类型之间。这里咱们主要来探讨一下对象到值类型的转换过程。this
JavaScript 中存在的六种值类型:Null, Undefined, Boolean, Number, String, Symbol。spa
既然是对象,就说明它存在,不能转为 Null 和 Undefined 类型。code
同理,对象必定非空,转 Boolean 类型为 true
。对象
对象不会自动发生 Symbol 转换,除非使用强制类型转换函数 Symbol()
。ip
比较常见的状况是,对象转 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
,这是历史遗留问题。
在实现上,对象到值类型的转换,会分三个步骤:
一、判断对象是否存在一个属性名为 Symbol.toPrimitive
的函数,若是存在调用它,并传入以上三种之一的 hint 做为参数。若是这个函数返回值类型,就是对象转值的结果;若是返回非值类型,会抛出异常。若是不存在这个属性,执行下一步。
二、hint 类型若是是 string
,会依次调用对象的 toString()
和 valueOf()
函数,直到某一个函数在该对象实现,并返回值类型为止。若是返回非值类型,会忽略这个方法。
三、hint 类型若是是 number
或者 default
,会依次调用对象的 valueOf()
和 toString()
,直到某个函数在该对象实现,并返回值类型为止。若是返回非值类型,会忽略这个方法。
一个实现 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
转简单值类型的全部状况。
若是对象没有实现 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()
的结果是空字符串 ''
。所以,[] + []
表达式的计算结果是 ''
。
参考连接