写在前面的话
近期刷了点js的面试题,发现很多基础知识已经模糊,于是重新回去看了一下《javascript权威指南(第六版)》,现将薄弱知识点记录下来,供以后再次复习。
正文开始
首先祭出必备的万能神器——类型转换表格(显式转换):
图中一些知识点的解释:
- 首先关于null、undefined转换为Object,在权威指南中写的是TypeError,但这里其实用Object()来转换时,会返回一个空对象{},报错的情况是在当你把它们当做一个对象来用时,比如读取对象属性null.x,此时就会出现TypeError。
- 补充:同样会报错的还有把一个非函数对象拿来调用
- 补充:虽然null不能当作对象来用,但是要记住 typeof null 的值是 object
- 字符串转换为数字时调用的是Number(),该函数只能转换纯数字字符串,允许前后有空格,中间不能有,否则返回NaN。
- 补充:parseInt()、parseFloat()同样可以将字符串转换为数字,不同于Number()的是,只要非空格的第一个字符是数字,它们就会尽可能长地进行转换,直到遇到空格会或非字母。如果第一个非空格字符是字母,则返回NaN。
- 对于数组[9]转化为Number()时的情况,还可以扩展到[[[9]]]转化为number时,也为9.
一些前置知识点
不同类型的对象调用toString()的结果(在没有重写该方法的情况下):
- 普通对象{}.toString() => "[object Object]"
- 数组类.toString() => 将每个数组元素转化为一个字符串,并在元素之间添加逗号后,合并成一个字符串【相当于arr.join(","))】
- 函数类.toString() => 返回这个函数的实现的字符串【也就是定义函数的源代码"function(){...} "】
- 日期类(Date).toString() => 返回一个可读的日期,与console.log结果相同
- RegExp类.toString() => 返回该正则表达式的字面量形式的字符串【相当于定义时的源字符串】
不同类型的对象调用valueOf()的结果(在没有重写该方法的情况下):
- 各种对象调用valueOf()时的结果,普通对象、数组类、函数类、RegExp类,都是返回他们本身。
- 如:Object {a: 1, b: 4} 、 function(){} 、 ["a", "b", "c"] 等形式。
- 注意这里与toString()不同,并不会用双引号括起来,toSting()的结果用typeof是string,而这里的结果用typeof是object、function等
- 日期类.valueOf()比较特殊,会返回一个1970年1月1日以来的毫秒数。
toString()和valueOf()在一些情况下需要替代使用:
对象转化为字符串的步骤:
- 如果具有toString(),且可以返回一个原始值,则调用,再将该原始值转化为字符串。
- 否则(没有toString()或不返回原始值),调用能返回原始值的valueOf(),再将这个原始值转化为字符串。
- 否则以上两个方法都不能取得原始值,返回一个类型错误
对象转化为数字的步骤:
- 先调用能够返回原始值的valueOf(),将原始值转换为数字。
- 否则调用toString()得到原始值,转化为数字。
- 上述两个方法都不能取得原始值,就返回一个类型错误。
下面来说说隐式类型转换
一元运算符部分
- 遇到 - * / % 等4个运算符时,会把操作数转化为数字。
- 遇到!时,会把操作数转化成Boolean值,并取反。
- 遇到+号时,比较复杂:
- 如果其中一个操作数是对象,则遵循对象到原始值的转换规则,除了日期对象调用toString(),以外,其他对象都通过valueOf()来转换(这是+号的隐式规定,即使另一个操作数是字符串,也不会优先调用toString())。当然这里调用valueOf()的前提是它可以返回一个原始值,如果没有这个方法或者不返回原始值,则调用toString()。
- 如果是布尔值和字符串,布尔值转化为字符串,拼接。
- 如果是布尔值和数字,布尔值转化为数字,相加。
- 如果是数字和字符串,则进行拼接处理。
- 如果是两个数字,加法操作。
- 下面举几个栗子:
- x + "" 等价于String(x)
- +x 等价于Number(x)
- x-0 等价于Number(x)
- !!x 等价于Boolean(x)
比较运算符部分
== 的比较过程:
null == undefined,为true
数字vs字符串,字符串转化为数字
布尔值vs字符串,两者转数字
字符串vs字符串,按unicode依次比较,直到结束或某一个字符不相同
对象vs数字,对象vs字符串,对象首先尝试valueOf(),再尝试toString()【日期类只调用toString()】,目的都是将对象转化为原始值,再进行比较。
如果其中一个操作数是NaN,那么总是返回false
其他不同类型之间的比较均不相等
. > < >= <=等比较运算符
如果有操作数是对象,则按==中的方式转换为原始值。
转换之后如果两者都是字符串,则按照Unicode字符的索引顺序比较
如果转换后至少有一个操作数不是字符串,则两个操作数都转化为数字进行比较。
如果其中一个操作数是NaN,那么总是返回false
说到NaN,再补充一点
- 用isNaN来检测某个值是否为NaN其实不准确,因为isNaN({})等在判断之前,首先会用Number()将{}进行一次隐式转换,任何对象、非纯数字的字符串,都会被转化成NaN,此时判断时会得true
- 真正检测NaN值的,是利用它不等于自身的性质,a !== a 来检测,如果为true,则a是NaN。
举个综合的例子:
console.log(([])?true:false);
- 解析:判断[]的真假,Boolean([])是true,所以控制台输出true。
console.log(([]==false?true:false));
- 解析:判断[]是否等于false,不能简单得看表格里的Boolean()转换的值,而是要遵循==的转换规则,==两边只要有一个不是字符串, 就要将两边都转换成数字再来比较,根据表格,[]转为number值时是0,false转为number值也是0,转换后是 0==0?,成立,所以控制台输出true。
console.log(({}==false)?true:false);
- 解析:{}转为number值总是NaN,false转换成数字是0,最终NaN == 0 ?,不成立,所以控制台输出false。
最终总结
- 对象的隐式转换里面坑比较多,其中之一就是,基础知识不牢固的话,不知道该转换成什么类型。这里根据自己的理解记录一下:
- 在if中,需要布尔值,所以总是调用Boolean()来进行转换。
- 在==、><、>=、<=这些比较运算符中,默认要求是要转换为Number。对象优先调用valueOf(),其次调用toString(),最终要取得一个原始值(否则报错),然后再根据两边的原始值判断是否需要进一步转换成数字。
- 补充:除了两边的原始值都是字符串的情况,否则都需要进一步转换成数字来比较。
- 在switch中,switch里的变量与case值比较时,是===,既毕竟类型,也比较值。
今天的笔记暂时到这,如果有不对的地方,欢迎大家指正交流 : )