JavaScript系列文章:自动类型转换
我们都知道,JavaScript是类型松散型语言,在声明一个变量时,我们是无法明确声明其类型的,变量的类型是根据其实际值来决定的,而且在运行期间,我们可以随时改变这个变量的值和类型,另外,变量在运行期间参与运算时,在不同的运算环境中,也会进行相应的自动类型转换。
自动类型转换一般是根运行环境和操作符联系在一起的,是一种隐式转换,看似难以捉摸,其实是有一定规律性的,大体可以划分为:转换为字符串类型、转换为布尔类型、转换为数字类型。今天我们就介绍一下这几种转换机制。
1. 转换为字符串类型(to string)
当加号“+”作为二元操作符(binary)并且其中一个操作数为字符串类型时,另一个操作数将会被无条件转为字符串类型:
// 基础类型 var foo = 3 + ''; // "3" var foo = true + ''; // "true" var foo = undefined + ''; // "undefined" var foo = null + ''; // "null"
// 复合类型 var foo = [1, 2, 3] + ''; // "1,2,3" var foo = {} + ''; // "[object Object]" // 重写valueOf()和toString() var o = {
valueOf: function() {
return 3;
},
toString: function() {
return 5;
}
}; foo = o + ''; // "3" o = {
toString: function() {
return 5;
}
}; foo = o + ''; // "5"
从上面代码中可以看到,对于基础类型,会直接转为与字面量相一致的字符串类型,而对于复合类型,会先试图调用对象的valueOf()方法,如果此方法返回值是引用类型,则接着再调用其toString()方法,最后将返回值转为字符串类型。上面我们定义了一个对象,包含valueOf()和toString()方法,然后和一个空字符串进行运算,可以看得出来,它是调用了valueOf()方法,然后我们重写此对象,将valueOf()移除,也就是不重写object的valueOf()方法,从最后的结果来看,它最终是调用了toString()方法,然后将返回的数字类型5与空字符串进行运算,最终得到一个字符串类型的值。
2. 转为布尔类型(to boolean)
a. 数字转为布尔类型(from number)
当数字在逻辑环境中执行时,会自动转为布尔类型。0和NaN会自动转为false,其余数字都被认为是true,代码如下:
// 0和NaN为false,其余均为true if (0) {
console.log('true');
} else {
console.log('false'); // output: false
} if (-0) {
console.log('true');
} else {
console.log('false'); // output: false
} if (NaN) {
console.log('true');
} else {
console.log('false'); // output: false
} // 其余数字均为true if (-3) {
console.log('true'); // output: true
} else {
console.log('false');
} if (3) {
console.log('true'); // output: true
} else {
console.log('false');
}
从上面的代码中可以看出,非0负值也会被认为是true,这一点需要注意。
b. 字符串转为布尔类型(from string)
和数字类似,当字符串在逻辑环境中执行时,也会被转为布尔类型。空字符串会被转为false,其它字符串都会转为true,代码如下:
// 空字符串为false if ('') {
console.log('true');
} else {
console.log('false'); // output: false
} // 其他字符串均为true if ('0') {
console.log('true'); // output: true
} else {
console.log('false');
} if ('false') {
console.log('true'); // output: true
} else {
console.log('false');
}
c. undefined和null转为布尔类型(from undefined and null)
当undefined和null在逻辑环境中执行时,都被认为是false,看下面代码:
// undefined和null都为false if (undefined) {
console.log('true');
} else {
console.log('false'); // output: false
} if (null) {
console.log('true');
} else {
console.log('false'); // output: false
}
d. 对象转为布尔类型(from object)
当对象在逻辑环境中执行时,只要当前引用的对象不为空,都会被认为是true。如果一个对象的引用为null,根据上面的介绍,会被转换为false。虽然使用typeof检测null为"object",但它并不是严格意义上的对象类型,只是一个对象空引用的标识。
另外,我们这里的逻辑环境不包括比较操作符(==),因为它会根据valueOf()和toString()将对象转为其他类型。
现在我们来看一下对象类型的示例:
// 字面量对象
var o = {
valueOf: function() {
return false;
},
toString: function() {
return false;
}
}; if (o) {
console.log('true'); // output: true
} else {
console.log('false');
} // 函数
var fn = function() {
return false;
}; if (fn) {
console.log('true'); // output: true
} else {
console.log('false');
} // 数组
var ary = []; if (ary) {
console.log('true'); // output: true
} else {
console.log('false');
} // 正则表达式
var regex = /./; if (regex) {
console.log('true'); // output: true
} else {
console.log('false');
}
可以看到,上面的对象都被认为是true,无论内部如何定义,都不会影响最终的结果。
正是由于对象总被认为是true,使用基础类型的包装类时,要特别小心:
// 以下包装对象都被认为是true if (new Boolean(false)) {
console.log('true'); // output: true
} else {
console.log('false');
} if (new Number(0)) {
console.log('true'); // output: true
} else {
console.log('false');
} if (new Number(NaN)) {
console.log('true'); // output: true
} else {
console.log('false');
} if (new String('')) {
console.log('true'); // output: true
} else {
console.log('false');
}
根据们上面介绍的,它们对应的基础类型都会被转为false,但使用包装类实例的时候,引擎只会判断其引用是否存在,不会判断内部的值,这一点初学者需要多多注意。当然我们也可以不使用new关键字,而是显示的调用其包装类函数,将这些值转为布尔类型:
if (Boolean(false)) {
console.log('true');
} else {
console.log('false'); // output: false
} if (Number(0)) {
console.log('true');
} else {
console.log('false'); // output: false
} if (Number(NaN)) {
console.log('true');
} else {
console.log('false'); // output: false
} if (String('')) {
console.log('true');
} else {
console.log('false'); // output: false
}
对于Boolean类,有一个特别需要注意的是,当传入一个字符串时,它不会去解析字符串内部的值,而是做个简单地判断,只要不是空字符串,都会被认为是true:
if (Boolean('false')) {
console.log('true'); // output: true
} else {
console.log('false');
} if (Boolean('')) {
console.log('true');
} else {
console.log('false'); // output: false
}
上面介绍了这么多,还有几个例子需要提一下,那就是逻辑非、逻辑与和逻辑或操作符,连用两个逻辑非可以把一个值转为布尔类型,而使用逻辑与和逻辑或时,根据上面的规则,参与运算的值会被转换为相对应的布尔类型:
// 下面几个转为false var isFalse = !!0; // false var isFalse = !!NaN; // false var isFalse = !!''; // false var isFalse = !!undefined; // false var isFalse = !!null; // false // 下面都转为true var isTrue = !!3; // true var isTrue = !!-3; // true var isTrue = !!'0'; // true var isTrue = !!{}; // true // 逻辑与 var foo = 0 && 3; // var foo = -3 && 3; // // 逻辑或 var foo = 0 || 3; // var foo = -3 || 3; // -3
3. 转为数字类型(to number)
操作数在数字环境中参与运算时,会被转为相对应的数字类型值,其中的转换规则如下:
i. 字符串类型转为数字(from string): 空字符串被转为0,非空字符串中,符合数字规则的会被转换为对应的数字,否则视为NaN
ii. 布尔类型转为数字(from boolean): true被转为1,false被转为0
iii. null被转为0,undefined被转为NaN
iv. 对象类型转为数字(from object): valueOf()方法先试图被调用,如果调用返回的结果为基础类型,则再将其转为数字,如果返回结果不是基础类型,则会再试图调用toString()方法,最后试图将返回结果转为数字,如果这个返回结果是基础类型,则会得到一个数字或NaN,如果不是基础类型,则会抛出一个异常
一个其他类型的值被转换为数字,跟其参与运算的操作符有很密切的联系,下面我们就来详细介绍:
当加号“+”作为一元操作符(unary)时,引擎会试图将操作数转换为数字类型,如果转型失败,则会返回NaN,代码如下所示:
var foo = +''; // var foo = +'3'; // var foo = +'3px'; // NaN var foo = +false; // var foo = +true; // var foo = +null; // var foo = +undefined; // NaN
上面代码中,对于不符合数字规则的字符串,和直接调用Number()函数效果相同,但和parseInt()有些出入:
var foo = Number('3px'); // NaN var foo = parseInt('3px'); //
可以看出,parseInt对字符串参数比较宽容,只要起始位置符合数字类型标准,就逐个解析,直到遇见非数字字符为止,最后返回已解析的数字部分,转为数字类型。
当加号“+”作为二元操作符时,我们上面也提到过,如果一个操作数为字符串,则加号“+”作为字符串连接符,但如果两个操作数都不是字符串类型,则会作为加法操作符,执行加法操作,这个时候,其他数据类型也会被转为数字类型:
var foo = true + 1; // var foo = true + false; // var foo = true + null; // var foo = null + 1; // var foo = null + undefined; // NaN var foo = null + NaN; // NaN
上面加法运算过程中都出现了类型转换,true转为1,false转为0,null转为0,undefined转为NaN,最后一个例子中,null和NaN运算时,是先转为0,然后参与运算,NaN和任何其他数字类型运算时都会返回NaN,所以最终这个结果还是NaN。
对于undefined转为NaN似乎很好理解,但为什么null会转为0呢?这里也有些历史渊源的,熟悉C的朋友都知道,空指针其实是设计为0值的:
// 空指针的值为0 int *p = NULL; if (p == ) {
printf("NULL is 0"); // output: NULL is 0
}
编程语言的发展是有规律的,语言之间也存在着密切的关联,新的语言总是会沿用老的传统,继而添加一些新的特性。从上面的例子中,我们发现,null被转为0其实很好理解,一点也不奇怪。
另外,我们可别忘了减号“-”操作符,当减号“-”作为一元操作符(unary negation)时,也会将操作数转换为数字,只不过转换的结果与上面相反,合法的数字都被转为负值。
除加号“+”以外的其他二元操作符,都会将操作数转为数字,字符串也不例外(如果转型失败,则返回NaN继续参与运算):
var foo = '5' - '2'; // var foo = '5' * '2'; // var foo = '5' / '2'; // 2.5 var foo = '5' % '2'; // var foo = '5' << '1'; // var foo = '5' >> '1'; // var foo = '5' ** '2'; // var foo = '5' * true; // var foo = '5' * null; // var foo = '5' * undefined; // NaN var foo = '5' * NaN; // NaN
上面的操作符中,位移和求幂操作符平时用的不多,不过在某些场景下(比如算法中)还是挺实用的。我们都知道,JavaScript中的数字类型都以浮点型存储,这就意味着我们不能想C和Java那样直接求整除结果,而是通过相关的函数进一步处理实现的,如果通过位移可以简化不少,而求幂操作也可以直接通过求幂运算符算出结果,看下面代码:
// 浮点型运算
var foo = 5 / 2; // 2.5 // 整除操作
var foo = Math.floor(5 / 2); // // 向右移一位实现整除
var foo = 5 >> 1; // // 求幂函数
var foo = Math.pow(5, 2); // // 求幂运算
var foo = 5 ** 2; //
除了上面的操作符之外,递增和递减操作符也会将操作数转为数字,下面以前缀递增操作符为例:
var foo = ''; ++foo; // foo: 1 var foo = '3'; ++foo; // foo: 4 var foo = true; ++foo; // foo: 2 var foo = null; ++foo; // foo: 1 var foo = undefined; ++foo; // foo: NaN var foo = '3px'; ++foo; // foo: NaN
上面就是基本数据类型在数字环境下的转换规则。对于对象类型,同样有一套转换机制,我们上面也提到了,valueOf()方法和toString()方法会在不同的时机被调用,进而得到相应的返回值,最后根据返回值再进行类型转换,将其转为目标类型。由于篇幅限制,关于自动类型转换的后续内容,博主安排在下一篇中讲解,敬请期待。
参考资料:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators
http://jibbering.com/faq/notes/type-conversion/
http://stackoverflow.com/questions/18808226/why-is-typeof-null-object
JavaScript系列文章:自动类型转换的更多相关文章
- JavaScript系列文章:自动类型转换-续
在上一篇文章中,我们详细讲解了JavaScript中的自动类型转换,由于篇幅限制,没能覆盖到所有的转换规则,这次准备详细讲解一下. 上次我们提到了对象类型参与运算时转换规则: 1). 在逻辑环境中执行 ...
- JavaScript系列文章:谈谈let和const
JavaScript系列文章:谈谈let和const 最近接触到ES6的一些相关新特性,想借let和const两个命令谈谈JavaScript在变量方面的改进. 由于let和const有很多相似之 ...
- 【转】JavaScript系列文章:自动类型转换
我们都知道,JavaScript是类型松散型语言,在声明一个变量时,我们是无法明确声明其类型的,变量的类型是根据其实际值来决定的,而且在运行期间,我们可以随时改变这个变量的值和类型,另外,变量在运行期 ...
- JavaScript系列文章:变量提升和函数提升
第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...
- JavaScript系列文章:从let和const谈起
注册博客园账号也有好些年了,有事没事经常来逛逛,感觉博客园的同学们一直都很活跃,相比国内其他社区来讲,这里的技术氛围很浓,非常适合学习和交流,所以博主我也决定在这里驻扎了,在这里,博主希望能坚持写一些 ...
- JavaScript系列文章:详解正则表达式之三
在上两篇文章中博主介绍了JavaScript中的正则常用方法和正则修饰符,今天准备聊一聊元字符和高级匹配的相关内容. 首先说说元字符,想必大家也都比较熟悉了,JS中的元字符有以下几种: / \ | . ...
- JavaScript系列文章:详解正则表达式之二
在上一篇文章中我们讲了正则表达式的基本用法,接下来博主想聊聊其中的细节,今天就从正则修饰符开始吧. 正则修饰符又称为正则标记(flags),它会对正则的匹配规则做限定,进而影响匹配的最终结果.在上次的 ...
- JavaScript系列文章:详解正则表达式之一
正则表达式是一个精巧的利器,经常用来在字符串中查找和替换,JavaScript语言参照Perl,也提供了正则表达式相关模块,开发当中非常实用,在一些类库或是框架中,比如jQuery,就存在大量的正则表 ...
- JavaScript系列文章:不能不看的数据类型检测
由于JavaScript是门松散类型语言,定义变量时没有类型标识信息,并且在运行期可以动态更改其类型,所以一个变量的类型在运行期是不可预测的,因此,数据类型检测在开发当中就成为一个必须要了解和掌握的知 ...
随机推荐
- sencha ext js 6 入门
Sencha Ext JS号称是目前世界上最先进和最强大的.支持多平台多设备的JavaScript应用程序开发框架.首先看一下Ext JS的发展简史. 1 Ext JS发展简史 YUI-Ext的作者J ...
- 模块化你的JS代码
为什么要使用模块模式? 因为在全局作用域中声明的变量和函数都自动成为全局对象Window的属性,这经常会导致命名冲突,还会导致一些非常重要的可维护性难题,全局变量越多,引入错误BUG的概率就越大!所以 ...
- ArcGIS Engine开发之地图基本操作(2)
地图数据的加载 1.加载地图文档 ArcGIS Engine支持加载多种类型的数据,有矢量数据的Coverage.Shapefile.dwg/dxf文件,栅格数据的BMP.GRID.控件数据库等.很多 ...
- swift学习笔记1——基础部分
之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...
- Atitit.http代理的实现 代码java php c# python
Atitit.http代理的实现 代码java php c# python 1. 代理服务器用途 代理服务器看成是一种扩展浏览器功能的途径.例如,在把数据发送给浏览器之前,可以用代理服务器压缩数据 调 ...
- SE Springer小组之《Spring音乐播放器》可行性研究报告三、四
3 对现有系统的分析 由于本次可行性分析主要是建立在团队自行实现一个音乐软件的目标上,并不是在一个现有系统的基础上开发改进的新系统.因此这里将分析一款市面上已经存在的音乐软件(以下称为W音乐),并为之 ...
- centos编译安装mysql
groupadd mysql #添加mysql组useradd -g mysql -s /sbin/nologin mysql #创建用户mysql并加入到mysql组,不允许mysql用户直接登录系 ...
- Spring、hibernate以及struts2三大框架的整合
1.首先导入整合框架所需要的43个jar包: 2.配置xml文件: <?xml version="1.0" encoding="UTF-8"?> & ...
- Jsoup问题---获取http协议请求失败 org.jsoup.UnsupportedMimeTypeException: Unhandled content type. Must be text/*, application/xml, or application/xhtml+xml.
Jsoup问题---获取http协议请求失败 1.问题:用Jsoup在获取一些网站的数据时,起初获取很顺利,但是在访问某浪的数据是Jsoup报错,应该是请求头里面的请求类型(ContextType)不 ...
- ERROR: Unable to globalize '/usr/local/NONE/etc/php-fpm.d/*.conf' 问题的解决
今天继续作大死,趟php7的配置的坑. 照例,安装了昨天的各种扩展之后,解压php7的压缩文件到 /usr/local/. 然后开始配置config的扩展: ./configure --prefix= ...