JS隐式转换
し'veBO 人气:0前言
熟练掌握类型转换,理解其中的规律。可以让你的代码更简洁更安全。
在编写代码的时候,我们通常会有意无意的写出一些,存在类型转换的代码。这些代码有时的返回值总让人迷惑,比如下列式子都是我们工作中常见的:
var a = 1; var b = '2'; var c = a + b; // '12'; var d = 43; d == '43'; // true d === '43'; // false
上面都是比较常见的,让我们再看几个例子:
// 例子1 const obj = { toString() { return : 'xxx'; } } const obj2 = {}; obj.toString(); // "xxx" 当然,我们还是可以使用Object.prototype.toString.call(obj)的,因为这是调用了Object对象,但是如果改写了Object的toString方法,那情况就不一样了。 obj2.toString(); // "[object Object]" // 例子2 const a = [1,2,3]; const b = [4,5]; a + b; // "1,2,34,5" // 例子3 false == []; // true false == ""; // true false == null; // false false == undefined; // false [] == ![]; // true
看到上面的结果,是否你也感觉头疼。现在让我们开始探索,看看这中间都发生了怎样的类型转换。
在讲强制类型转换是我们先思考一个问题,不同的数据类型值都会发生什么类型转换?在什么情况下会发生类型转换?具体是怎么转换的?
发生了类型转换通常都是操作值做了【抽象操作】(ES5规范第9节中定义了一些“仅供内部使用的操作”)。这里我们简单介绍一下ToString
,ToNumber
,ToBoolean
同时还有一个ToPrimitive
。
ToString
ToString
是对非字符串进行强制类型转换。
- 对于基本数据类型,ToString则是进行字符串化,null转化为"null",undefined转化为"undefined",true转化为"true"。对于数字的ToString,需要注意的是,数字的极大值跟极小值会转为指数形式表示。
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000; a.toString(); // "1.07e21";
- 对于引用数据类型的ToString,通常会调用引用对象中的 toString()进行转化,如果对象没有这个方法则会发生报错。(注意:从ES5开始,使用Objest.create(null)创建的对象,因为创建对象的
[[Prototype]]
为null,所以不会有toString()和valueOf()这两个方法)。
var a = [1,2,3,4]; a.toString(); // "1,2,3,4" var b = { a: 42, foo: 'foo' } b.toString(); // "[object Object]"
ToNumber
- 对基本数据类型,ToNumber跟调用Number转数字一样。
Number(true); // 1 Number(false); // 0 Number(null); // 0 Number(undefined); // NaN Number(""); // 0 Number("123"); // 123 Number("123aaa"); // NaN
- 对于引用数据类型,会先判断对象是否有valueOf(),如果有则将返回的值进行类型转换。如果没有,则判断是否有toSrting(),并将其返回值做类型转换。如果这两个方法都没有则报错TypeError。
var a = { valueOf() { return "42"} }; var b = { toString() { return "42"} }; var c = [1,2]; c.toString = function() { return this.join(""); } Number(a); // 42 Number(b); // 42 Number(c); // 12 Number([]); // 0 Number([1,2]); // NaN
ToBoolean
对于抽象操作ToBoolean
,ES5规范9.2中列出一些假值,既对这些值进行布尔强制类型转换时,会转为false的值。除了这些值以外,所有值进行强制类型转换时,都会得到true。
undefined null "" // 空字符串 +0, -0, NaN false
ToPrimitive
抽象操作ToPrimitive
,其实在ToSting
,ToNumber
,ToBoolean
获取对象原始值的时候,就是通过ToPrimitive
来获取的,获取完原始值之后,再进行toString(),Number(),Boolean()等类型转换。
总结,在对值进行类型转换时,如果是基本数据类型,则直接对使用该值进行类型转换;如果是引用类型,会先通过ToPrimitive
获取原始值,然后使用获取的原始值进行类型转换。
而之前说的【先检查对象是否包含valueOf()
,如果没有在检查toString()
】这个过程就是 ToPrimitive
。
现在我们分析完类型转换的过程都具体做了什么操作了,现在我们看下,在那些操作下会发生类型转换。
运算符 +
如果操作数中存在字符串,则进行字符串拼接;如果操作数是对象,则会对对象进行 ToPrimitive
抽象操作以获取原始值,通常这操作都会获取到字符串。最后进行 + 操作。
var a = [1,2]; var b = [3,4]; a + b; // "1,23,4"
逻辑判断相关
- if(..)
- while(..)和do..while(..)
- 三元表达式 ?:
- for(..; ..; ..){}
- 逻辑或|| 和 逻辑与&& 左边的操作数
上面的语句中所包含的判断表达式,都会将非布尔值通过抽象操作ToBoolean
,将值转为boolean。
== 宽松对等规则
1. 字符串和数字之间的相等比较
- 如果Type(x)是数字,Type(y)是字符串,则返回 x === ToNumber(y)的结果。
- 如果Type(x)是字符串,Type(y)是数字,则返回 ToNumber(x) === y的结果。
var x = 42; var y = '42'; x === y; // false x == y; // true 这里转换为: 42 == ToNumber('42') => true
2. 其他类型和布尔类型的相等比较
- 如果Type(x)是布尔类型,则返回 ToNumber(x) === y 的结果。
- 如果Type(y)是布尔类型,则返回 x === ToNumber(y)的结果。
var x = true; var y = '42' x === y; // false x == y; // false // false 这里转换为:toNumber(true) == toNumber('42') => 1 == 42 => false
3. null 和 undefined 之间的相等比较
- 如果x为null,y为undefined,则结果为true。
- 如果x为undefined,y为true,则结果为true。
注意: 在 == 中null和undefined相等(也与自身相等),除此之外其他值都不和他们相等。 所以可以使用一特性,对值进行判断是否等于null或undefined
var x = null; var y; x === y; // false x == y; //true
4. 对象和非对象之间的相等比较
- 如果Type(x)是字符串或数字,Type(y)是对象,则返回 x == ToPrimitive(y)的结果。
- 如果Type(y)是字符串或数字,Type(x)是对象,则返回 ToPrimitive(x) == y的结果。
var a = 42 var b = [42] a == b; // true // [42]调用ToPrimitive,返回"42",变成 "42" == 42,然后又变成 42 == 42,最后两者相等。 var a = "abc"; var b = Object(a); // new String( a )一样 a === b; // false a == b; // true
这里有些值不一样
var a = null; var b = Object(a); // 和Object()一样 a == b; // false var c = undefined; var d = Object(c); // 和Object()一样 c == d; // false var e = NaN; var f = Object(e); // 和new Number(e) 一样 e == f; // NaN == NaN => false
因为js中,没有undefined 和 null 对应的封装对象。所以对他们进行Object封装跟调用Object()一样,返回一个常规对象。
而NaN是因为,NaN本身就不等于自己。
总结
总的来说:在宽松对比中,对比的值具有这样的转换优先级,对象 > 非对象(非对象中:字符串,布尔都会ToNumber转为数字,进行比较)
。同时需要注意 NaN(唯一不等于自己的值),undefined和null这几个值的转换。
当然,宽松对等中不同值对比,才会进行类型转换。如果是相同类型的比较,不会进行类型转换。"0" == "" 返回false
。因为这是字符串"0"
和""
的对比,不会进行抽象操作ToNumber
。
理解了对象的类型转换都是ToPrimitive
操作,那么我们需要注意,任何情况下都不要重写对象的 valueOf()
和toString()
方法。
加载全部内容