隐式强制转换

在其可控的情况下,减少冗余,让代码更简洁,很多地方都进行了隐式转换,比如常见的三目表达式、if()、for()、while、逻辑运算符 || &&,适当通过语言机制,抽象和隐藏一些细枝末节,有助于提高代码可读性,以三目表示式为例

a? trueAction : falseAction

!!a ? trueAction : falseAction

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

通过重载,+ 运算符即能用于数字加法,也能用于字符串拼接。JavaScript 怎样来判断我们

var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42

通常我们的理解是,两个操作数中至少其中之一含有字符串,结果就为字符串,这种解释对了一半,看下面例子

var a = [1,2];
var b = [3,4];
a + b; // "1,23,4" 这里如何解释呢

我们得看一下ES5规范:

根据ES5 规范11.6.1 节,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用ToPrimitive 抽象操作(规范9.1 节),该抽象操作再调用[[DefaultValue]](规范8.12.8
节),以数字作为上下文

简单来说,如果+其中的一个操作数是字符串(或者通过此规范能得到字符串),则进行字符串拼接,否则执行数字加法

1,23,4的过程解析:

数组的valueOf() 操作无法得到简单基本类型值,于是它转而调用toString()。因此上例中的两个数组变成了"1,2" 和"3,4"。+ 将它们拼接后返回"1,23,4"。

这里有一个坑经常被提及

[]+ {} // "[object Object]"
{} + [] // 0

[] + {}

[].toSting()的结果是"" + {} ,这是相当于隐式将{}转换为字符串,由于valueof返回自身,所以转向了toString方法,而一般对象没没有设置toString方法的话,默认是调用Object.prototype.toString,所以最终结果是 "[obejct Obejct]"

{} + []

{} 被认为是一个空白代码块,解析的是 + [],此时 [].valueof返回是数组本身,而不是基本值,转而调用toString,返回了"",然后使用Number("")转换成数字,最后结果是0

这里有一个需要注意的地方 a+""和String(a)区别在于,a+ ""会根据ToPrimitive抽象操作规则,a+ ""会对a调用valueOf方法,然后通过ToString抽象操作将返回值转换为字符串,而String(a)则是直接调用ToString

var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // "42"
String( a ); // "4"

目前ES6对ToPrimitive抽象操作在Symbol上定义了一个名为Symbol.toPrimitive变量来供我们改写ToPrimitive行为,看下面例子

// hint表示js引擎获取到的操作偏好
let obj = {
[Symbol.toPrimitive](hint) {
if(hint === 'number'){
console.log('Number场景');
return 123;
}
if(hint === 'string'){
console.log('String场景');
return 'str';
}
if(hint === 'default'){
console.log('Default 场景');
return 'default';
}
}
}
console.log(2*obj); // Number场景 246
console.log(3+obj); // String场景 3default
console.log(obj + ""); // Default场景 default
console.log(String(obj)); //String场景 str

目前内置对象对Symbol.ToPrimitive使用,比如Date对象

var a = [1, 2, 3]

a.valueOf = function () {

    return 'hello'

}

a.valueOf() // "hello"

a.toString() // "1,2,3"

'' + a // "hello"

var date = new Date()

date.valueOf() // 1536416960724

date.toString() // "Sat Sep 08 2018 22:29:20 GMT+0800 (中国标准时间)"

'' + date // "Sat Sep 08 2018 22:29:20 GMT+0800 (中国标准时间)"

这里date.valueOf能够获取基本值,但是行为被Symbol.ToPrimitive定义为优先返回toString

2.布尔值到数字的隐式强制转换

再将某些复杂的布尔逻辑转换为数字加法的时候,隐式强制转换能派上大用处,当然情况比较少见,特殊情况特殊处理

例如我们需要保证参数中只能有一个为true,

function onlyOne(a,b,c) {
return !!((a && !b && !c) ||
(!a && b && !c) || (!a && !b && c));
}
var a = true;
var b = false;
onlyOne( a, b, b ); // true
onlyOne( b, a, b ); // true
onlyOne( a, b, a ); // false

参数少我们尚可对所有情况进行枚举,但是3个参数就应该开始使得代码的可读性下降,多个参数枚举使得代码会更加难读,还更容易出错

function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
// 跳过假值,和处理0一样,但是避免了NaN
if (arguments[i]) {
sum += arguments[i];
}
}
return sum == 1;
}
var a = true;
var b = false;
onlyOne( b, a ); // true
onlyOne( b, a, b, b, b ); // true

“异常”的逻辑运算符 && ||,我觉得应该称之为选择器运算符,因为它们是返回操作数之中的其一

var a = 42;
var b = "abc";
var c = null;
a || b; // 42
a && b; // "abc"
c || b; // "abc"
c && b; // null

3.符号的强制转换

ES6允许符号显式转换字符串,而隐式转换会产生错误

var s1 = Symbol( "cool" );
String( s1 ); // "Symbol(cool)"
var s2 = Symbol( "not cool" );
s2 + ""; // TypeError

4.宽松相等和严格相等

4.1== 、=== 的区别,常见的误区是“== 检查值是否相等,=== 检查值和类型是否相等”。听起来蛮有道理,然而

还不够准确。很多JavaScript 的书籍和博客也是这样来解释的,但是很遗憾他们都错了。

正确的解释是:“== 允许在相等比较中进行强制类型转换,而=== 不允许。”

4.2相等比较的性能

如果两个值的类型不同,我们就需要考虑有没有强制类型转换的必要,有就用,没有就

用=,不用在乎性能。

4.3抽象相等,==运算符的行为,这是隐式强制转换最让人诟病的,开发人员认为它们过于晦涩,很难掌握和运用。

规则

两个值类型相同,仅比较它们相等,需要注意非常规的NaN!=NaN以及+0 == -0,对于对象,只判断其引用是否相等,不发生强制类型转换

两个不同类型的值会发生隐式强制转换

4.3.1 字符串和数字之间的相等比较,将字符串转换为数字再进行比较,具体规范如下

(1) 如果Type(x) 是数字,Type(y) 是字符串,则返回x == ToNumber(y) 的结果。
(2) 如果Type(x) 是字符串,Type(y) 是数字,则返回ToNumber(x) == y 的结果。

例子

var a = 42;
var b = "42";
a === b; // false
a == b; // true

4.3.2 其他类型和布尔类型之间的相等比较,将布尔值转换为数字,然后继续比较,这种比较会导致宽松相等会进行多次的隐式类型转换,不要使用 宽松相等来和布尔值比较,它的隐式转换过于晦涩。

(1) 如果Type(x) 是布尔类型,则返回ToNumber(x) == y 的结果;
(2) 如果Type(y) 是布尔类型,则返回x == ToNumber(y) 的结果

例子

var x = true;
var y = "42";
x == y; // false

解析

Type(x) 是布尔值,所以ToNumber(x) 将true 强制类型转换为1,变成1 == "42",二者的类型仍然不同,"42" 根据规则被强制类型转换为42,最后变成1 == 42,结果为false。

5 null和undefined之间的相等比较,在==情况下它们相等

规范

(1) 如果x 为null,y 为undefined,则结果为true。
(2) 如果x 为undefined,y 为null,则结果为true。

6 对象和非对象之间的相等比较,对对象进行ToPrimitive操作然后进行操作

规范

(1) 如果Type(x) 是字符串或数字,Type(y) 是对象,则返回x == ToPrimitive(y) 的结果;
(2) 如果Type(x) 是对象,Type(y) 是字符串或数字,则返回ToPromitive(x) == y 的结果。

例子

var a = 42;
var b = [ 42 ];
a == b; // true

解析

[ 42 ] 首先调用ToPromitive 抽象操作(参见4.2 节),返回"42",变成"42" == 42,然后又变成42 == 42,最后二者相等。

== 中的假值比较

"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false

应对假值比较的特殊情况

"0" == false; // true -- 晕!
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
// 前面四种我们可以通过不使用== boolean来避免
// 这个比较少用
"" == []; // true -- 晕! // == "" == 0 我们得特别注意,在有 [] 、0 、"" 我们尽量少使用
"" == 0; // true -- 晕!
0 == []; // true -- 晕!
小结,对于宽松相等,对象会被ToPrimitive抽象操作转换为基本值,然后我们只需要记住数字、布尔、字符串比较的隐式转换规则即可
字符串和数字比较,字符串会被转换数字
布尔值和其他类型比较,会被转换成数字

深入js系列-类型(隐式强制转换)的更多相关文章

  1. 深入js系列-类型(显式强制转换)

    什么是显式 这里的显式和隐式是以普遍的标准来进行讨论的,你能看出来是怎么回事,那么它对你是"显式",相反你不知道的话,对你就是"隐式" 抽象操作 字符串.数字. ...

  2. 你不知道的JavaScript--Item3 隐式强制转换

    JavaScript的数据类型分为六种,分别为null,undefined,boolean,string,number,object. object是引用类型,其它的五种是基本类型或者是原始类型.我们 ...

  3. 【C++自我精讲】基础系列五 隐式转换和显示转换

    [C++自我精讲]基础系列五 隐式转换和显示转换 0 前言 1)C++的类型转换分为两种,一种为隐式转换,另一种为显式转换. 2)C++中应该尽量不要使用转换,尽量使用显式转换来代替隐式转换. 1 隐 ...

  4. js学习日记-隐式转换相关的坑及知识

    隐式转换比较是js中绕不过去的坎,就算有几年经验的工程师也很有可能对这块知识不够熟悉.就算你知道使用===比较从而避免踩坑,但是团队其它成员不一定知道有这样或那样的坑,有后端语言经验的人常常会形成一个 ...

  5. Java数据类型的转换:隐式(自动)转换与强制转换

    原文链接:http://java.chinaitlab.com/base/725590.html 一些初学JAVA的朋友可能会遇到JAVA的数据类型之间转换的苦恼,例如,整数和float,double ...

  6. 1067: spark.components:NavigatorContent 类型值的隐式强制指令的目标是非相关类型 String

    1.错误描写叙述 此行的多个标记: -workId -1067: spark.components:NavigatorContent 类型值的隐式强制指令的目标是非相关类型 String. 2.错误原 ...

  7. 1118: 属于 static 类型 Object 的值的隐式强制指令的目标可能是非相关类型 Number。

    1.错误描述 此行的多个标记: -1118: 属于 static 类型 Object 的值的隐式强制指令的目标可能是非相关类型 Number. -left 2.错误原因 /** * 刷新按钮函数 */ ...

  8. 深入探究js中的隐式变量声明

    前两天遇到的问题,经过很多网友的深刻讨论,终于有一个相对可以解释的通的逻辑了,然后我仔细研究了一下相关的点,顺带研究了一下js中的隐式变量. 以下文章中提到的隐式变量都是指没有用var,let,con ...

  9. Js 中那些 隐式转换

    曾经看到过这样一个代码:  (!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]] = sb , 你敢相信, ...

随机推荐

  1. SpringBoot整合junit

    SpringBoot整合junit 主要分为4步 添加依赖 创建测试类 在测试类上添加注解 在测试类注入测试对象 1:导入依赖包 <dependency> <groupId>o ...

  2. 【CTS2019】氪金手游(动态规划)

    [CTS2019]氪金手游(动态规划) 题面 LOJ 洛谷 题解 首先不难发现整个图构成的结构是一棵树,如果这个东西是一个外向树的话,那么我们在意的只有这棵子树内的顺序关系,子树外的关系与这棵子树之间 ...

  3. 【CTS2019】随机立方体(容斥)

    [CTS2019]随机立方体(容斥) 题面 LOJ 洛谷 题解 做这道题目的时候不难想到容斥的方面. 那么我们考虑怎么计算至少有\(k\)个极大值的方案数. 我们首先可以把\(k\)个极大值的位置给确 ...

  4. Linux(01):linux的起源、应用场景和学习目标

  5. Redis(二)特性和学习路线

    Redis是高效的内存数据库或者说缓存.对多种数据结构都支持,每种数据结构都有相应的应用场景. 特性 Redis支持非常多的特性,从用途.性能.高可用.数据安全方面都提供了相应的支持和保障. Redi ...

  6. pxelinux.0:winboot:网络引导(启动)wim格式的windows PE系统:配置文件写法

    关键:加载wimboot引导模块,并传入参数 todo:通过标准kenerl的append传入启动参数..........todo.todo default menu.c32 label wimboo ...

  7. python爬虫—爬取英文名以及正则表达式的介绍

    python爬虫—爬取英文名以及正则表达式的介绍 爬取英文名: 一.  爬虫模块详细设计 (1)整体思路 对于本次爬取英文名数据的爬虫实现,我的思路是先将A-Z所有英文名的连接爬取出来,保存在一个cs ...

  8. mvc5 源码解析2-2 mvchandler的执行

    我们从application获取的时候查看stepmanager的实现类 IHttpHandler applicationInstance = HttpApplicationFactory.GetAp ...

  9. SpringIOC源码解析(上)

    注意,看完这篇文章需要很长很长很长时间... 准备工作 本文会分析Spring的IOC模块的整体流程,分析过程需要使用一个简单的demo工程来启动Spring,demo工程我以备好,需要的童鞋自行在下 ...

  10. bash信号捕捉

    我们ping一个主机,然后按下ctrl+c那么就会终止这个ping动作,如下图: 可是如果使用一个循环来逐个ping不同主机,你再按下ctrl+c就会发现停不下来,直到循环完成,如下图: #!/bin ...