Skip to content

强制转换

Ned2017 edited this page Jan 22, 2021 · 12 revisions

https://github.com/getify/You-Dont-Know-JS/blob/1ed-zh-CN/types%20%26%20grammar/ch4.md#json-%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%96

https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/types%20%26%20grammar/ch4.md

强制转换复杂多变,晦涩难懂,一直都不得要领。知其然而不知其然,很多时候知道是这么转换的,但是却不清楚是如何发生的,所以尝试着系统学习一番,以此来记录总结我所学习到的知识,主要参考于《你不懂JS》第四章。

首先需要明白的一点是强制转换并不是一无是处,它有它的优点和用途,我们不能把孩子和洗澡水一起倒了。我有幸能和作者达成这样的共识。

强制转换(coercion):将一个值隐含地转换成不同类型的值。

然而,在 JavaScript 中,大多数人将所有这些类型的转换都称为 强制转换(coercion),所以我偏好的区别方式是使用“隐含强制转换(implicit coercion)”与“明确强制转换(explicit coercion)”。

称呼并不重要,关键是这些转换是如何达到它们想要的效果的。

ToString

当一个非string值转换为string表现形式时,由ToString抽象操作处理。

对象默认的toString()返回内部的[[Class]],例如[object Object],可以根据这个方法去判断值类型。

例如:Object.prototype.toString.call([1])

JSON字符串化

如果一个object值定义了一个toJSON()方法,则首先调用这个方法进行序列化。

toJSON应当返回合适的实际普通值,然后JSON.stringify()处理字符串化。

可以用stringify和parse进行深克隆。

ToNumber

如果任何非number值,以一种要求它是number的方式被使用,就会发生规范9.3部分定义的ToNumber抽象操作。

Object将会首先转换成对应的基本类型,然后再调用ToNumber进行强制转换。

ToPrimitive

在转换成基本类型时,ToPrimitive抽象操作会首先查找是否存在valueOf()方法。如果存在且该方法返回一个基本类型,那么则会使用该方法进行强制转换;否则会用toString()方法代替,如果它可用的话。

测试了下优先调用自定义方法,没有再考虑原生方法。即valueOfCustom > toStringCustom > valueOfNative > toStringNative。

如果都不可用,那么会抛出TypeError的错误。

所以有意思的是,你可用创造一个既没有valueOf()方法也没有toString()方法的对象,这样它就不能被强制转换了。最简单的方式是Object.create(null)

ToBoolean

假值

所有被强转后为false的值,其余的为真值。

假值列表:

  • undefined
  • null
  • false
  • -0, +0, NaN
  • ""

对象也存在假值,例如广为人知的历史遗留的document.all.

显式强制转换

string和number之间的相互转换

用内建的Number()和String()方法显式强制转换,通过不使用new关键字返回基本类型,而不是包装对象。

String()用ToString操作的规则进行强制转换,将其他类型的值转换成string类型。

Number()用ToNumber操作的规则进行强制转换,将其他类型的值转换成number类型。

此外,还有其他显式转换方式:

var a = 42;
var b = a.toString();

var c = "3.14";
var d = +c;

b; // "42"
d; // 3.14

~

跳过待学

解析数字字符串

parseInt正常来说仅接受字符串参数,但是如果你传递非字符串和非数字类型的值,JS引擎会首先帮你把值强制转换成字符串。

所以千万禁止不要将非字符串参数传入parseInt

parseInt(Infinity, 19); // 18
parseInt( 0.000008 );		// 0   ("0" from "0.000008")
// 小数点前多于21位或小数点后紧跟的0多于5个将转为科学计数法表示
// 由此看来parseInt会将数字首先转换成字符串再转回去
parseInt( 0.0000008 );		// 8   ("8" from "8e-7")
parseInt(1234567890123456789012)// 1
parseInt( false, 16 );		// 250 ("fa" from "false")
parseInt( parseInt, 16 );	// 15  ("f" from "function..")

parseInt( "0x10" );			// 16
parseInt( "103", 2 );		// 2

其他类型转布尔值

Boolean()通过ToBoolean强制转换。

!!是最常见的布尔值显式转换。

隐式强制转换

隐式转换也不全是坏处,我想它能让我们精简代码,把关注点放到重要的意图上。

用于简化的隐式

作者认为隐式转换有好的部分,所以不要把孩子和洗澡水一起倒出去。

字符串和数字之间的隐式转换

{} == {}对象间比较的是引用地址。

number + ""会把number隐式转换成字符串,作者认为这是一个隐式转换有用的列子,尽管有很多人批评隐式转换,但是还是会在代码中使用它。

a + ""隐式转换会先调用valueOf()方法,通过内部的ToString抽象操作转换成字符串,而String(a)显式转换会直接调用toString()

如果想把字符串转换成数字,则用a - 0,或者不怎么常用的a/0a*0,因为这些操作符只用于数字类型的操作。

布尔和数字之间的隐式转换

作者认为通过隐式转换把特定类型的复杂布尔逻辑简化成简单的数字加法是隐式转换最闪亮的地方。

任何值隐式转为布尔

操作符&&和||总是返回其中一个值,而不是像C或PHP一样返回结果,所以在JS中作者更愿意把它们称作选择操作符,而不是逻辑操作符。

Symbol隐式转换

Symbol值禁止被隐式转换成字符串,如果发生则会抛出错误。

但是它们能被隐式转换成布尔值,并且总是true。

宽松等价 vs 严格等价

一种比较常见的误解是==比较值,而===不仅比较值,还比较类型。甚至很多有名的JS书籍和博客都这么说。

正确的理解应该是==允许强制转换,而===不允许。

等价性的性能

因为==可能会进行强制转换,所以它可能会慢一丢丢,但是这不是关键。

我们选择的理由是应不应该进行强制转换。

抽象等价性

ES5规范11.9.3

同类型值比较

  • undefined等于undefined
  • null等于null
  • +0等于-0
  • 比较对象时比较它们的地址

不同类型比较

  • nudefined等于null,而且它俩跟任何其他值比较都是false,所以把它们看成相同的值是安全的。
  • 如果一个是number另一个是string,则ToNumber(string),number保持不变
  • 如果一个是boolean,则将boolean转为数字,也就是说boolean相当于数字1或0
  • 如果是将Object和number或string比较,则ToPrimitive(Object),也就是说对象强制转换的优先级最高

对象相互比较时,==和===的行为是一样的。

根据规则,"42"和boolean值做比较时,会首先将boolean转为数字,再将"42"转为数字,所以"42"即不==false,也不==true。怎么会有一个值既不是真值,也不是假值,这听起来很疯狂,但是这不是你的错,而是你的大脑在耍你。因为这不是一个boolean测试。

所以最好的建议是,永远不要使用==true或==false

边界情况

JavaScript并没有错,错的是那些拿它做错误的事的开发者。所以当程序出错时,多审视自己,而不要去找语言的麻烦。

[] == ![];		// true

这个表达式会首先执行![],因为空数组是真值,所以转为false,之后就显而易见啦(太懒不想写了)。

作者认为数组的强制转换除了转为字符串没有更好的方式了。

请注意[null]强转后为"",而String(null)则为"null"undefined同理。

两条最重要的准则:

  • 如果任何一边有true或false,千万不要用==。

  • 如果任何一边有[],""或0,最好不要用==。

抽象关系比较

对象间的比较 a<b

首先调用ToPrimitive强制转换两边的值,如果其中一个结果不是string,则调用ToNumber将值转为number。

var a = { b: 42 };
var b = { b: 43 };

a < b;	// false
a == b;	// false
a > b;	// false

a <= b;	// true
a >= b;	// true

a<=b会首先判断b<a,然后取反。

复习

强制转换有很多坏处,但是它在一些案例中十分有用,所有对于有责任心的JS开发者有必要好好学习这些内容。

不管是显式转换还是隐式转换,只要使用得当,它们都有助于提高代码的可读性。