热身环节

在讲述接下来的内容之前,话不多说,让我们先来做一道题练练手。

请思考下列 JS 语句输出结果是什么?并复制粘贴进浏览器控制台中运行验证。

 let foo = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]let bar = foo[1, 2, 1, 2].forEach(item => console.log(item))

如果输出和你预期中的一样,那么恭喜你,你的 JS 基础非常扎实。

如果输出与你预期中的不一样,不用沮丧,大多数人的预期都和你一样。这个时候你可以试着把这段代码复制进你的 JS 文件中保存,然后再用 Node 环境去运行这个 JS 文件,输出可能又和你最开始的预期一样。造成这种输出的不同,并不是 Node 环境和浏览器运行环境的不同,而是可能当你在用 Vscode 保存 JS 代码时自动格式化工具帮你悄悄加上了分号。

 //Let's say it together:Thank you Auto Formatter!!!let foo = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];let bar = foo;[1, 2, 1, 2].forEach(v => console.log(v));

上面的题里其实包含了两个小知识,自动分号补全逗号操作符

Tips: 以下的两个知识点的标题和相关引用均有超链接跳转,可点击跳转原文

自动分号补全

当一行语句没有句末分号的时候,JS 解析器会尽量把新的一行纳入当前行解释,只有符合自动分号补全规则才会在合适的位置自动补全分号。当不符合自动分号补全规则时,警惕句首以 [ ( + - * ` 这六种特殊字符开头,这时候很有可能会被当做上一行的一部分来解析。

  1. 当新的一行并入当前行将构成非法语句不能正确解析时,将自动插入分号。
  2. 当新行以 } 开头时,即代码块的结束位置,将自动插入分号。
  3. 当以 return 语句结束时,在行末自动插入分号。
  4. 当以 break 语句结束时, 在行末自动插入分号。
  5. 当以 throw 语句结束时,在行末自动插入分号。
  6. 当以 continue 语句结束时,在行末自动插入分号。
  7. 当以 ES6 的 yield 语句结尾时,在行末自动插入分号。
  8. ++-- 后缀表达式作为新行的开始,在行首自动插入分号。
  9. 源代码文件末尾自动插入分号。

----以上规则引用自JS分号的一些细节

下面是一个JS解析器解析的简单例子:

 let            // 当JS读到这行时并不会停止,而是继续往下。foo = 'foo'    // 和上面组成let foo = 'foo' 一句完整语句,进行自动分号补全,let bar = 'bar'// 在末尾同样插入分号/*等同于*/let foo = 'foo';let bar = 'bar';

这个时候回头看第一个例子会发现代码会被这么解释:

 let foo = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]let bar = foo[1, 2, 1, 2, 1, 2].forEach(v => console.log(v))/*等同于*/let foo = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];let bar = foo[1, 2, 1, 2].forEach(v => console.log(v));//即:[[1,2,3],[4,5,6],[7,8,9]][1,2,1,2].forEach(v => console.log(v));

简单了解完自动分号补全机制可以跳转第二个知识点:逗号操作符

逗号操作符

逗号操作符 对它的每个操作数求值(从左到右),并返回最后一个操作数的值。 ---- MDN

请在控制台中输入如下的例子帮助理解:

 (1,2,3,4,5);  // 输出:5 返回逗号分隔的最后一个数5[2,4,6,8,10][0];  // 输出:2 这个很容易理解,数组的index为0的位置是2[2,4,6,8,10][0,1]; // 输出:4 和上面不同的是,后一个中括号里有两个数,逗号操作符让0,1返回1,最终输出 4

当你两个数组字面量连在一起的时候,后一个会被当做数组的属性访问来解释。

两个知识点结合起来之后,我们就可以明白第一个例子到底发生了什么。

 // 第一个例子的最终解释版本// 即:[[1,2,3],[4,5,6],[7,8,9]][1,2,1,2].forEach(v => console.log(v))// 即:[[1,2,3],[4,5,6],[7,8,9]][2].forEach(v => console.log(v))// 即:[7,8,9].forEach(v => console.log(v))

发生场景

上面所举的例子是可以运行的,不会在控制台里面报错并中断进程。当发生突破预期行为时,不大容易被识别出来。当没加分号的时候,我们在平常开发中有可能会遇到如下的场景:

  1. 句首以 [ 或是 ( 开头时,控制台会出现报错 Cannot read properties of undefined (reading ‘forEach’) 或者 xx is not a function。前一个是因为句首的 [ 会把上一个变量当做对象进行属性访问,紧跟着的数组方法就可能在该属性里面找不到了,后一个是因为句首的 ( 会把上一个变量当做函数进行函数调用。

  2. 句首以 + - * 开头时,该行可能会和上一行一起当做 运算符 计算进行解析,期间还可能会发生 隐式转换,不过句首以 + - * 开头的场景非常罕见。

  3. 句首以 ` 开头时,它跟 ( 一样,会和上一行的变量组成函数调用,使用模板字符串调用参数的函数被称作是标签函数(Tag Functions)

     //Simple Examplelet foo = "hello!"function consoleFun(x) {console.log(x)}consoleFun(foo)     //输出: hello world!consoleFun`${foo}`  //输出: ['', '']
    

    糟糕!有些奇怪的事情发生了。首先奇怪的是它可以以这种形式调用,其次输出的结果很奇怪。简略的解释的话:就是当以标签函数的形式调用时,函数第一个参数会接收到该模板字符串用 “${ 变量名 }” split 而成的数组,然后剩余的参数再依次接收 “${ 变量名 }” 所代表变量的值。当然这个解释不是清晰易懂,如同费马所言:

    我有一个绝妙的解释方法,但是空白处太少,我写不下 。

    值得庆幸的是,句首用 反引号 ` 开头的同样很少见,我们可以先记住一个结论,函数也可以用模板字符串直接调用。关于 标签函数的解释,有机会以后可以展开讨论一下。

灵异事件?

在我进行刷题的时候,不管在牛客还是力扣,都曾遇过相同的情形,代码逻辑没写错,但是自测时语法报错了。

例如在牛客网,它的题目需要你自己来处理输入输出。它提供了一个 readline 函数来让你读取输入,然后你可以使用 console 或者 print 来对结果进行输出。

 // 问题:统计字符出现的个数,并按照规定格式输出。// 示例输入: 'abbcd' 。// 示例输出: 'a:1 b:2 c:1 d:1'。let hash = {}let str = readline()//即let str = ‘abbcd’[...str].forEach(v => hash[v] = hash[v] ? hash[v] + 1 : 1)console.log(Object.keys(hash).map(key => `${key}:${hash[key]}`).join(' '))

一般来说,当你在接收到一个字符串之后,你会把它转换成数组。这样你就可以来执行一些遍历操作。

当我书写完上列代码,点击自测运行时,控制台提示 SyntaxError: Unexpected token …

这时候就会有点怀疑人生了,脑子冒出了一万种想法:难道字符串不可迭代?难道展开运算符写错了?在网上一通查询无果,换了个 Api 执行来实现相同功能,稀里糊涂就过去了。最后自我安慰:当排除一切不可能的,剩下的即使再不可能,那也是真相 。那就是一定是有灵异事件发生了!!!

 //.split('')和[...str]在这里可实现相同的功能str.split('').forEach(v => hash[v] = hash[v] ? hash[v] + 1 : 1)

现在回头再来看的话,我们就会发现,其实出问题的那行代码被解析成:

 let str = readline()[...str].forEach(v => hash[v] = hash[v]? hash[v] + 1 : 1)

然后我们查阅下 MDN 关于展开语法的定义:

展开语法 (Spread syntax), 可以在函数调用/数组构造时,将数组表达式或者 string 在语法层面展开;还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开。(译者注: 字面量一般指 [1, 2, 3] 或者 {name: "mdn"} 这种简洁的构造方式)

因为作为属性访问的时候,中括号里面是不支持展开语法的。所以控制台才会报出语法错误,看来计算机里面的的确确是没有黑魔法的,一切看起来不可思议的东西,很有可能只是自己的知识的局限性。

规避措施

正如代码缩进的大小是两个字符还是四个字符在网上引发持续的争论,加不加分号也在前端的圈子里也是如此。站在天平的两端的人们都互认对方是异端,我们今天不去讨论哪种方式是正确的,而是给大家介绍现成的两种解决方案。

  1. 使用格式化工具

    我们可以使用诸如 PrettierVeturBeautify 这一类代码格式化插件对代码进行格式化,甚至其实可以使用 Vscode 默认自带的格式化程序对代码进行格式化。设置一下关于分号的配置,以及在代码保存时进行自动格式化,就可以很容易的避免出现上述的问题。

    你可以在项目根目录创建.prettierrc文件,写入下列配置,便可在句末自动插入分号。

     {"semi": true, }
    

    或者你也可以在 Vscodesetting.json 文件中进行配置,好处是切换项目依然可以有效格式化文件。不过记住一个点项目里面的配置优先级高于编辑器的配置。

  2. 使用句首分号

    另外一部分人觉得其实没必要加句末分号,我们只需要在句首加上防御性分号即可,会引发异常行为的字符其实不多,当句首出现上述的六个字符,只需在句首加上防御性分号即可。

    下面是我根据这个规则,在第三行句首加上分号,运行之后也获得正确的结果。

     let hash={}let str =readline();[...str].forEach(v=>hash[v]=hash[v]?hash[v]+1:1)console.log(Object.keys(hash).map(key=>`${key}:${hash[key]}`).join(' '))
    

    但其实你也可以使用上述工具来帮你自动格式化,比如在上面的配置项把 semi 的值改成 false ,这样就会仅添加必要的句首分号。

这两种方案都有效地规避了没有加句末分号时引发的异常行为,大家可以各取所需。

JS的句末分号:你可能不了解的事实相关推荐

  1. then在c语言中什么意思,then 放在句末可以表示”然后“的意思吗?

    一般不会放在句末表然后的意思.但是在口语中不会太遵循固定语法,有时也会放在句末表示然后.Then的用法如下: 副词 ad. 1.那时,当时 He worked in that factory then ...

  2. js 四句话搞懂原型和原型链

    一.概念 当试图查找实例对象的某个属性时,不仅仅会在实例类上查找,还会在这个类的父亲爷爷祖爷爷,层层向上,直到查找到第一个名称相同的属性或者到达一个空的原型,这个就是javascript的原型链机制. ...

  3. js一句代码算出二叉树深度

    输入一棵二叉树的根节点,求该树的深度.从根节点到叶节点依次经过的节点(含根.叶节点)形成树的一条路径,最长路径的长度为树的深度. 例如: 给定二叉树 [3,9,20,null,null,15,7], ...

  4. 使用JSLint提高JS代码质量

    随着富 Web 前端应用的出现,开发人员不得不重新审视并重视 JavaScript 语言的能力和使用,抛弃过去那种只靠"复制 / 粘贴"常用脚本完成简单前端任务的模式.JavaSc ...

  5. VScode 结局插件prettier和vetur格式化冲突

    先上配置代码 {"workbench.iconTheme": "vscode-icons","workbench.startupEditor" ...

  6. python是c语言_python与c语言

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! python语言调用c语言进行扩展,或者增加程序的运行速度都是特别方便的. 同时 ...

  7. 新一代记事本“Notepad++”个性化设置备份

    Notepad++是一套非常有特色的自由软件的纯文字编辑器(许可证:GPL),有完整的中文化接口及支援多国语言撰写的功能(UTF8 技术).它的功能比 Windows 中的 Notepad(记事簿)强 ...

  8. 分享个人建站的一点经验

    对于一个接触过Web开发的IT人来说,一般都考虑过创建属于自己的网站,可能是定制自己特有风格的博客类网站,可能是私密的个人主页,也可能是展示自己开源工具的网站,当然,酝酿着做个商业网站来创业的人肯定也 ...

  9. 入门科普:Python、R、大数据、云计算最全学习资源都在这里

    导读:本文写给有抱负的新兴数据科学家.知道各种专业知识的程序员,还有那些不懂任何编程技巧的初学者.本文提供了简单的教程和可实践的分析,而不是理论.我还试图将Python与R结合起来,为学习者提供对比的 ...

最新文章

  1. Unix Linux大学教程(三):过滤器、正则表达式、vi
  2. R语言使用scales包的hue_pal函数获取ggplot2任何级别的离散色码、使用scales包的hue_pal函数获取ggplot2任何级别的反序(reverse)离散色码
  3. “学霸”是怎样炼成的?
  4. python 100实例_[Python] Python 100例
  5. 大学c语言第三章作业,c语言程序设计一章部分和第三章习题答案.doc
  6. 博文视点新书样章下载
  7. CMMI认证是什么,级别分类有哪些?
  8. ai故障风字体_AI教程!3步搞定酷炫故障文字效果
  9. 计算机硬件维修的步骤和方法,计算机硬件维护的具体方法
  10. PCAN-View 软件添加111K波特率选项
  11. 瓦楞机自动排单技术收藏
  12. Win10笔记本开启热点让手机上网
  13. asp+access实现增删改查
  14. 正式入职开发工程师工作近半年有感
  15. 傅里叶级数用matlab,傅里叶级数展开matlab实现
  16. 使用docker安装mysql5.7
  17. Unity WebGL网页背景透明化(2021更新)
  18. PHP获取网页返回的JSON数据并在微信换行展示
  19. php 公众号验证回调方法_微信公众号运营的技巧和方法?
  20. Excel如何快速合并相同内容单元格

热门文章

  1. JavaScript 18 JavaScript 字符串
  2. vue项目 在style标签中引入css,less,sass的方法
  3. 公安警务综合系统业务整合解决方案
  4. SpringSecurity - 基于 Servlet 的应用程序
  5. GC是什么,为什么要有GC?
  6. 基于Java企业财务管理系统的设计与实现(论文+源码)_kaic
  7. linux加电脑白名单命令,Linux的EMOS邮件系统的白名单操作的一些命令
  8. 如何将 Java 对象转换为 JSON?
  9. 你看有一只大黑狗,扑过来了……(拜托请启动快一点)
  10. php微信小程序毕业设计 php校园跑腿小程序毕业设计毕设作品参考