33 个 JavaScript 核心概念系列(三): 显式 (名义) 与 隐式 (鸭子)类型转换
原文地址:落明的博客
一、 前言
说实话,JavaScript 的类型转换是个相当头疼的问题,无论是对于初学者还是有经验的老司机。它的难处并不在于概念多难理解,而是情况多且杂,看似相同的情况结果却又出人意料,很少有人能保证时刻都能做出正确的判断。
因此,这篇文章希望能讲的足够细致和明确,让大家能够在日常使用中,能够尽快的搞清楚类型转换的顺序和结果。
长文预警,建议先 mark, 分多次查看。
二、类型转换
1. 什么叫类型转换?
我们知道,JavaScript 中存在七种数据类型,在必要的时候,我们会对不同类型的值进行相互间的转换。比如说,在进行条件判断时,我们需要将其他类型的值转为布尔类型值,在使用 console.log()
打印内容时,需要将其转为字符串输出。
2. JavaScript 中的类型转换方式有哪些?
在 JavaScript 中,分为显式类型转换和隐式类型转换。
其中,显式类型转换是我们为了功能需要,人为的将一种类型的值转换为另一中类型,转换的时机和结果都是我们预期的;而隐式类型转换则是 JavaScript 在代码运行时,未经我们允许而进行的强制类型转换。
三、显式类型转换
1. 其他类型转换为字符串( ToString )
值类型 | 例子 | 转换后 | 调用法则 |
---|---|---|---|
number | 34 | '34' |
String(34)
|
boolean | true | 'true' |
String(true)
|
boolean | false | 'false' |
String(false)
|
undefiend | undefined | 'undefined' |
String(undefined)
|
null | null | 'null' |
String(null)
|
object | { a: 'fa' } | "[object Object]" |
String({a: 'fa'})
|
object | new String(45) | '45' |
String(new String(45))
|
object | [1, 2] | '1,2' |
String([1,2])
|
object | function() {var d;} | "function() { var d; }" |
String(function() {var d;})
|
其他类型的值转换为字符串,是通过调用原生函数String()
实现,但不同类型值的实现却有明显的差异。
对于基本类型的值,直接将其转化为值的字符串形式。而对于对象类型来说,便有些复杂了。
首先,每个对象内部都有一个 [[Class]]
属性,我们通过Object.prototype.toString()
方法可以得到这个属性的字符串值。
对于对象(如{ a: 'ff'; }
)而言,除非自己定义 toString()
方法,否则,调用 String()
方法将返回和调用 Object.prototype.toString()
相同的值。(如 : "[object Object]"
)。
const obj_1 = {b: 'lalala'
};
const obj_2 = {toString() {return "fasfa";}
};
String(obj_1); // '[object Object]'
String(obj_2); // 'fasfa'
复制代码
其次, JavaScript 中,除了普通对象,还有以下几种:
封装对象
对于基本类型值
string
、number
、boolean
是没有.length
及toString()
方法的,因此,JavaScript 提供了内建函数String()
、Number()
、Boolean()
,通过new
调用后会将基本类型值封装为一个对象。如果想要取到封装对象中的基本类型值,可以使用
valueOf()
方法。// string 类型 const a = 'i am string'; typeof a; // 'string' // string 封装对象 const b = new String('i am sringObject'); typeof b; // 'object' // 拆封 b.valueOf(); // i am sringObject 复制代码
那对于封装对象,
String()
会返回什么值呢?事实上,封装对象对于
toString()
方法进行了封装,因此,对封装对象调用String()
方法,将会返回封装对象调用toString()
方法返回的值。const numObj = new Number(false); // Number {0} numObj.toString(); // '0' String(numObj); // '0' 复制代码
函数
对于函数来说,它也包装了自己的
toString()
方法,因此,调用String()
方法时将返回函数字符串化后的值。function bar() {console.log('bar'); } String(bar); // "function bar() {↵ console.log('bar');↵}" bar.toString(); // "function bar() {↵ console.log('bar');↵}" Object.prototype.toString.call(bar); // "[object Function]" 复制代码
从上例可以看到,
String()
与toString()
方法调用的是函数自己封装的toSring()
,如果调用对象的toString()
方法,则函数与普通对象一样,返回的是函数对象内部的[[Class]]
属性。数组
数组同函数一样,同样包装了自己的
toString()
方法。此方法会将数组中的每一项用逗号连接成一个字符串。const arr = [1,4,6]; String(arr); // "1,4,6" arr.toString(); // "1,4,6" Object.prototype.toString.call(arr); // "[object Array]" 复制代码
2. 其他类型值转为数字( ToNumber )
同样,先感受一下什么叫绝望?~~
值类型 | 例子 | 转换后 | 调用法则 |
---|---|---|---|
string | '34' | 34 |
Number('34')
|
string | '' | 0 |
Number('')
|
string | '34fad' | NaN |
Number('34fad')
|
string | '34fad'、'34.24'、'34' | 34 |
parseInt('34fad')
|
string | '34fad'、'34' | 34 |
parseFloat(值)
|
string | '34.34' | 34.34 |
parseFloat(值)
|
boolean | true | 1 |
Number(true)
|
boolean | false | 0 |
Number(false)
|
undefiend | undefined | NaN |
Number(undefined)
|
null | null | 0 |
Number(null)
|
object | { a: 'fa' } | NaN |
Number({a: 'fa'})
|
object | new String('fff') | NaN |
Number(new String('fff'))
|
object | [] | 0 |
Number([])
|
object | [1, 2] | NaN |
Number([1,2])
|
object | function() {var d;} | NaN |
Number(function() {var d;})
|
看完一脸懵逼有没有?!哈哈,不用害怕,乍看上去,大概会觉得异常混乱,其实稍加整理,不外乎以下几种情况:
转换后值为 NaN
数字与字符串不同,并不是任何类型值都能转为数字,因此,就会有 NaN,意思就是
not a number
。诸如包含非数字的字符串、undefined、非空数组,部分对象,都是我们知道无法转化为一个数字的。
boolean 类型值
对于
true
和false
,true
转换为 1,false
转为 0。带有数字的字符串
从上面我们可以看到,对于带有数字的字符串,有三种方法进行转换,但规则不同。
Number()
方法会对字符串整体进行转换, 它会先判断这个字符串是否是个正确的数字字符串,如果不是,则会返回NaN
。parseInt()
方法则会对字符串从左往右依次解析,直到遇到第一个非数字字符(包括小数点),如果最左边的字符是非数字字符,则返回NaN
。parseFloat()
方法解析顺序同parseInt()
相同,不同的是它遇到第一个小数点时会正常往右继续解析,直至遇到非数字字符停止。
其实严格来讲,只有
Number()
方法是进行转换操作,而后两者属于将字符串解析 为数字,但为了讲解方便,我将它们放在一起讲述。对象
对于对象而言,会先将对象转为基本类型值( ToPrimitive ),再对基本类型值调用
Number()
方法。那如何将对象转为基本类型值?首先会调用对象的
valueOf()
方法,如果没有此方法或者此方法返回值不是基本类型值,则会调用toString()
方法,如果toString()
方法不存在或者返回值也不是基本类型值,会产生TypeError
错误。// 普通对象 const nomalObj = {a: '56' }; nomalObj .valueOf(); // { a: '56'} nomalObj.toString(); // "[object Object]" // Number(nomalObj) 相当于Number("[object Object]") Number("[object Object]"); // NaN Number(nomalObj); // NaN// valueOf() 返回基本类型值的对象 const obj_1 = {a: '56',valueOf: function() {return '23';} }; obj_1.valueOf(); // '23' // Number(obj_1) 相当于 Number('23'); Number('23'); // 23 Number(obj_1); // 23// valueOf() 返回非基本类型值,toString() 返回基本类型值的对象 const obj_2 = {a: '56',valueOf: function() {return {b: 34}},toString: function() {return false;} }; obj_2.valueOf(); // {b: 34} obj_2.toString(); // false // Number(obj_2) 相当于 Number(false) Number(obj_2); // 0 Number(false); // 0 复制代码
上面的规则,适用于我们所说的所有对象,比如数组,封装对象和函数。
3. 其他类型转换为 boolean 值( ToBoolean )
我们可以通过 Boolean()
方法 或!!
运算符来显式的将一个值转换为布尔值。
相对来说,判断一个值是 true
还是 false
则比较容易,我们只需要记住以下几种值会转换为 false
,而其他值,均为 true
。
- undefined
- null
- false
- +0、-0 和 NaN
- ""
当我们看到 []、{}
甚至是 "''"
时,也一定要记住,它们是真值。
Boolean(false); // fasle
Boolean([]); //true
Boolean({}); //true
Boolean(''); // false
Boolean('""'); // true
Boolean('false'); // true
复制代码
四、隐式强制类型转换
除了进行强制类型转换,JavaScript 会在运行时根据需要,自动进行类型的转换,尽管这个特点饱受争议,但不得不承认,某些情况下我们仍旧更喜欢使用某些隐式转换规则。
一旦某些隐式的规则被接受并广泛使用,从某种意义上来讲,这些规则便同显式转换一样。
1. 奇怪的 +
号
先看一一个最常见的例子:
const a = 5;
const b = '6';
console.log(a+a); // 10
console.log(a+b); // '56'
console.log(b+b); // '66'
复制代码
之所以会产生上例中的状况,原因就在于在JavaScript 中,+
运算符既可以作用于number
类型值,也可以作用于 string
类型值。前者进行数字相加,后者则进行字符串的拼接。
这就是为什么5 + 5 = 10
而 '6' + '6' = '66'
。而当 +
号两边既有数字也有字符串时,则会隐式的将数字转换为字符串,然后进行字符串的拼接。
那两边没有字符串的情况呢?比如:
const a = [1,4];
const b = [2,3];
const c = 4;
console.log(a+c); // '1,44'
console.log(a+b); // '1,42,3'
复制代码
为什么会这样?原来只要+
的其中一个操作数可以通过某种方式(toPrimitive
)转换为字符串,就会进行字符串的拼接。
我们知道,数组[1,4]
可以通过 toString()
方法返回字符串 '1,4'
,因此,[1,4] + 4
就相当于 '1,4' + 4
。
因为这个特性,我们在想将一个数字 a 转换为字符串时,便可以直接使用 a + ''
的形式即可。相对于显式使用String(a)
,隐式转换则更加简洁。
从数组的例子我们可以看到,除了数字,其他类型的值也可以通过 + ' '
的形式转化为字符串。
const a = {b: '2'}
console.log( a+ ''); // "[object Object]"
复制代码
但有一点需要注意,对于对象而言,使用 String()
方法是直接取这个对象 toString()
方法的返回值,而 + ' '
,则会对这个对象调用 valueOf
反法,然后对 valueOf
的返回值调用 toString()
,将其转换为字符串。
const a = {toString: function() { return 45 },valueOf: function() { return 4}};
String(a); // '45'
a + ' '; // // '4'
复制代码
好在除非我们特意去改变一个对象的 valueOf
及 'toString()' 方法,通过上述两个方式的转换后的结果都是一致的。
2. 有用的 -
号
与 +
号不同的是,-
号只能用于数字的相减,对于它两边的操作数,都会经过隐式类型转换转为数字。
const a = '34';
const b = '4';
console.log(a - b); // 30
const c = 'dd';
console.log(a - c); // NaN
const d = [4];
console.log(a - d); // 30
复制代码
根据上例,我们可看到,如果 -
号两边是字符串,则会将他们强制转换为数字,如果 -
两边不是字符串,则会先将其转为字符串,再将这个字符串转为数字。
3. 隐式转换为布尔值
将其他类型值隐式转换为布尔值是我们最常用的一种转换。因为程序的编写实质上就是不停的进行判断。
在以下场景中,都是进行判断,而只要传入的值不是布尔值,都会通过隐式类型转换转为布尔值。
if (..) {}
语句中的条件判断表达式。for ( .. ; .. ; ..)
语句中的条件判断表达式。while (..)
和do ... while ( ..)
中的条件判断表达式。? :
中的条件判断表达式。- 逻辑或
||
或逻辑与&&
左边的操作数。
在这些情况下,都将会进行其他类型值到布尔类型值的隐式转换,规则同显式调用 Boolean()
。
五、最后
上面就是不同数据类型直接显式或隐式的转换规则,我们不需要将每一种情况都牢记在心,但有必要对他们进行充分的了解,这可以保证我们在实际写代码时避免不少奇怪又难以排查的 bug 。
33 个 JavaScript 核心概念系列(三): 显式 (名义) 与 隐式 (鸭子)类型转换相关推荐
- 33 个 JavaScript 核心概念系列(四): == 与 ===
原文地址:落明的博客,转载请注明出处! 一.前言 作为一个程序员,我想大家在第一次看到 a = b 的语句时一定是懵逼的.后来才知道 = 在编程语言中是用来赋值的,而不是用来判断两个值是否相等. 那该 ...
- JavaScript高级day02-AM【函数的prototype、显式原型与隐式原型、原型链】
笔记.视频.源码:JavaScript(基础.高级)笔记汇总表[尚硅谷JavaScript全套教程完整版] 目 录 P15 15.尚硅谷_JS高级_函数的prototype 15:04 1. 函数 ...
- 三种等待方式:强制等待、显式等待、隐式等待
我们在使用selenium的时候,会遇到一种定位不到的情况,因为web页面有一个加载的过程 当页面元素未出现时,去定位肯定是定位不到的,所以我们需要用到了'等待',该如何使用等待呢,让我们一起来探讨一 ...
- Oracle显式游标和隐式游标
游标的概念: 游标是SQL的一个内存工作区,由系统或用户以变量的形式定义.游标的作用就是用于临时存储从数据库中提取的数据块.在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处 ...
- Android显式意图和隐式意图
intent基本理解 我们都知道Android四大组件:Activity .Service.broadcast receiver 及 内容提供者. 其中,Activity .Service.broad ...
- C#的显式接口和隐式接口
C#的显式接口和隐式接口 接口的实现分为:隐式实现和显式实现.如果类或者结构要实现的是单个接口,可以使用隐式实现,如果类或者结构继承了多个接口那么接口中相同名称成员就要显式实现.显示实现是通过使用接口 ...
- 【selenium-python】显式等待和隐式等待的使用和区别
我的博客 网上教程挺多,看完还是没太理解,看了官方文档稍微理解了一些,在此记录. 部分观点为个人理解,请批判性阅读.如有错误,请指正,万分感谢. 参考: webdriver_waits When to ...
- Andriod 显式启动、隐式启动练习简析
activity_main.xml代码 <?xml version="1.0" encoding="utf-8"?> <LinearLayou ...
- Python Selenium显式等待和隐式等待详细说明
分享知识 传递快乐 现在的大多数的Web应用程序是使用Ajax或一些前端框架技术来完成加载页面,这样程序便不能确定某个元素何时才能完全加载出来.因不能确定元素被加载到浏览器的时间,这使得定位元素变得 ...
最新文章
- (一)深入浅出图解Git,入门到精通(保姆级教程)
- asp 去除最后一个逗号为空字符串的代码
- CSU OJ1960
- silverlight后台加载本地图片
- Servlet中如何获取param-name对应的值?
- Laravel大型项目系列教程(五)之文章和标签管理
- 计算机组成原理,P函数,深入浅出计算机组成原理学习笔记:第五讲
- 文件查找-locate find 学习笔记
- 【Android】Fragment的简单笔记
- 学院派 实践派 计算机科学与技术,饶旻现场为boss所在企业“挑错”
- Spring基础系列-参数校验
- JS:ES6-8 Promise入门
- Linux虚拟机-配置文件说明
- Java中类的初始化顺序是什么?
- c#json对象转数组_C#中将json字符串转为List数组对象
- CDN的基本原理和基础架构
- java读取和写入txt_Java读取和写入txt文件
- Cinnamon 任务栏网速
- Mono.Cecil DefaultAssemblyResolver.Dispose
- 《UnityAPI.ParticleSystem粒子系统》(Yanlz+Unity+SteamVR+云技术+5G+AI+VR云游戏+Particle+loop+Emit+立钻哥哥++OK++)
热门文章
- mysql_select按照指定的格式输出到文件
- Mac OS X 创新卡关三年,唯一看得出版本不同之处是「预设桌布」
- 使用logminer进行审计 Audit by using logminer
- 【转】在 Linux 平台下使用 JNI
- python django flask_Flask 与 Django 的简单对比
- iphone微信美颜插件_Cydia插件推荐
- Unity 2D游戏开发教程之使用脚本实现游戏逻辑
- 直接操作游戏对象C#游戏开发
- dr.web for android version 9,DrWeb安全防护
- python a any_Python any() 函数