近日看到一篇叫做百度web前端面试经历(原文已佚,这篇也是转载)的文章,其中作者详细的描述了自己去百度web前端面试的经历,Interviewer问及问题的范围也十分广泛,有一定的参考价值。不过这篇文章的美中不足之处就在于,作者没有对问题给出一个合理的解释,有些面试的问题作者本身所知也并不详细。这里就写一篇小文章分析一下面试中提到的问题。

面试官:javascript的类型转换(比如"2"*1, "a"*1)。

我:javascript会调用valueOf来转换为一个基本数据类型,在这种情况下,如果javascript不能通过valueOf转成一个number,会尝试调用toString,然后再转。实在无法转就只能NaN了。

由于JS一切皆对象的基础理念,Object 在 JS中广泛存在,但JS的所有变量还是分为原始值和对象。JS的类型转换不仅仅是狭义意义上的原始值(即 string、int 和 boolean)之间的互相转换,按照犀牛书中所说,主要分为三个大类:

  • 原始值转原始值
  • 原始值转对象
  • 对象转原始值

在此基础上,也有根据转换方式将其分为“显式转换”和“隐式转换”的说法,所谓的显示转换,即运用基本类型函数的构造函数(Number()String()Boolean()Object())对某个变量进行人为的强制类型转换;所谓的隐式转换,是指在进行运算时JavaScript进行自动的类型转换,如 == 运算符、+ 运算符等。其中有几个比较著名的隐式转换,可以作为类型转换的惯用方式:

x+''   //等价于 String(x)
+x     //等价于 Number(x)
!!x    //等价于 Boolean(x)

然后我们再来看一下,类型转换的三个大类。

  • 原始值转原始值

    相对简单,可以采用上面所述的显式或隐式的转换方式,在字符串、数字和布尔值之间进行转换。

    但是这里有一些值得注意的转换,例如空字符串、正负0、NaN 在转化为布尔值时均为false(注意字符串 'false' 转化为布尔值为真!),在一些情况下可以进行快速的判断;字符串如果不能转为数字,则转化为 NaNundefined 也会转化为这个),而 null 却可以转化为数字0;数字里的 Infinity-Infinity 都可以转化为对应的字符串,转化为布尔值时也是true。

  • 原始值转对象

    这里实际上就是使用基本函数的构造函数,加上 new 运算符,生成一个对象。这里又几个巧妙的用法,比如数值可以用 toString() 方法进行快速的进制转换;在我前几日写过的一篇小文:JavaScript绝句小研究中,绝句6中也展示了一个用 toFixed() 函数进行位数截取的功能。

  • 对象转原始值

    对于对象转化为布尔值而言,比较简单:均为true(这一点和python又有不同,比如空数组和空对象,在JS中也视为true)。对于转化为数值和字符串:根据犀牛书中所说,所有的对象都继承了两个方法来进行到原始值的转换,一个是 toString ,一个是 valueOf,这两个方法顾名思义,一个是转化为字符串的,一个是转化为数值的。只是 toString 的方法强大一些,基本上什么都能转;valueOf 相比之下弱一些,唯一能完整转换的对象就是把 Date() 对象转化为对应的时间戳(绝句的第二个)。

    那么作者在面试中说的正确与否呢?犀牛书里说,这里分为三个部分,

    • 隐式转换:除Date外,统统是先 valueOf、再 toString(Date 在 +== 时优先转化为字串)。
    • 显式对象转数值:对象转数值的方法,先尝试 valueOf (如果能返回原始值就退出)、如果不行再尝试 toString ,否则NaN。
    • 显式对象转字符串:对象转字符串的过程恰好是反过来的,先尝试 toString (能返回原始值就退出),再 valueOf ,否则进行报错。

    我们可以实际写一个小例子测试一下,这里我们用数组来表示一个对象,观察其类型转换:

    Array.prototype.valueOf = function() {return 10}     // 重写valueOf函数
    Array.prototype.toString = function() {return '88'}  // 重写toString函数
    !![]        // 结果为true,任意对象转换为布尔值均为真
    [] + 1      // 结果为11,在隐式转换里,先valueOf,有原始值且能参与运算则直接返回
    [] + ''     // 结果为'10',隐式转换,虽然是要转换为字符串,但同样是先进行了valueOf
    Number([])  // 结果为10,显示转换,先进行valueOf
    String([])  // 结果为'88',显式转换,先进行toString
    Array.prototype.valueOf = null                       // 令valueOf失效
    [] + 1      // 结果为'881',隐式转换valueOf不行后转到toString,得到原始值字符串直接返回
    Number([])  // 同理,结果为88
    

所以实际上作者所说比较片面,实际上局限与隐式转换的情况——不过看面试官的提问,大概也就是要说道这点为止吧。经过上面的分析,其实有很多看起来头大的题目都可以迎刃而解,比如这道为什么 ++[[]][+[]]+[+[]] = 10?;但是在浏览 justjavac 的博客时还是发现了一个跟类型转换相关,但又有点蹊跷的题目:JavaScript中,{}+{}等于多少?,有兴趣的读者可以研究一下。