从一道面试题深入浅出JavaScript隐式转换的问题 您所在的位置:网站首页 hive类型隐式转换 从一道面试题深入浅出JavaScript隐式转换的问题

从一道面试题深入浅出JavaScript隐式转换的问题

2023-05-21 02:29| 来源: 网络整理| 查看: 265

声明:本文转载自github.com/jawil/blog/…

起因 ++[[]][+[]]+[+[]]==10?

上面这段代码的执行效果是啥呢?你可以先给出你的答案然后在浏览器输出验证一下,结果可能出乎意料。

通过这个题,我们本文会把js的隐式类型转换刨根问底的搞清楚了,也更加深入的明白了为什么JS是弱类型语言了。

题外话

一看就看出答案的大神可以跳过,鄙文会浪费你宝贵的时间,因为此文会很长,涉及到知识点很多很杂很细,以及对js源码的解读,而且很抽象,如果没有耐心,也可以直接跳过,本文记录本人探索这道问题所有的过程,会很长。

首先对于这种问题,有人说是闲的蛋疼,整天研究这些无聊的,有啥用,开发谁会这么写,你钻牛角尖搞这些有意思吗?

对于这种质疑,我只能说:爱看不看,反正不是写给你看。

当然,这话也没错,开发过程中确实不会这么写,但是我们要把开发和学习区分开来,很多人开发只为完成事情,不求最好,但求最快,能用就行😊。学习也是这样,停留在表面,会用API就行,不会去深层次思考原理,因此很难进一步提升,就是因为这样的态度才诞生了一大批一年经验重复三五年的API大神😄。

但是学习就不同,学习本生就是一个慢慢深入,寻根问底,追根溯源的过程,如果对于探寻问题的本质追求都没有,我只能说做做外包就好,探究这种问题对于开发确实没什么卵用,但是对我们了解JavaScript这门语言却有极大的帮助,可以让我们对这门语言的了解更上一个台阶。JavaScript为什么是弱类型语言,主要体现在哪里,弱类型转换的机制又是什么?

有人还是觉得其实这对学习JS也没什么多大卵用,我只能说:我就喜欢折腾,你管得着?反正我收获巨多就够了。

++[[]][+[]]+[+[]]===10?这个对不对,我们先不管,先来看几个稍微简单的例子,当做练习入手。

一、作业例子:

这几个是留给大家的作业,涉及到的知识点下面我会先一一写出来,为什么涉及这些知识点,因为我自己一步步踩坑踩过来的,所以知道涉及哪些坑,大家最后按照知识点一步一步分析,一定可以得出 答案来,列出知识点之后,我们再来一起分析++[[]][+[]]+[+[]]===10?的正确性。

{}+{}//chrome:"[object Object][object Object]",Firfox:NaN {}+[]//0 []+{}//"[object Object]"

首先,关于1、2和3这三个的答案我是有一些疑惑,先给出答案,希望大家看完这篇文章能和我讨论一下自己的想法,求同存异。

4.{}+1 5.({}+1) 6.1+{} 7.[]+1 8.1+[] 9.1-[] 10.1-{} 11.1-!{} 12.1+!{} 13.1+"2"+"2" 14.1+ +"2"+"2" 15.1++"2"+"2" 16.[]==![] 17.[]===![]

这几个例子是我随便写的,几乎包含了所有弱类型转换所遇到的坑,为什么会出现这种情况,就不得不从JS这门语言的特性讲起,大家都知道JS是一门动态的弱类型语言,那么你有没有想过什么叫做弱类型?什么叫做动态?大家都知道这个概念,但有没有进一步思考呢?

今天通过这几个例子就来了解一下JS的弱类型,什么是动态暂时不做探讨。

二、强弱类型的判别

按照计算机语言的类型系统的设计方式,可以分为强类型和弱类型两种。二者之间的区别,就在于计算时是否可以不同类型之间对使用者透明地隐式转换。从使用者的角度来看,如果一个语言可以隐式转换它的所有类型,那么它的变量、表达式等在参与运算时,即使类型不正确,也能通过隐式转换来得到正确地类型,这对使用者而言,就好像所有类型都能进行所有运算一样,所以这样的语言被称作弱类型。与此相对,强类型语言的类型之间不一定有隐式转换。

三、JS为什么是弱类型?

弱类型相对于强类型来说类型检查更不严格,比如说允许变量类型的隐式转换,允许强制类型转换等等。强类型语言一般不允许这么做。具体说明请看维基百科的说明。

根据强弱类型的判别定义,和上面的十几个例子已经充分说明JavaScript 是一门弱类型语言了。


先讲一讲一些概念,要想弄懂上面题目答案的原理,首先你要彻底弄懂以下的概念,有些时候对一些东西似懂非懂,其实就是对概念和规则没有弄透,弄透之后等会回过头对照就不难理解,不先了解透这些后面的真的不好理解,花点耐心看看,消化一下,最后串通梳理一下,一层一层的往下剥,答案迎刃而解。


 为了能够弄明白这种隐式转换是如何进行的,我们首先需要搞懂如下一些基础知识。如果没有耐心,直接跳到后面第四章4.8 小结我总结的几条结论,这里仅给想要一步步通过过程探寻结果的人看。





四、ECMAScript的运算符、{}解析、自动分号插入 4.1 ECMAScript 运算符优先级 运算符描述. [] ()字段访问、数组下标、函数调用以及表达式分组++ — - + ~ ! delete new typeof void一元运算符、返回数据类型、对象创建、未定义值* / %乘法、除法、取模+ - +加法、减法、字符串连接> >>>移位< >= instanceof小于、小于等于、大于、大于等于、instanceof== != === !==等于、不等于、严格相等、非严格相等&按位与按位异或&&逻辑与?:条件= oP=赋值、运算赋值,多重求值 4.2 ECMAScript 一元运算符(+、-)

一元运算符只有一个参数,即要操作的对象或值。它们是 ECMAScript 中最简单的运算符。

delete,void,--,++这里我们先不扯,免得越扯越多,防止之前博文的啰嗦,这里咋们只讲重点,有兴趣的可以看看w3school(点我查看)对这几个的详细讲解。

上面的例子我们一个一个看,看一个总结一个规则,基本规则上面例子几乎都包含了,如有遗漏,还望反馈补上。

这里我们只讲 一元加法 和 一元减法 :

我们先看看ECMAScript5规范(熟读规范,你会学到很多很多)对一元加法和一元减法的解读,我们翻到11.4.6和11.4.7。

其中涉及到几个ECMAScript定义的抽象操作,ToNumber(x),ToPrimitive(x)等等 下一章详细解答,下面出现的抽象定义也同理,先不管这个,有基础想深入了解可以提前熟读ECMAScript5规范(点击查看)。

规范本来就是抽象的东西,不太好懂不要紧,我们看看例子,这里的规范我们只当做一种依据来证明这些现象。

大多数人都熟悉一元加法和一元减法,它们在 ECMAScript 中的用法与您高中数学中学到的用法相同。 一元加法本质上对数字无任何影响:

var iNum = 20; iNum = +iNum;//注意不要和iNum += iNum搞混淆了; alert(iNum); //输出 "20"

尽管一元加法对数字无作用,但对字符串却有有趣的效果,会把字符串转换成数字。

var sNum = "20"; alert(typeof sNum); //输出 "string" var iNum = +sNum; alert(typeof iNum); //输出 "number"

这段代码把字符串 "20" 转换成真正的数字。当一元加法运算符对字符串进行操作时,它计算字符串的方式与 parseInt() 相似,主要的不同是只有对以 "0x" 开头的字符串(表示十六进制数字),一元运算符才能把它转换成十进制的值。因此,用一元加法转换 "010",得到的总是 10,而 "0xB" 将被转换成 11。

另一方面,一元减法就是对数值求负(例如把 20 转换成 -20):

var iNum = 20; iNum = -iNum; alert(iNum); //输出 "-20"

与一元加法运算符相似,一元减法运算符也会把字符串转换成近似的数字,此外还会对该值求负。例如:

var sNum = "20"; alert(typeof sNum); //输出 "string" var iNum = -sNum; alert(iNum); //输出 "-20" alert(typeof iNum); //输出 "number"

在上面的代码中,一元减法运算符将把字符串 "-20" 转换成 -20(一元减法运算符对十六进制和十进制的处理方式与一元加法运算符相似,只是它还会对该值求负)。

4.3 ECMAScript 加法运算符(+)

在多数程序设计语言中,加性运算符(即加号或减号)通常是最简单的数学运算符。 在 ECMAScript 中,加性运算符有大量的特殊行为。

我们还是先看看ECMAScript5规范(熟读规范,你会学到很多很多)对加号运算符 ( + )解读,我们翻到11.6.1。

前面读不懂不要紧,下一章节会为大家解读这些抽象词汇,大家不要慌,但是第七条看的懂吧, 这就是为什么1+"1"="11"而不等于2的原因 ,因为规范就是这样的,浏览器没有思维只会按部就班的执行规则,所以规则是这样定义的,所以最后的结果就是规则规定的结果,知道规则之后,对浏览器一切运行的结果都会豁然开朗,哦,原来是这样的啊。

在处理特殊值时,ECMAScript 中的加法也有一些特殊行为:

某个运算数是 NaN,那么结果为 NaN。 -Infinity 加 -Infinity,结果为 -Infinity。 Infinity 加 -Infinity,结果为 NaN。 +0 加 +0,结果为 +0。 -0 加 +0,结果为 +0。 -0 加 -0,结果为 -0。

不过,如果某个运算数是字符串,那么采用下列规则:

如果两个运算数都是字符串,把第二个字符串连接到第一个上。 如果只有一个运算数是字符串,把另一个运算数转换成字符串,结果是两个字符串连接成的字符串。

例如:

var result = 5 + 5; //两个数字 alert(result); //输出 "10" var result2 = 5 + "5"; //一个数字和一个字符串 alert(result); //输出 "55"

这段代码说明了加法运算符的两种模式之间的差别。正常情况下,5+5 等于 10(原始数值),如上述代码中前两行所示。不过,如果把一个运算数改为字符串 "5",那么结果将变为 "55"(原始的字符串值),因为另一个运算数也会被转换为字符串。

注意:为了避免 JavaScript 中的一种常见错误,在使用加法运算符时,一定要仔细检查运算数的数据类型

4.4 ECMAScript 减法运算符(-)

减法运算符(-),也是一个常用的运算符:

var iResult = 2 - 1;

减、乘和除没有加法特殊,都是一个性质,这里我们就单独解读减法运算符(-)

我们还是先看看ECMAScript5规范(熟读规范,你会学到很多很多)对减号运算符 ( - )解读,我们翻到11.6.2。

与加法运算符一样,在处理特殊值时,减法运算符也有一些特殊行为:

某个运算数是 NaN,那么结果为 NaN。 Infinity 减 Infinity,结果为 NaN。 -Infinity 减 -Infinity,结果为 NaN。 Infinity 减 -Infinity,结果为 Infinity。 -Infinity 减 Infinity,结果为 -Infinity。 +0 减 +0,结果为 +0。 -0 减 -0,结果为 -0。 +0 减 -0,结果为 +0。 某个运算符不是数字,那么结果为 NaN。

注释:如果运算数都是数字,那么执行常规的减法运算,并返回结果。





4.5 ECMAScript 前自增运算符(++)

直接从 C(和 Java)借用的两个运算符是前增量运算符和前减量运算符。 所谓前增量运算符,就是数值上加 1,形式是在变量前放两个加号(++):

var iNum = 10; ++iNum;

第二行代码把 iNum 增加到了 11,它实质上等价于:

var iNum = 10; iNum = iNum + 1;

我们还是先看看ECMAScript5规范(熟读规范,你会学到很多很多)对前自增运算符 ( ++ )解读,我们翻到11.4.4。

此图有坑,后面会说到,坑了我很久。。。

看不懂这些抽象函数和词汇也不要紧,想要深入了解可以通读ECMAScript5规范中文版,看几遍就熟悉了,第一次看见这些肯定一脸懵逼,这是什么玩意,我们只要明白++是干什么就行,这里不必去深究v8引擎怎么实现这个规范的。 至于

var a=1; console.log(a++);//1 var b=1; cosole.log(++b);//2

还弄不明白的该好好补习了,这里不在本文的知识点,也不去花篇幅讲解这些,这里我们只要明白一点: 所谓前增量运算符,就是数值上加 1 。

4.6 ECMAScript 自动分号(;)插入

尽管 JavaScript 有 C 的代码风格,但是它不强制要求在代码中使用分号,实际上可以省略它们。

JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。 因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。

4.6.1例子 var foo = function() { } // 解析错误,分号丢失 test()

自动插入分号,解析器重新解析。

var foo = function() { }; // 没有错误,解析继续 test() 4.6.2工作原理

下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。

(function(window, undefined) { function test(options) { log('testing!') (options.list || []).forEach(function(i) { }) options.value.test( 'long string to pass here', 'and another long string to pass' ) return { foo: function() {} } } window.test = test })(window) (function(window) { window.someLibrary = {} })(window)

下面是解析器"猜测"的结果。

(function(window, undefined) { function test(options) { // 没有插入分号,两行被合并为一行 log('testing!')(options.list || []).forEach(function(i) { }); //


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有