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

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

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

什么是隐式类型转换呢?顾名思义就是有时候你感受不到这是类型转换可是实际上类型转换已经发生了。因此这个 "隐式" 取决于咱们的理解和经验,若是你看不出来那就是隐式的。git

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

一元操做符 +、-

var a = '123';
var b = +a;
console.log(b); // 123
复制代码

先来看看 +- 在一个类型值前面,这里会执行 ToNumber 类型转换。若是是 - 在前面的话,还会将结果的符号取反,如:-"123" 的结果是 -123。而且若是原类型是对象的话也是遵循 ToNumber 的转换规则,你们能够本身试试,这里就再也不举多余的例子了。post

二元操做符

接下来咱们来看一下二元操做符相关的隐式转换,好比:+-&&||==等等这些。测试

相减 a - b

var a = '123';
var b = true;
console.log(a - b); // 122
复制代码

当执行减法操做时,两个值都会先执行 ToNumber 转换,因此这个是比较简单的,当类型是对象时也是遵循一样的规则。ui

相加 a + b

console.log('123' + 4); // "1234"
console.log(123 + true); // 124
复制代码

相加的状况有点复杂,但隐式转换的规则你们能够按照我总结的来记:es5

  1. 若是 + 的操做数中有对象,则执行 ToPrimitive 而且 hint 是 Number
  2. 若是 + 中有一个操做数是字符串(或经过第一步获得字符串),则执行字符串拼接(另外一个操做数执行 ToString 转换),不然执行 ToNumber 转换后相加

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

第一步,先看操做数里面有没有对象,若是有就是执行 hint 是 Number 的 ToPrimitive 操做。你们能够回忆下上篇说的 ToPrimitive 的内容,这里要注意的是这里的 ToPrimitive 并无将操做数强制转化为 Number 类型。由于 hint 是 Number,因此先执行 valueOf() ,若是返回了字符串那转换结果就是字符串了;若是返回的不是基本类型值才会执行 toString(),若是都没有返回基本类型值就直接抛异常了。3d

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

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

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

参考资料

相关文章
相关标签/搜索