[Effective JavaScript 笔记] 第6条:了解分号插入的局限
分号可以省略
js可以在语句结束不强制加分号。(建议还是添加,不添加分号往往会出现不易发现的BUG)
function Point(x,y){
this.x=x||0;
this.y=y||0;
}
Point.prototype.isOrigin=function(){
return this.x===0 && this.y===0
}
上面代码可以运行,是由于js可以自动插入分号,它是一种程序解析技术。能推断出某些上下文中省略的分号,然后有效地自动地将分号“插入”到程序中。
ECMAScript标准细心地制定了分号插入机制,可以方便移植。
分号插入的陷阱
不能避免学习其规则,受分号插入的影响,js语法有一些额外的限制。
分号插入的原则
第一条原则
分号仅在}标记之前、一个或多个换行之后和程序输入的结尾被插入。
讲人话就是,你只能在一行、一个代码块和一段程序结束的地方省略分号。
function square(x){
var n=+x
return n*n
}
function area(r){r=+r;return Math.PI*r*r}
function add1(x){return x+1}
不合法的
function area(x){r=+r return Math.PI*r*r}
第二条原则
分号仅在随后的输入标记不能解析时插入。
人话:分号插入是一种错误校正规则。
a=b
(f());
能解析为一条单独的语句,等价于:
a=b(f());
没有分号插入,因为b(f())是一个合法的语句
a=b
f();
会解析为两条独立的语句
a=b f();
解析有误。
这条说明你省略分号的时候一定要小心。
有问题的字符
5个明确有问题的字符需要密切注意:(、[、+、/。
每个字符都能作为一个表达式运算符或一条语句的前缀,依赖于上下文。
小心以表达式结束的语句,如赋值语句。下一行以上面这5个字符的其中之一开始,不插入分号。
出问题最好的是以(或[开头的语句。
a=b
[“r”,”g”,”b”].forEach(function(key){
bakcground[key]=foreground[key]/2;
});
这句话会被等价于
a=b[“r”,”g”,”b”].forEach(function(key){
bakcground[key]=foreground[key]/2;
});
注意:中括号表达式有点怪,js允许逗号分隔表达式,常用于声明多个变量,逗号分隔符用于赋值时,操作符总会返回最后一个表达式的值。

+、-、/字符出现在语句开始并不常见,但也有这种情况。
+号:

10是立即运行的结果
NaN是+undefined的结果
/号:出现在语句的开始实际不是一个入口标记,而是正则表达式

/Error/i.test(str)&&fail();
(&&是一种短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。)
如果/Error/i.test(str)结果为真,才会执行fail()操作。
但如果情况如下
a=b
/Error/i.test(str)&&fail();
会被解析为
a=b/Error/i.test(str)&&fail();
/被当成除法运算符了。
想省略分号,可以在语句后跟一个声明,以保证语句不会被错误解析。
a=b
var x
(f())
重构时可能被人改为
var x
a=b
(f())
虽然上面把var语句提前,这两段代码应该是等价的。但事实上,b后面跟着的一个括号,会被错误地解析为
var x;
a=b(f());
结果,你还是要小心省略分号的地方,检查接下来的一行开始的标记是否会禁止自动插入分号。
或者在(、[、+、/字符的开始前置加一个额外的分号语句的方法。
a=b
var x
;(f())
现在把var提前也不会出错了。
var x
a=b
;(f())
另外一个常见的情况是,省略分号可能导致脚本连接问题。每个文件可能由大量的函数表达式组成。
file1.js
(function(){
//…..
})()
file2.js
(function(){
//…..
})()
当合并压缩时,结果被视为
(function(){
//….
})()(function(){
//….
})();
省略分号时,不仅要当心当前文件的下一个标记,而且要当心脚本合并时可能出现在语句之后的任一标记。
可以防御性在加一个前缀的分号以保护脚本免受合并的影响。
file1.js
;(function(){
//…..
})()
file2.js
;(function(){
//…..
})()
合并后
;(function(){
//….
})();(function(){
//….
})();
脚本正常运行。
那么不省略任何分号,是不是就没有问题了呢?也不尽然,如
在打
return {};
时打成
return
{};
没有返回一个对象,因为上面这句被解析成
return ;
{}
;
3条单独的语句。
这就是所谓的js语法限制产生式,它不允许在两个字符之间出现换行。如果存在换行,可能会被自动插入分号。
如上面的return 语句。在return关键字和其可选参数之间一定不能出现换行.
其它的限制产生式:
- throw语句
- 带有显式标签的break或continue语句
- 后置自增或自减运算符
最后一条是为了消除以下情况
a
++
b
会被解析为
a;++b;
第三条规则
分号不会作为分隔符在for循环空语句的头部被自动插入。
人话:你必须在for循环的头部显式地包含分号。
for(var i=0,len=20
i<len
i++
){
//…
}
上面会出现语法错误。

空循环的while同样也需要显式的分号。否则也会报错
function infiniteLoop(){while(true)}

必须写成
function infiniteLoop(){while(true);}

提示
- 仅在“}”标记之前、一行的结束和程序的结束处自动插入分号
- 仅在紧接着的标记不能被解析的时候插入分号
- 在以(、[、+、-、/字符开头的语句前绝不能省略分号
- 当脚本文件进行连接时,在脚本开头加上防御性的分号
- 在return、throw、 break 、continue、 ++、 --的参数之前绝不能换行
- 分号不能作为for循环的头部或空语句的分隔符而自动插入

[Effective JavaScript 笔记] 第6条:了解分号插入的局限的更多相关文章
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合
对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...
- [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染
之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
- [Effective JavaScript 笔记]第66条:使用计数器来执行并行操作
第63条建议使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串.downloadAllAsync并不只有清理嵌套回 ...
随机推荐
- 怎样写 OpenStack Neutron 的 Extension (一)
前两篇文章讨论了怎么写一个 Neutron 的插件.但是最基本的插件只包括 Network, Port,和 Subnet 三种资源.如果需要引入新的资源,比如一个二层的 gateway 的话,就需要在 ...
- IE8/9的console之坑
这几天遇到个深坑,在改别人代码时,发现ajax在ie8下请求不成功.清理了缓存后,可以请求成功!(清理缓存只是表象而已,后文说原因) 后来慢慢看代码,发现ajax成功回调了!在success回调里,我 ...
- Object C学习笔记14-分类(category)
在.NET中有一个非常带劲的特性,那就是扩展方法. 扩展方法使你能够向现有类型“添加”方法(包括你自定义的类型和对象噢),而无需创建新的派生类型.重新编译或以其他方式修改原始类型.扩展方法是一种特殊的 ...
- AngularJS开发指南13:AngularJS的过滤器详解
AngularJS过滤器是用来格式化输出数据的.除了格式化数据,过滤器还能修改DOM.这使得过滤器通常用来做些如“适时的给输出加入CSS样式”等工作. 比如,你可能有些数据在输出之前需要根据进行本地化 ...
- hibernate事务
hibernate事务 9.3 Hibernate的事务管理 事务(Transaction)是工作中的基本逻辑单位,可以用于确保数据库能够被正确修改,避免数据只修改了一部分而导致数据不完整,或者在修改 ...
- python IO文件处理
python的文件读写操作符有:r w a r+ w+ rb wb 除了以file的方式打开文件,还有一种方式就是open了,两个的用法是一模一样的,可以看成open就是file的别名 下面这个表格是 ...
- Vijos p1892 树上的最大匹配 树形DP+计数 被卡常我有特殊技巧heheda
https://vijos.org/p/1892 此题需要手动开栈: <<; //256MB char *p=(char*)malloc(size)+size; __asm__(" ...
- 本地的手机号码归属地查询-oracle数据
最近做的项目中,有个功能是手机归属地查询,因为项目要在内网下运行,所以不能用提供的webservice,只好在网上找手机归属地的数据,很多都是access的,我们的项目是用oracle,只好自己转吧, ...
- struts2理解
(1) Struts2(一)---struts2的环境搭建及实例 (2) struts2(二)---ModelDriven模型驱动 (3) Struts2属性驱动与模型驱动 (4)
- git使用记录
唔,git有本地版本管理功能,所以,这个完全是可以拿来自己做版本管理的.所以有必要学习一下,另外,在oschina上开了个账户,用来管理自己一些代码,也是增加自己学习git的动力. 1. 使用clon ...