资讯专栏INFORMATION COLUMN

掌握 Javascript 类型转换:隐式转换救救孩子

weapon / 1928人阅读

摘要:看下面的代码和会对操作数执行条件判断,如果操作数不是布尔值,会先执行类型转换后再执行条件判断。大家记住这个规则布尔值如果与其他类型进行抽象比较,会先用将布尔值转换为数字再比较。

在上一篇中我们聊过了 JS 类型转换的规则和我发现的一些常见书籍中关于类型转换的一些小错误,当碰到显示类型转换的时候大家可以按照这些规则去拆解出答案。但 JS 中存在一些很隐晦的隐式类型转换,这一篇就来谈下我对隐式类型转换的一些总结。

关于 JS 类型转换规则请看上一篇的内容:掌握 JS 类型转换:从规则开始

什么是隐式类型转换呢?顾名思义就是有时候你感觉不到这是类型转换但是实际上类型转换已经发生了。所以这个 "隐式" 取决于我们的理解和经验,如果你看不出来那就是隐式的。

下面按照我自己对于隐式转换的分类来逐个聊聊吧。

一元操作符 +、-
var a = "123";
var b = +a;
console.log(b); // 123

先来看看 +- 在一个类型值前面,这里会执行 ToNumber 类型转换。如果是 - 在前面的话,还会将结果的符号取反,如:-"123" 的结果是 -123。并且如果原类型是对象的话也是遵循 ToNumber 的转换规则,大家可以自己试试,这里就不再举多余的例子了。

二元操作符

接下来我们来看一下二元操作符相关的隐式转换,比如:+-&&||==等等这些。

相减 a - b
var a = "123";
var b = true;
console.log(a - b); // 122

当执行减法操作时,两个值都会先执行 ToNumber 转换,所以这个是比较简单的,当类型是对象时也是遵循同样的规则。

相加 a + b
console.log("123" + 4); // "1234"
console.log(123 + true); // 124

相加的情况有点复杂,但隐式转换的规则大家可以按照我总结的来记:

如果 + 的操作数中有对象,则执行 ToPrimitive 并且 hint 是 Number

如果 + 中有一个操作数是字符串(或通过第一步得到字符串),则执行字符串拼接(另一个操作数执行 ToString 转换),否则执行 ToNumber 转换后相加

这个相加操作的隐式转换规则看似有点麻烦,其实解析后还是很明确的。

第一步,先看操作数里面有没有对象,如果有就是执行 hint 是 Number 的 ToPrimitive 操作。大家可以回忆下上篇说的 ToPrimitive 的内容,这里要注意的是这里的 ToPrimitive 并没有将操作数强制转化为 Number 类型。因为 hint 是 Number,所以先执行 valueOf() ,如果返回了字符串那转换结果就是字符串了;如果返回的不是基本类型值才会执行 toString(),如果都没有返回基本类型值就直接抛异常了。

第二步,如果有一个操作数是字符串,那么整个结果就是字符串拼接的,否则就是强转数字加法;第二个操作数就会按这个规则进行对应的类型转换。

开头的代码说明了字符串加数字、数字加布尔值的结果按这个规则走的,下面我们来看看对象情况下的代码:

var a = Object.create(null);
a.valueOf = function() {
  return "123";
}
a.toString = function() {
  return "234";
}
console.log(a + 6); // "1236"

以上的执行结果说明了执行 ToPrimitive 并且 hint 是 Number 结论是正确的,因为 "123"valueOf 返回的。两个操作数相加的其他情况大家也可以自己试试,记住我上面的总结就完了。

a && b、a || b

在 JS 中我们都知道 &&|| 是一种"短路”写法,一般我们会用在 ifwhile 等判断语句中。这一节我们就来说说 &&|| 出现的隐式类型转换。

我们通常把 &&|| 称为逻辑操作符,但我觉得 《你不知道的 Javascript(中卷)》中有个说法很好:称它们为"选择器运算符"。看下面的代码:

var a = 666;
var b = "abc";
var c = null;

console.log(a || b); // 666
console.log(a && b); // "abc"
console.log(a || b && c); // 666

&&|| 会对操作数执行条件判断,如果操作数不是布尔值,会先执行 ToBoolean 类型转换后再执行条件判断。最后 &&|| 会返回一个操作数的值还不是返回布尔值,所以称之为"选择器运算符"很合理。

这里有个可能很多人都不知道的情况是:在判断语句的执行上下文中,&&|| 的返回值如果不是布尔值,那么还会执行一次 ToBoolean 的隐式转换:

var a = 666;
var b = "abc";
var c = null;

if (a && (b || c)) {
  console.log("yes");
}

如果要避免最后的隐式转换,我们应该这样写:

if (!!a && (!!b || !!c)) {
  console.log("yes");
}
a == b 和 a === b

从这里开始是 JS 中隐式转换最容易中坑的地方

首先我们先明确一个规则:"== 允许在相等比较中进行类型转换,而 === 不允许。"

所以如果两个值的类型不同,那么 === 的结果肯定就是 false 了,但这里要注意几个特殊情况:

NaN !== NaN

+0 === -0

ES5 规范定义了 == 为"抽象相等比较",即是说如果两个值的类型相同,就只比较值是否相等;如果类型不同,就会执行类型转换后再比较。下面我们就来看看各种情况下是如何转换的。

null == undefined

这个大家记住就完了,null == undefined // true。也就是说在 == 中 null 与 undefined 是一回事。

所以我们判断变量的值是 null 或者 undefined 就可以这样写了:if (a == null) {...}

数字和字符串的抽象相等比较

一个操作数是字符串一个是数字,则字符串会被转换为数字后再比较,即是:ToNumber(字符串) == 数字

var a = 666;
var b = "666";
console.log(a == b); // true
布尔值与其他类型的抽象相等比较

注意,这里比较容易犯错了:

var a = "66";
var b = true;
console.log(a == b); // false

虽然 "66" 是一个真值,但是这里的比较结果却不是 true,很容易掉坑里。大家记住这个规则:布尔值如果与其他类型进行抽象比较,会先用 ToNumber 将布尔值转换为数字再比较。

显然 "66" == 1 的结果当然是 false 咯。

对象与非对象的抽象相等比较

先说下规则:如果对象与非对象比较,则先执行 ToPrimitive(对象),并且 hint 参数为空;然后得到的结果再与非对象比较。

这里值得注意的是:在 ToPrimitive() 调用中如果 hint 参数为空,那么 [[DefaultValue]] 的调用行为跟 hint 是Number 时一样——先调用 valueOf() 不满足条件再调用 toString()

注意这里有个例外情况:如果对象是 Date 类型,则 [[DefaultValue]] 的调用行为跟 hint 是 String 时一样。

我们来测试一下是不是这样的:

var a = Object.create(null);
a.valueOf = function() {
  console.log("a.valueOf is invoking.");
  return 666;
};
a.toString = function() {
  console.log("a.toString is invoking.");
  return "666";
};

console.log(a == 666);
// a.valueOf is invoking.
// true

console.log(a == "456");
// a.valueOf is invoking.
// false

a.valueOf = undefined;
console.log(a == "666");
// a.toString is invoking.
// true

根据输出来看依据上面的规则来解释是 OK 的。

有一个开源项目有张图表可以方便大家去记忆 =====,点击 这里 查看。
a > b、a < b

按惯例先总结规则,情况略微复杂:

第一步:如果操作数是对象则执行 ToPrimitive(对象),并且 hint 参数为空。

第二步:

如果双方出现非字符串,则对非字符串执行 ToNumber,然后再比较

如果比较双方都是字符串,则按字母顺序进行比较

我们还是用代码来测试下:

var a = Object.create(null);
a.valueOf = function() {
  console.log("a.valueOf is invoking.");
  return "666";
};
a.toString = function() {
  console.log("a.toString is invoking.");
  return true;
};

console.log(a > "700");
// a.valueOf is invoking.
// false

a.valueOf = undefined;
console.log(a < 2);
// a.toString is invoking.
// true

这里注意下当测试 a < 2 时,toString() 返回了 true,然后会执行 ToNumber(true) 返回 1,最后 1 < 2 的结果就是 true。

a ≥ b,a ≤ b

最后这里也是一个比较容易中坑的地方。

根据规范 a ≤ b 会被处理为 a > b,然后将结果反转,即处理为 !(a > b);a ≥ b 同理按照 !(a < b) 处理。

我们来看个例子:

var a = { x: 666 };
var b = { x: 666 };

console.log(a >= b); // true
console.log(a <= b); // true

这里 a 和 b 都是字面量对象,valueOf() 的结果还是对象,所以转为执行 toString(),结果都是"[object Object]",当然 a < ba > b 的结果都是 false,然后取反结果就是 true 了。≤ 和 ≥ 的结果都是 true,是不是有点出乎意料呢

总结

上一篇写了 JS 类型转换的规则,这一篇写了隐式转换中我总结的经验和判断法则。感觉已经差不多了,剩下的就是实践中自己去理解了,后续可能还会找一些比较坑的类型转换示例代码写一篇拆解分析。

感谢大家花时间听我比比,欢迎 star 和关注我的 JS 博客:小声比比 Javascript

参考资料

ES5 规范注释

《你不知道的 Javascript(中卷)》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104662.html

相关文章

  • Front-end developmenter必看,超实用的javaScript隐式类型转换规则记忆

    摘要:下面先看看涉及到的几个函数以及他们的转换规则,这个是需要记忆的内容类型转换需要使用到的函数对于布尔值用到的是对于数值,用到的是当然还有但是对于隐式类型转换的时候,调用的是前者。 javaScript类型转换规则 javaScript的类型转换其实一直是很多前端开发人员很迷的地方,一会儿这里要转换,一会儿那里又要转换,总之就是一个大写的迷,因为它隐式类型转换的地方实在是太多了。 但其实...

    fuchenxuan 评论0 收藏0
  • 深入js隐式类型转换

    摘要:结合实际中的情况来看,有意或无意中涉及到隐式类型转换的情况还是很多的。此外当进行某些操作时,变量可以进行类型转换,我们主动进行的就是显式类型转换,另一种就是隐式类型转换了。 前言 相信刚开始了解js的时候,都会遇到 2 ==2,但 1+2 == 1+2为false的情况。这时候应该会是一脸懵逼的状态,不得不感慨js弱类型的灵活让人发指,隐式类型转换就是这么猝不及防。结合实际中的情况来看...

    tomato 评论0 收藏0
  • JavaScript 类型转换深度学习

    摘要:当一个值为字符串,另一个值为非字符串,则后者转为字符串。文章出自的个人博客 showImg(https://segmentfault.com/img/bVEWkS?w=3376&h=1312); JavaScript 是一门弱类型语言,刚接触的时候感觉方便快捷(不需要声明变量类型了耶!),接触久了会发现它带来的麻烦有的时候不在预期之内 呵呵一笑,哪有这么夸张,可能有人看过这样一段代码 ...

    microcosm1994 评论0 收藏0
  • 掌握 Javascript 类型转换:从规则开始

    摘要:首先,为了掌握好类型转换,我们要理解一个重要的抽象操作为什么说这是个抽象操作呢因为这是内部才会使用的操作,我们不会显示调用到。基本规则中的类型转换总是返回基本类型值,如字符串数字和布尔值,不会返回对象和函数。 Javascript 里的类型转换是一个你永远绕不开的话题,不管你是在面试中还是工作写代码,总会碰到这类问题和各种的坑,所以不学好这个那是不行滴。关于类型转换我也看过不少的书和各...

    mikyou 评论0 收藏0
  • 【JS进阶】你真的掌握变量和类型了吗

    摘要:本文从底层原理到实际应用详细介绍了中的变量和类型相关知识。内存空间又被分为两种,栈内存与堆内存。一个值能作为对象属性的标识符这是该数据类型仅有的目的。 导读 变量和类型是学习JavaScript最先接触到的东西,但是往往看起来最简单的东西往往还隐藏着很多你不了解、或者容易犯错的知识,比如下面几个问题: JavaScript中的变量在内存中的具体存储形式是什么? 0.1+0.2为什...

    fuyi501 评论0 收藏0

发表评论

0条评论

weapon

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<