我们都知道,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: 自动类型转换的更多相关文章

  1. JavaScript: 自动类型转换-续

    在上一篇文章中,我们详细讲解了JavaScript中的自动类型转换,由于篇幅限制,没能覆盖到所有的转换规则,这次准备详细讲解一下. 上次我们提到了对象类型参与运算时转换规则: 1). 在逻辑环境中执行 ...

  2. 慎用javascript自动类型转换

    1.如果把非空对象用在逻辑运算环境中,则对象被转换为true.此时的对象包括所有类型的对象,即使是值为false的包装对象也被转换为true. 2.如果把对象用在数值运算环境中,则对象会被自动转换为数 ...

  3. JavaScript系列文章:自动类型转换-续

    在上一篇文章中,我们详细讲解了JavaScript中的自动类型转换,由于篇幅限制,没能覆盖到所有的转换规则,这次准备详细讲解一下. 上次我们提到了对象类型参与运算时转换规则: 1). 在逻辑环境中执行 ...

  4. JavaScript系列文章:自动类型转换

    我们都知道,JavaScript是类型松散型语言,在声明一个变量时,我们是无法明确声明其类型的,变量的类型是根据其实际值来决定的,而且在运行期间,我们可以随时改变这个变量的值和类型,另外,变量在运行期 ...

  5. 【转】JavaScript系列文章:自动类型转换

    我们都知道,JavaScript是类型松散型语言,在声明一个变量时,我们是无法明确声明其类型的,变量的类型是根据其实际值来决定的,而且在运行期间,我们可以随时改变这个变量的值和类型,另外,变量在运行期 ...

  6. JavaScript中判断变量类型最简洁的实现方法以及自动类型转换(#################################)

    这篇文章主要介绍了JavaScript中判断整字类型最简洁的实现方法,本文给出多个判断整数的方法,最后总结出一个最短.最简洁的实现方法,需要的朋友可以参考下 我们知道JavaScript提供了type ...

  7. javascript之类型转换

    JavaScript是一种无类型语言,但同时JavaScript提供了一种灵活的自动类型转换的处理方式.基本规则是,如果某个类型的值用于需要其他类型的值的环境中,JavaScript就自动将这个值转换 ...

  8. JavaScript自动生成博文目录导航

    转载于:JavaScript自动生成博文目录导航 我们在写博客的时候,如果博文里面有目录,会给人结构清晰.一种一目了然的感觉,看目录就知道这篇博文要讲解的内容,并且点击目录标题就可以跳转到 具体的内容 ...

  9. struts基于ognl的自动类型转换需要注意的地方

    好吧,坎坷的过程我就不说了,直接上结论: 在struts2中使用基于ognl的自动类型转换时,Action中的对象属性必须同时添加get/set方法. 例如: 客户端表单: <s:form ac ...

随机推荐

  1. 测量MySQL的表达式和函数的速度

    测量MySQL的表达式和函数的速度,可以调用benchmark()函数.语法格式是benchmark(loop_count,expr).运行的返回值是0,但是mysql会打印一行显示语句大概要执行多长 ...

  2. 关于Linux中nohup.out日志过大问题

    背景,java项目,一般在运行JAVA程序时需要用到nohup命令来实现后台启动日志,默认保存在当前目露nohup.out文件.但是有些程序输出nohup文件会出现过大的情况. 在此解决如下: 1,在 ...

  3. 廖雪峰Git教程3

    转自:https://www.liaoxuefeng.com/wiki/896043488029600 [标签管理] 发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签 ...

  4. Linux目录结构和作用

    转载自:https://blog.csdn.net/wangrongrongwq/article/details/79624797 [常见目录说明] 目录 /bin 存放二进制可执行文件(ls,cat ...

  5. PAT 甲级 1077 Kuchiguse (20 分)(简单,找最大相同后缀)

    1077 Kuchiguse (20 分)   The Japanese language is notorious for its sentence ending particles. Person ...

  6. JAVA Asponse.Word Office 操作神器,借助 word 模板生成 word 文档,并转化为 pdf,png 等多种格式的文件

    一,由于该 jar 包不是免费的, maven 仓库一般不会有,需要我们去官网下载并安装到本地 maven 仓库 1,用地址   https://www-evget-com/product/564  ...

  7. Bazel安装及使用入门

    Bazel [文档][https://docs.bazel.build/versions/1.1.0/bazel-overview.html] MacOS安装 brew tap bazelbuild/ ...

  8. Django:前后端分离 djangorestframework开发API接口 serializer序列化认证组件

    参考:https://blog.csdn.net/zhangmengran/article/details/84887206 目的: 使用serializer序列化器将QuerySet数据序列化为js ...

  9. System.gc()介绍

    System.gc()用于垃圾收集器,调用垃圾收集器将回收未使用的 System.gc()进行回收的准则: 回收没有被任何可达变量指向的对象 JDK实现 public static void gc() ...

  10. TCP粘包和拆包的定义,产生的原因以及解决方案

    TCP粘包:指发送方发送的若干数据包在接收方接收时粘成一团,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾 产生的原因: 1.发送方的原因:TCP默认使用Nagle算法,而Nagle算法主要做两件 ...