我们都知道,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,其余均为trueif (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
}// 其余数字均为trueif (-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,代码如下:

// 空字符串为falseif ('') {console.log('true');
} else {console.log('false');    // output: false
}// 其他字符串均为trueif ('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都为falseif (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,使用基础类型的包装类时,要特别小心:

// 以下包装对象都被认为是trueif (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
}

上面介绍了这么多,还有几个例子需要提一下,那就是逻辑非、逻辑与和逻辑或操作符,连用两个逻辑非可以把一个值转为布尔类型,而使用逻辑与和逻辑或时,根据上面的规则,参与运算的值会被转换为相对应的布尔类型:

// 下面几个转为falsevar isFalse = !!0;            // falsevar isFalse = !!NaN;         // falsevar isFalse = !!'';           // falsevar isFalse = !!undefined;    // falsevar isFalse = !!null;         // false// 下面都转为truevar isTrue = !!3;             // truevar isTrue = !!-3;            // truevar isTrue = !!'0';           // truevar isTrue = !!{};            // true// 逻辑与var foo = 0 && 3;             // 0var foo = -3 && 3;            // 3// 逻辑或var foo = 0 || 3;             // 3var foo = -3 || 3;            // -3

3. 转为数字类型(to number)

操作数在数字环境中参与运算时,会被转为相对应的数字类型值,其中的转换规则如下:

i. 字符串类型转为数字(from string): 空字符串被转为0,非空字符串中,符合数字规则的会被转换为对应的数字,否则视为NaN

ii. 布尔类型转为数字(from boolean): true被转为1,false被转为0

iii. 对象类型转为数字(from object): valueOf()方法先试图被调用,如果调用返回的结果为基础类型,则再将其转为数字,如果返回结果不是基础类型,则会进一步调用返回值的toString()方法,最后再试图将返回结果转为数字

iv. null被转为0,undefined被转为NaN

一个其他类型的值被转换为数字,跟其参与运算的操作符有很密切的联系,下面我们就来详细介绍:

加号“+”作为一元操作符(unary)时,引擎会试图将操作数转换为数字类型,如果转型失败,则会返回NaN,代码如下所示:

var foo = +'';            // 0var foo = +'3';           // 3var foo = +'3px';         // NaNvar foo = +false;         // 0var foo = +true;          // 1var foo = +null;          // 0var foo = +undefined;     // NaN

上面代码中,对于不符合数字规则的字符串,和直接调用Number()函数效果相同,但和parseInt()有些出入:

var foo = Number('3px');      // NaNvar foo = parseInt('3px');    // 3

可以看出,parseInt对字符串参数比较宽容,只要起始位置符合数字类型标准,就逐个解析,直到遇见非数字字符为止,最后返回已解析的数字部分,转为数字类型。

加号“+”作为二元操作符时,我们上面也提到过,如果一个操作数为字符串,则加号“+”作为字符串连接符,但如果两个操作数都不是字符串类型,则会作为加法操作符,执行加法操作,这个时候,其他数据类型也会被转为数字类型:

var foo = true + 1;          // 2var foo = true + false;      // 1var foo = true + null;       // 1var foo = null + 1;          // 1var foo = null + undefined;  // NaNvar foo = null + NaN;        // NaN

上面加法运算过程中都出现了类型转换,true转为1,false转为0,null转为0,undefined转为NaN,最后一个例子中,null和NaN运算时,是先转为0,然后参与运算,NaN和任何其他数字类型运算时都会返回NaN,所以最终这个结果还是NaN。

对于undefined转为NaN似乎很好理解,但为什么null会转为0呢?这里也有些历史渊源的,熟悉C的朋友都知道,空指针其实是设计为0值的:

// 空指针的值为0int *p = NULL;if (p == 0) {printf("NULL is 0");    // output: NULL is 0
}

编程语言的发展是有规律的,语言之间也存在着密切的关联,新的语言总是会沿用老的传统,继而添加一些新的特性。从上面的例子中,我们发现,null被转为0其实很好理解,一点也不奇怪。

另外,我们可别忘了减号“-”操作符,当减号“-”作为一元操作符(unary negation)时,也会将操作数转换为数字,只不过转换的结果与上面相反,合法的数字都被转为负值

除加号“+”以外的其他二元操作符,都会将操作数转为数字,字符串也不例外(如果转型失败,则返回NaN继续参与运算):

var foo = '5' - '2';          // 3var foo = '5' * '2';          // 10var foo = '5' / '2';           // 2.5var foo = '5' % '2';          // 1var foo = '5' << '1';          // 10var foo = '5' >> '1';          // 2var foo = '5' ** '2';          // 25var foo = '5' * true;          // 5var foo = '5' * null;          // 0var foo = '5' * undefined;     // NaNvar foo = '5' * NaN;          // NaN

上面的操作符中,位移和求幂操作符平时用的不多,不过在某些场景下(比如算法中)还是挺实用的。我们都知道,JavaScript中的数字类型都以浮点型存储,这就意味着我们不能想C和Java那样直接求整除结果,而是通过相关的函数进一步处理实现的,如果通过位移可以简化不少,而求幂操作也可以直接通过求幂运算符算出结果,看下面代码:

// 浮点型运算
var foo = 5 / 2;                // 2.5// 整除操作
var foo = Math.floor(5 / 2);    // 2// 向右移一位实现整除
var foo = 5 >> 1;              // 2// 求幂函数
var foo = Math.pow(5, 2);       // 25// 求幂运算
var foo = 5 ** 2;              // 25

除了上面的操作符之外,递增和递减操作符也会将操作数转为数字,下面以前缀递增操作符为例:

var foo = '';++foo;    // foo: 1var foo = '3';++foo;    // foo: 4var foo = true;++foo;    // foo: 2var foo = null;++foo;    // foo: 1var foo = undefined;++foo;    // foo: NaNvar foo = '3px';++foo;    // foo: NaN

上面就是基本数据类型在数字环境下的转换规则。对于对象类型,同样有一套转换机制,我们上面也提到了,valueOf()方法和toString()方法会在不同的时机被调用,进而返回相应的数据,根据返回值再进行下一步的转换。由于篇幅限制,关于自动类型转换的后续内容,博主安排在下一篇中讲解,敬请期待。

JavaScript系列文章:自动类型转换相关推荐

  1. JavaScript系列文章:谈谈let和const

    最近接触到ES6的一些相关新特性,想借let和const两个命令谈谈JavaScript在变量方面的改进. 由于let和const有很多相似之处,我们就先说一说let吧. 1. let添加了块级作用域 ...

  2. JavaScript系列文章:变量提升和函数提升

    第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...

  3. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    才华横溢的Stoyan Stefanov,在他写的由O'Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会 ...

  4. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点(转)

    才华横溢的Stoyan Stefanov,在他写的由O'Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会 ...

  5. 深入理解JavaScript系列(4):立即调用的函数表达式

    前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下"自执行"这个叫法,本文对这个功能的叫 ...

  6. 深入理解JavaScript系列(32):设计模式之观察者模式

    介绍 观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们 ...

  7. 深入理解JavaScript系列:《你真懂JavaScript吗?》答案详解

    介绍 昨天发的<大叔手记(19):你真懂JavaScript吗?>里面的5个题目,有很多回答,发现强人还是很多的,很多人都全部答对了. 今天我们来对这5个题目详细分析一下,希望对大家有所帮 ...

  8. 深入理解JavaScript系列:This? Yes,this!

    介绍 在这篇文章里,我们将讨论跟执行上下文直接相关的更多细节.讨论的主题就是this关键字.实践证明,这个主题很难,在不同执行上下文中this的确定经常会发生问题. 许多程序员习惯的认为,在程序语言中 ...

  9. 深入理解JavaScript系列(5):强大的原型和原型链

    前言 JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型. 虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大.实 ...

最新文章

  1. 嵌入式Linux系统中的.lds链接脚本基础
  2. redis主键失效机制
  3. 【2012百度之星 / 资格赛】I:地图的省钱计划
  4. hive获取月份_【Hive】Hive中常用日期函数整理
  5. HDU 3916 Sequence Decomposition 【贪心】
  6. Http长连接的例子_亲测可用哦
  7. 多核处理器_游戏爱好者的福音!AMD全新一代高性能多核处理器3950X
  8. 如何巧妙使用Camtasia库中的素材?
  9. Android 开发一定要看的15个实战项目
  10. 【zookeeper 获取节点数据getData源码解析】
  11. 基于java的户籍管理系统的设计与实现
  12. hp DV4 声卡驱动 IDT声卡补丁1.1
  13. word文字上下间距怎么调_word文档里的文字上下左右之间的间距如何调整?
  14. 使用模板快速编写测试用例
  15. inputBox 与 Application.inputBox 的用法与区别。
  16. 神经网络(二)—— 神经元、Logistic回归模型
  17. 对日ODC与欧美ODC技术分析
  18. 2020年部编人教版小学一年级语文(上册)全部知识点汇总
  19. 延边大学计算机科学与技术专业(研)解读
  20. (译)理解ConstraintLayout性能上的好处

热门文章

  1. 创业公司CEO每天应该做的13件事
  2. 年过35岁的程序员都去哪儿了
  3. 《HTML5与CSS3实例教程》
  4. mysql开启skip-name-resolve 导致root@127.0.0.1(localhost)访问引发的ERROR 1045 (28000)错误解决方案...
  5. 数据库启动时报ORA-00845错误解决方法
  6. C# 设计时动态改变实体在PropertyGrid中显示出来的属性
  7. 2014 7-8月MoonCake新增功能更新
  8. POJ 3761:Bubble Sort——组合数学
  9. Hibernate 事务总结
  10. 全文检索lucene中文分词的一些总结