JS 中的隐式强制类型转换原理探究
注意查看最新 es 规范,本文对照的是的 es7 标准(这个界面比 es6 的更易读):
262.ecma-international.org/7.0/#sec-to…
最新标准查看:
实际的类型转换结果和浏览器等宿主环境的支持规范的版本有关,规范迭代会导致某些规则或属性发生变换。
背景
做一个需求的过程中,疏忽了类型导致类型不匹配的报错。后续对输入进行强制类型转换解决了该问题。
不久前看了《你不知道的 JavaScript》,但其中还有一些似是而非的点比较模糊,所以正好借机重新整理一下。
JS 中强制类型转换有隐式显式两种类型
- 显示转换需要调用构造函数,比较明显,如调用
Number
,String
。
- 隐式转换多见于
==
、if
语句中,当你使用操作符对不同类型数据进行处理的时候会发生,不太明显。
相对于显式强制类型转换(以下称显示转换)能明显得出符合预期的类型,隐式强制类型转换(以下称隐式转换)似乎蒙着神秘的面纱,让人捉摸不透。本文着重介绍隐式转换。
为什么要写隐式类型转换?
当我们在意代码可读性、风格一致性、代码简洁性等情况下,需要这么处理。毕竟要向项目中存在的成熟的代码风格靠拢,另外在条件语句中判断空字符串操作确实比较常见,也比较推荐。
在生产开发过程中,需要避免使用很生僻的类型转换
隐式转换的要点:转换成相同类型
用例子说话
数组相加
+
过程中会将对象(Object
)转换成原始类型(Primitive
),a
和 b
都会被转化为原始类型。
const a=[1,2];
const b=[3,4];
const res=a+b;
console.log(res);
// 操作数 a 是对象,对 a 调用 toPrimitive
console.log('a.valueOf()',a.valueOf());// 还是数组 [1, 2]
console.log('typeof a.valueOf()',typeof a.valueOf());// object
console.log('a === a.valueOf()',a === a.valueOf());// true
// 数组的 valueOf() 返回数组 [1, 2],无法得到简单基本类型,于是调用 toString
console.log('a.toString()',a.toString());// 1,2
console.log('typeof a.toString()',typeof a.toString());// string
//数组的 toString() 返回字符串,结果就是两个字符串相加
console.log('a.toString()+b.toString()',a.toString()+b.toString());
字符串和数组相加
+
过程中会将对象(Object
)转换成原始类型(Primitive
),a
保持不变,b
从对象类型转换为原始类型。
const a='word';
const b=[1,2];
const res = a + b;
console.log(res);
// 操作数 a 是字符串,直接返回原始参数
// 操作数 b 是对象,对 b 调用 toPrimitive
console.log('b.valueOf()', b.valueOf());// 还是数组 [1, 2]
console.log('typeof b.valueOf()', typeof b.valueOf());// object
console.log('b === b.valueOf()', b === b.valueOf());// true
// 数组的 valueOf() 返回数组 [1, 2],无法得到简单基本类型,于是调用 toString
console.log('b.toString()', b.toString());// 1,2
console.log('typeof b.toString()', typeof b.toString());// string
//数组的 toString() 返回字符串,结果就是两个字符串相加
console.log('a.toString()+b.toString()', a.toString() + b.toString());
布尔相加
+
调用 default
转换,最终调用 number
转换。
const a=true;
const b=false;
const res = a + b;
console.log(res);
console.log('Number(a)', Number(a));// 1
console.log('Number(b)', Number(b));// 0
console.log('Number(a) + Number(b)', Number(a) + Number(b));// 1
数字除以字符串
/
调用 number
转换。这里换成两个字符串相除也是一样的效果。
const a=12;
const b='6';
const res = a / b;
console.log(res);
console.log('Number(a)', Number(a));// 12
console.log('Number(b)', Number(b));// 6
console.log('Number(a) / Number(b)', Number(a) /Number(b));// 2
字符串和布尔宽松等于
==
调用 default
转换,最终调用 number
转换。
const a='true';
const b=true;
const res=a==b;
console.log(res);
console.log('Number(a)', Number(a));// 'NaN'
console.log('typeof Number(a)',typeof Number(a));
console.log('Number(b)', Number(b));// 1
数组和 null
比较
const a=[1];
const b=null;
const res=a>b;
console.log(res);
console.log('a.toString()',a.toString());// "1"
console.log('typeof a.toString()',typeof a.toString());// string
console.log('Number',Number(b));// 0
const c='1';
const d=0;
const res2=c>d;// true
console.log(Number(c)>Number(d));// true
console.log(Number(c)===c);// false
console.log(Number(d)===d);// true
为了演示效果,只设置一个数组元素,不然会出现 NaN
的情况。
ToString、ToNumber 操作过程中会涉及 toPrimitive
ToString
、ToNumber
对 Object 的转换会涉及到抽象操作 toPrimitive
,是重点。除此之外,简单提一下这两类操作中的一些特性。
ToString:262.ecma-international.org/7.0/#sec-to…
ToNumber:262.ecma-international.org/7.0/#sec-to…
查看对象的种类一般通过 Object.prototype.toString()
来查看。同时,它可以通过 toStringTag
改写。
function Foo() { }
const a = new Foo();
console.log(a.toString());// [object Object]
Foo.prototype[Symbol.toStringTag] = 'Foo';
console.log(a.toString());// [object Foo]
抽象操作 toPrimitive
262.ecma-international.org/7.0/#sec-to…
总的来说,转换的主要步骤如下
- 调用
obj[Symbol.toPrimitive](hint)
如果这个方法存在,
-
否则,如果
hint
是string
- 尝试调用
obj.toString()
或obj.valueOf()
,无论哪个存在。
- 尝试调用
-
否则,如果
hint
是number
- 尝试调用
obj.valueOf()
或obj.toString()
,无论哪个存在。
- 尝试调用
- 如果
valueOf()
和toString()
均不返回基本类型值,会产生TypeError
错误。
抽象操作 ToPrimitive
接受一个输入参数和一个可选的参数 PreferredType
。抽象操作 ToPrimitive
将其输入参数转换为一个非对象类型。如果一个对象能够转换到一个以上的原始类型,它可以使用可选的提示 PreferredType
来选择该类型。分为两步。
-
ToPrimitive(input [, PreferredType])
主要执行步骤
-
OrdinaryToPrimitive(input, hint)
主要执行步骤
hint 的判断方式
- 在
preferredType
参数的帮助下,不同的操作符可以触发number
或string
转换。但有两个例外:==
和二进制+
运算符会触发default
的转换模式(没有指定首选类型,或等于default
)。
-
大多数内置类型都假定
number
转换是默认的(Date
默认string
转换)
比如在把变量作为键值使用的时候,会调用ToPrimitive
把键值转化为原始数据类型,并且PreferredType
的值是hint string
。
调用 >
,>=
时,PreferredType
是 hint number
更多情况可以参考红宝书以及一些网上资源都有总结。
定义@@toPrimitive
设置 hint
这里以 Date
中覆盖 toPrimitive
举例,Date
类型 hint
的默认值是 string
,与相对于其他内建 ECMAScript
对象不同。
- 设置
hint
为default
- 设置
hint
为string
- 设置
hint
为number
- 设置
hint
为无效类型
改写 toString 和 valueOf
const a = [1, 2]
a.toString = function () {
return 'origin is array'
}
const res='the ' + a ;
console.log(res);
const a = [1]
a.valueOf = function () {
return 'origin is array'
}
console.log('a.valueOf()',a.valueOf());
console.log('a.toString()',a.toString());
console.log('typeof a.toString()',typeof a.toString());
const arr=[];
arr[a]='firstElement'
console.log(arr)
参考链接
录屏 | tech.bytedance.net/videos/7160… | ||
---|---|---|---|
和 && | zh.javascript.info/logical-ope… | ||
ToPrimitive 和 OrdinaryToPrimitive | segmentfault.com/a/119000001… | ||
类型转换教程 | www.freecodecamp.org/news/js-typ… | ||
www.youtube.com/watch?v=wFi… | |||
为 Date 设置 hint | www.geeksforgeeks.org/javascript-… | ||
总结部分 | zh.javascript.info/object-topr… |
转载自:https://juejin.cn/post/7157663823837528095