这一部分内容是承接上一篇的, 建议先阅读谈 JavaScript 中的强制类型转换 (1. 基础篇)

前两章讨论了基本数据类型和基本包装类型的关系, 以及两个在类型转换中十分重要的方法: valueOftoString 方法. 接下来的内容建立在前两章之上, 给出判断隐式类型转换结果的方法, 文章最后部分给出了多个练习以及解析, 用以检验文中讨论方法的正确性.

3 各种类型之间的强制类型转换

此处谈的强制类型转换指的是除了符号类型(symbol)之外的基本数据类型以及对象之间的类型转换, 对于符号类型(symbol)单独讨论.

3.1 ToPrimitive 将变量转换为 基本数据类型

把一个变量转换为 基本数据类型 的转换过程可以被抽象成一种称为 ToPrimitive 的操作, 主意它只是一个抽象出来的名称, 而不是一个具体的方法, 各种数据类型对它的实现才是具体的.

ToPrimitive 把一个变量转变成一个基本的类型, 会根据变量的类型不同而采取不同的操作:

  1. 如果这个变量已经是基本类型了: 那就不进行转换了, 直接返回这个变量, 就直接用这个变量的值了.

  2. 当这个变量是一个对象时: 就调用这个对象的内部的方法 [[DefaultValue]] 来来把对象转换成基本类型。

简单来说, 对于基本数据类型直接返回本身. 对于对象就执行对象本身的 [[DefaultValue]] 方法来获得结果. 那么这个 [[DefaultValue]] 方法是怎么工作的呢, 其实也并不难.

3.2 [[DefaultValue]] 操作 返回对象的基本数据类型(原始类型)的值

[[DefaultValue]] 方法利用对象内部的 valueOf 方法或 toString 方法返回操作数的基本数据类型(可以指定想得到的类型偏好)。此操作的过程可如此简单理解:

在默认的情况下:

先调用 valueOf() 方法, 如果返回值是基本类型, 则使用这个值; 否则:

调用 toString() 方法, 得到返回值.

如果这两个方法都无法得到基本数据类型的返回值,则会抛出 TypeError 异常.

另: 对于 Date 对象, 会将这两个方法的调用顺序颠倒过来. 先调用 toString ,若得不到基本类型的值, 就再调用 valueOf. 若都不能得到基本类型的值, 同样抛出 TypeError 异常.

简单总结一下 3.1 部分的内容:

在将一个值转换为基本数据类型的时候, 如果这个值本身就是一个基本数据类型, 则直接使用它自己; 如果这个值是个对象, 就调用对象的两个方法: valueOftoString , 这两个函数得到的结果就是这个对象转换成的基本类型的值.

3.2 基本数据类型之间的类型转换

前一部分讨论了对象如何强制转换为基本数据类型, 夲节主要讨论基本数据类型之间的相互转换. 主要包含三个小部分:

  1. 其他类型数据转换成 字符串
  2. 其他类型转换成 数值
  3. 其他类型转换成 布尔值

下面来一一具体讨论.

3.2.1 其他类型数据转换成 字符串

其他基本数据类型的值转换成字符串类型其实非常简单, 直接变成字符串的形式就可以了. 例如:

null -> "null", undefined -> "undefined", true -> "true", false -> "false", 3.14159 -> "3.14159"

注: 对于非常大或者非常小的数字来说, 转换成字符串会是科学记数法的形式, 例如:

3140000000000000000000 -> "3.14e+21" // 很大的数转换成字符串

0.000000314 -> "3.14e-7"  // 很小的数转换成字符串

3.2.2 其他类型数据转换成 数值

其他基本数据类型转换成数值类型也比较简单, 只有字符串需要做非常简单的判断.

具体为:

null -> 0, undefined -> NaN, true -> 1, false -> 0

对于字符串来说, 可细分为一下的情况:

  • 空字符串转换为 0
  • 若字符串中只含 数字, 加减号, 小数点符号, 则直接转换, 而且忽略前导的 0, 例如:
"3.14" -> 3.14
"-0003.14" -> -3.14 // 前导有 0
  • 如果字符串中内容为十六进制, 则转换成的数值为十进制的形式, 例如: "0xa -> 10"

  • 其他情况,则都转换结果为 NaN, 例如:

"A10" -> NaN
"1A0" -> NaN
"10A" -> NaN

3.2.3 其他类型数据转换成 布尔值

这就更简单了, 只有几个特殊的值转换后为 false , 除此之外的其他值转换后都为 true, 这几个特殊的值如下:

NaN, undefined, null, 0, +0, -0, 空字符串""

上述 3.2 部分的内容的记忆是比较简单的, 在处理具体类型转换的问题时只需要灵活运用就可以了, 但是有时候在同一个问题中的同一个变量涉及多个转换过程, 比如从 对象 转为字符串, 然后再从字符串转为 数值.

下一节将会讨论涉及到隐式类型转换的实际应用, 会包含很多例子.

4 涉及到隐式类型转换的情况

JavaScript 中很多常用的操作都会引起隐式的强制类型转换, 下面的部分举几个常见的例子.

4.1 加减乘除号引起的隐式类型转换 + - * /

常用的四则运算操作符在有些时候会引起隐式强制类型的转换

4.1.1 加号 +

在 JavaScript 中, 加号 + 可以用来做加法运算, 也可以用来拼接字符串, 那该怎么判断它执行的是哪个操作呢?

有人说只要加号连接的两个变量其中有一个是字符串时就执行的是拼接操作, 否则就执行加法操作. 这种说法是不完整的, 例如下面这几个例子就无法按照这种说法得到结果, 因为加号两边的变量都不是字符串类型:

// 例 1
console.log(true + true); // ?
console.log(1 + null); // ? // 例 2
let array = [2, 3];
let a = 1 + array; let obj = { name: 'doug', age: 4 };
let b = 1 + obj; console.log(a, b); // ?

那么到底应该怎么判断呢? 个人认为可以这样来做, 分成两种简单的情况 :

  1. 如果加号的左右两边都是除字符串之外的基本类型值, 或者是可以通过 ToPrimitive(见 3.1 部分) 抽象操作转换成这些类型的 对象, 那么后台会尝试将这两个变量都转换成数字(具体机制见3.2.2节)进行加法操作.

看下面的实验结果:

console.log( 1 + 1 );       // 2

// true -> 1; false -> 0
console.log( 1 + true ); // 2
console.log( 1 + false ); // 1
console.log( false + 1 ); // 1
console.log( false + true ); // 1 // null -> 0
console.log( 1 + null ); // 1
console.log( true + null ); // 1 // undefined -> NaN
console.log( 1 + undefined ); // NaN
console.log( true + undefined ); // NaN
console.log( null + undefined ); // NaN // 通过 ToPrimitive 操作返回 number, boolean, null, undefined 基本类型值的对象
// 重写了对象的 valueOf 和 toString 方法
let obj = {
valueOf: function(){
return true;
}, toString: function(){
return 'call obj';
}
} console.log(1 + obj); // 2
console.log(obj + obj); // 2, 这个例子更加典型

前面的内容提到过在默认情况下 ToPrimitive 会首先调用 对象 objvalueOf 方法来获取基本类型的值, 所以得到了 true , 然后输出语句就变成了 console.log(1 + true)console.log(true + true) , 之后 true 被转换成数值类型 1, 式子变成console.log(1+1). 实验结果证实了刚才的设想, 适用于 例1 中的情况.

那么 什么情况下进行字符串的拼接操作? 设想如下 :

  1. 如果加号的左右两边存在 字符串 或者可以通过 ToPrimitive (见 3.1 部分)抽象操作转换成字符串的对象, 则执行的就是拼接操作.

在 例2 中:

let array = [2, 3];
let a = 1 + array; let obj = { name: 'doug', age: 4 };
let b = 1 + obj; console.log(a, b); // ?

变量 a 等于 1 加上一个 数组 array, 数组是可以通过 ToPrimitive 转化为字符串的, 按照3.1 和 3.2 节的内容, 数组先调用了自己的 valueOf 方法, 发现返回的是 数组本身, 不是基本数据类型; 于是接着调用 toString 方法, 返回了一个各项用 "," 连接的字符串 "2,3" . 于是现在就有了 let a = 1 + "2,3", 是数值和字符串相加, 结果就是 "12,3".

对象 obj 调用 valueOf 返回它本身, 再调用 toString 方法返回字符串 "[object Object]". 然后就变成了 let b = 1 + "[object Object]" , 就变成了数值和字符串相加, 是拼接操作, 所以结果就出来了 "1[object Object]".

再看一个例子:

function fn(){ console.log('running fn'); }

console.log(fn + fn);
/*
function fn(){ console.log('running fn'); }function fn(){ console.log('running fn'); }
*/

这个例子的结果用刚才的设想是可以比较容易的得到结果的. 即: 函数 fn 可以通过 ToPrimitive 操作返回一个字符串, 然后式子就变成了 字符串+字符串, 结果就是字符串的拼接.

至此. 上面的论断可能存在不严谨的地方, 欢迎批评指正.

4.1.2 减乘除 - * / 运算符产生的强制类型转换

这三个运算符会将左右两边不是数值类型的变量强制转换成简单数值类型, 然后执行数学运算, 例如:

console.log(true - false);	// 1
console.log(true - null); // 1
console.log(true * true); // 1
console.log(2 - undefined); // NaN console.log([2] - 1); // 1
// [2] -valueOf-> [2] -toString-> "2" -> 2 console.log('3' * 2); //6 console.log('4' / '2'); // 2 let obj = {
toString: function(){ // 重写了 toString 方法, 返回一个字符串
return '4';
}
}; console.log(obj * [2]); // 8

上述几个例子中变量最终都被转换成了数值型的基本数据类型. 其中数组和对象通过 ToPrimitive (见 3.1 部分)先转换成字符串, 接着强制转换成数值类型再进行数学运算.

4.2 逻辑运算符 ||&&

我们常用将逻辑运算符用在条件判断中, 例如:

if(a || b){
// codes
}

这是很自然的操作.

然而逻辑运算符返回的并不是想象中的布尔类型的 true or false , 而是它左右两个操作数中的一个。例如:

let a = 50;
let b = 100; console.log(a || b, a && b); // 50 100, 并没有输出 true 或者 false

可以看到输出结果并不是布尔值, 而是两个操作数中的一个. 同时还发现, 对于两个相同的操作数, ||&& 操作符的输出情况并不一样. 下面来讨论一下原因.

这两个操作符会根据左边(只判断左边, 不判断右边)的操作数转换成布尔类型之后的值决定返回哪个操作数, 会先检验左边的操作数的真值, 再做出决定. 具体机制如下:

  1. 对于 || , 当左边的真值为 true 时, 则返回左边的; 否则返回右边的操作数.
  2. 对于 && , 当左边的真值为 false 时, 直接返回左边的; 否则返回右边的操作数.

可以这样来简单理解:

|| 意为 或, 只要两个钟有一个为真就可以了, 所以如果左边为真整体就为真, 直接返回左边就可以了.

&& 意为 且, 要求两边都为真, 如果左边为真, 那么就取决于右边的真假情况了, 所以直接返回右边.

回到开头的例子:

if(a || b){
// codes
}

根据上面的讨论, 可以知道 a || b 并不返回布尔值, 然而 if 却是根据布尔值决定是否执行内部操作的, 那么为什么可以正常执行呢? 原因是 if 语句还要对 a || b 的返回值进行一次隐式强制类型转换, 转换成布尔值, 然后再进行下一步的决定.

类似进行隐式强制类型转换判断的情况还有:

  • for循环
  • while 和 do while 循环
  • 三元运算符 xx? a:b

4.3 非严格相等符号 ==

4.3.1 与严格相等符号 === 的异同

非严格相等符号(==)是和严格相等符号(===)相关的概念. 它们的区别是: == 允许进行强制类型转换, 再比较转换后的左右操作数; 而 === 不进行强制类型转换, 直接比较左右两个操作数.

当左右两个操作数的类型相同的时候, 这两种比较符号的效果相同, 运用的原理也相同.

在比较对象的时候, 这两个比较符号的原理也相同: 比较左右两个变量指向的是不是同一个对象.

4.3.2 对象(包括数组和函数)和基本数据类型之间的 == 比较

在对象与基本数据类型的比较的时候, 对象会通过 ToPrimitive (见 3.1 部分) 操作返回基本数据类型的值, 然后再进行比较.

4.3.3 布尔值和其他类型的 == 比较

在布尔值与其他类型比较时, 会先将布尔类型的值转换成数值, 即: true->1, false->0.

4.3.4 字符串和数值的 == 比较

将字符串转换成数值类型, 然后进行比较

4.3.4 nullundefined 的比较

nullundefined 在用 == 比较时返回的是 true, 而且除了它们自身之外, 只有这二者相互比较时才返回 true.

换句话说, 除了其自身之外, null 只有和 undefined== 比较时才为 true, 与其他任何值比较时都是 false;

同样的, 除了其自身之外, undefined 只有和 null== 比较时才为 true, 与其他任何值比较时都是 false.

即: 对于 nullundefined 来说, 只有这三种情况为真:

console.log( null == undefined ); 	// true
console.log( undefined == undefined ); // true
console.log( null == null ); // true

其他特殊情况

  1. NaN 不与任何值等, 即使和自身相比也不相等
console.log(NaN == NaN); 	// false
  1. +0 -0 0 三者相等
console.log(0 == +0);	// true
console.log(0 == -0); // true
console.log(-0 == +0); // true

总结一下: 对象(包括数组和函数)和其他类型比较时, 要进行类型转换的是对象; 布尔值和其他数据比较时, 要进行类型转换的是布尔值, 转换成数值类型1或0; 在数值和字符串比较时, 要转换类型的是字符串, 转换成数值类型.

5 应用, 举例分析

下面举一些隐式强制类型转换的例子, 用之前讨论的内容判断, 并给出解析:

console.log( "4" == 4 );		// true
// 字符串和数值比较, 字符串转换为数值, 即 "4" -> 4 console.log( "4a" == 4 ); // false
/* 原理同上, 字符串和数值比较, 字符串转换为数值,
但是字符串里包含 "a", 所以转换后是 NaN, 即 "4a" -> NaN;
式子变成 `NaN == 4", 因为 NaN 与任何值都不等, 故为 false */ console.log( "5" == 4 ); // false
// 字符串和数值比较, 字符串转换为数值, 即 "5" -> 4, 式子变成 `5 == 4`, false console.log( "100" == true ); // false
/* 存在布尔值, 首先布尔值转换为数值, 即: true -> 1,
式子变成: `"100" == 1`, 此时为字符串和数值比较, 字符串转换为数值,
式子变成 `100 == 1`, false
*/ console.log( null == undefined ); // true
console.log( undefined == undefined ); // true
console.log( null == null ); // true
console.log( null == [] ); // false
console.log( null == "" ); // false
console.log( null == {} ); // false
console.log( null == 0 ); // false
console.log( null == false); // false
console.log( undefined == [] ); // false
console.log( undefined == "" ); // false
console.log( undefined == {} ); // false
console.log( undefined == 0 ); // false
console.log( undefined == false ); // false
console.log(null == Object(null) ); // false
console.log(undefined == Object(undefined) ); // false
// 以上的答案比较容易得出, 因为 null 和 undefined 除了自己之外只认识彼此, 文章 4.3.4 部分 console.log( "0" == false ); // true
/* 包含布尔值, 首先布尔值转换为数值, 即: false -> 0,然后式子变成 ` "0" == 0 `;
此时变成了字符串和数值比较, 字符串转换为数值, 即: "0" -> 0,
然后式子变成 ` 0 == 0 `, true */ console.log( 0 == false); // true
// false 转换为 0, true console.log( false == "" ); // true
/* false -> 0, 式子变成 ` 0 == "" `, 数字和字符串比较;
字符串转换为数值, 即: "" -> 0, 式子变成 ` 0 == 0 `, true */ console.log( false == [] ); // true
// 包含对象和布尔值, 布尔值优先 转换, 随后对象通过 ToPrimitive 操作转换为基本数据类型后比较:
// false -> 0, [] -> "" -> 0; ` 0 == 0 `, true console.log( false == {} ); // false
// 包含对象和布尔值, 布尔值优先 转换, 随后对象通过 ToPrimitive 操作转换为基本数据类型后比较:
// false -> 0, {} -> "Object Object" -> NaN; ` 0 == NaN ` false console.log( 0 == ""); // true
// "" -> 0; ` 0 == 0 ` true console.log( "" == [] ); // true
// 字符串和对象比较, 对象通过 ToPrimitive 操作转换为基本数据类型后比较:
// [] -toString- -> ""; 式子变成 ` "" == "" `, true console.log( 0 == []); // true
// 数值和对象比较, 对象通过 ToPrimitive 操作转换为基本数据类型后比较:
// [] -toString- -> ""; 式子变成 ` 0 == "" `, 此时是数值类型和字符串比较, 字符串转换为数值
// "" -> 0 ; 式子变成了 ` 0 == 0 `,true console.log( 0 == {}); // false
// 数值和对象比较, 对象通过 ToPrimitive 操作转换为基本数据类型后比较:
// {} -> "Object Object" -> NaN; ` 0 == NaN ` false

JavaScript 中设计的强制类型转换的内容不止文中提到的这些, 仍存在没有讨论到的内容会在将来讨论.同时文中可能存在错误, 请不吝指正, 谢谢.

参考资料:

  • 《JavaScript 高级程序设计》
  • 《你不知道的 JavaScript》
  • MDN

谈 JavaScript 中的强制类型转换 (2. 应用篇)的更多相关文章

  1. 谈 JavaScript 中的强制类型转换 (1. 基础篇)

    1. 从基本包装类型讲起 讨论基本包装类型的前提是了解基本数据类型(也可以称为原始类型, 简单数据类型等).然后通过基本数据类型调用方法的行为, 引出基本包装类型的概念和作用. 1.1 基本数据类型 ...

  2. 详细理解javascript中的强制类型转换

    将值从一种类型转换为另一种类型通常称为类型转换,这是显式的情况:隐式的情况称为强制类型转换,JavaScript 中的强制类型转换总是返回标量基本类型值,如字符串.数字和布尔值. 如何理解: 类型转换 ...

  3. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  4. C#中的强制类型转换与as转换的区别

    C#中的强制类型转换 例如有ClassA与ClassB两个类创建两个类的对象进行转换 1 2 ClassA a = new ClassA();  ClassB b = new ClassB(); 如果 ...

  5. JS在if中的强制类型转换

    JS在if中的强制类型转换 众所周知,JS在很多情况下会进行强制类型转换,其中,最常见两种是: 1.使用非严格相等进行比较,对==左边的值进行类型转换 2.在if判断时,括号内的值进行类型转换,转化为 ...

  6. 浅谈JavaScript中的null和undefined

    浅谈JavaScript中的null和undefined null null是JavaScript中的关键字,表示一个特殊值,常用来描述"空值". 对null进行typeof类型运 ...

  7. 浅谈JavaScript中的正则表达式(适用初学者观看)

    浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...

  8. JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符

    文章目录 1.强制类型转换Number 1.1 代码 1.2 测试结果 2.进制表示 2.1 代码 2.2 测试结果 3.强制类型转换为Boolea 3.1 代码 3.2 测试结果 4.运算符 4.1 ...

  9. JavaScript中的数据类型转换

    本文中提到的“原始值”指的是undefined,null,Boolean,string和number. 本文中的对象是native对象,宿主对象(浏览器定义的对象)按照各自的算法转换. JavaScr ...

随机推荐

  1. 解题:NOI 2009 诗人小G

    题面 今天考试考了,于是开始糊学决策单调性DP 这是一个完全不会优化DP的人 决策单调性DP的一种优化方法是用单调队列优化 存下{左端点l,右端点r,最优决策点p}的三元组,按照单调队列的通常操作来说 ...

  2. 解题:THUWC 2017 在美妙的数学王国中畅游

    题面 _“数字和数学规律主宰着这个世界.”_ 在 @i207M 帮助下折腾了半天终于搞懂了导数和泰勒展开,引用某学长在考场上的感受:感觉整个人都泰勒展开了 显然是个奇奇怪怪的东西套上LCT,发现直接维 ...

  3. chessboard

    题意:n*n的矩阵,m次赋值一个子矩阵为c,最后输出答案. n<=1e3 m<=1e5 解:倒序处理. 拆行处理. 每行内并查集维护未被赋值的地方. 这样每个地方最多被赋值一次,每次修改要 ...

  4. bug找到吐的赶脚

    bug找到吐的赶脚,真**刺激 一.单元测试 设计思路 首先是需要写一个无括号四则运算函数 下面的运算先是运算括号内的数 然后将null后置 全部代码测试,覆盖率92.4% 二.结构优化 uml图 流 ...

  5. HDU 6156 数位dp

    Palindrome Function Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 256000/256000 K (Java/Ot ...

  6. CGI浏览器与服务器的交互

    一直在做项目,跟着写前端后端,却没有思考一个问题:前端和后端为什么能够进行通信?为什么能够将HTML页面的内容传输给后台,然后又将结果反馈给前端? 寒假偶尔看到了这个问题,也解决了我的疑惑,这是基于C ...

  7. Hadoop生态圈-flume日志收集工具完全分布式部署

    Hadoop生态圈-flume日志收集工具完全分布式部署 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   目前为止,Hadoop的一个主流应用就是对于大规模web日志的分析和处理 ...

  8. Java大话设计模式

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  9. Java 调用 groovy 脚本文件,groovy 访问 MongoDB

    groovy 访问 MongoDB 示例: shell.groovy package db import com.gmongo.GMongoClient import com.mongodb.Basi ...

  10. BAT及各大互联网公司2014前端笔试面试题--Html,Css篇(昨天有个群友表示写的简单了点,然后我无情的把他的抄了一遍)

    某个群友 http://www.cnblogs.com/coco1s/   很多面试题是我自己面试BAT亲身经历碰到的.整理分享出来希望更多的前端er共同进步吧,不仅适用于求职者,对于巩固复习前端基础 ...