最近有空可以让我静下心来看看各种代码,function与感叹号的频繁出现,让我回想起2个月前我回杭州最后参加团队会议的时候,@西子剑影抛出的一样的问题:如果在function之前加上感叹号 (!) 会怎么样?比如下面的代码:
!function(){alert('iifksp')}() // true
在控制台运行后得到的值时true,为什么是true这很容易理解,因为这个匿名函数没有返回值,默认返回的就是undefined,求反的结果很自然的就是true。所以问题并不在于结果值,而是在于,为什么求反操作能够让一个匿名函数的自调变的合法?
平时我们可能对添加括号来调用匿名函数的方式更为习惯:
(function(){alert('iifksp')})() // true
或者:
(function(){alert('iifksp')}()) // true
虽然上述两者括号的位置不同,不过效果完全一样。
那么,是什么好处使得为数不少的人对这种叹号的方式情有独钟?如果只是为了节约一个字符未免太没有必要了,这样算来即使一个100K的库恐怕也节省不了多少空间。既然不是空间,那么就是说也许还有时间上的考量,事实很难说清,文章的最后有提到性能。
回到核心问题,为什么能这么做?甚至更为核心的问题是,为什么必须这么做?
其实无论是括号,还是感叹号,让整个语句合法做的事情只有一件,就是让一个函数声明语句变成了一个表达式。
function a(){alert('iifksp')} // undefined
这是一个函数声明,如果在这么一个声明后直接加上括号调用,解析器自然不会理解而报错:
function a(){alert('iifksp')}() // SyntaxError: unexpected_token
因为这样的代码混淆了函数声明和函数调用,以这种方式声明的函数a,就应该以 a(); 的方式调用。
但是括号则不同,它将一个函数声明转化成了一个表达式,解析器不再以函数声明的方式处理函数a,而是作为一个函数表达式处理,也因此只有在程序执行到函数a时它才能被访问。
所以,任何消除函数声明和函数表达式间歧义的方法,都可以被解析器正确识别。比如:
var i = function(){return 10}(); // undefined
1 && function(){return true}(); // true
1, function(){alert('iifksp')}(); // undefined
赋值,逻辑,甚至是逗号,各种操作符都可以告诉解析器,这个不是函数声明,它是个函数表达式。并且,对函数一元运算可以算的上是消除歧义最快的方式,感叹号只是其中之一,如果不在乎返回值,这些一元运算都是有效的:
!function(){alert('iifksp')}() // true
+function(){alert('iifksp')}() // NaN
-function(){alert('iifksp')}() // NaN
~function(){alert('iifksp')}() // -1
甚至下面这些关键字,都能很好的工作:
void function(){alert('iifksp')}() // undefined
new function(){alert('iifksp')}() // Object
delete function(){alert('iifksp')}() // true
最后,括号做的事情也是一样的,消除歧义才是它真正的工作,而不是把函数作为一个整体,所以无论括号括在声明上还是把整个函数都括在里面,都是合法的:
(function(){alert('iifksp')})() // undefined
(function(){alert('iifksp')}()) // undefined
说了这么多,实则在说的一些都是最为基础的概念——语句,表达式,表达式语句,这些概念如同指针与指针变量一样容易产生混淆。虽然这种混淆对编程无表征影响,但却是一块绊脚石随时可能因为它而头破血流。
最后讨论下性能。我在jsperf上简单建立了一个测试:http://jsperf.com/js-funcion-expression-speed,可以用不同浏览器访问,运行测试查看结果。我也同时将结果罗列如下表所示(由于我比较穷,测试配置有点丢人不过那也没办法:奔腾双核1.4G,2G内存,win7企业版):
!function(){;}() | 3,773,196 | 10,975,198 | 572,694 | 2,810,197 |
+function(){;}() | 21,553,847 | 12,135,960 | 572,694 | 1,812,238 |
-function(){;}() | 21,553,847 | 12,135,960 | 572,694 | 1,864,155 |
~function(){;}() | 3,551,136 | 3,651,652 | 572,694 | 1,876,002 |
(function(){;})() | 3,914,953 | 12,135,960 | 572,694 | 3,025,608 |
(function(){;}()) | 4,075,201 | 12,135,960 | 572,694 | 3,025,608 |
void function(){;}() | 4,030,756 | 12,135,960 | 572,694 | 3,025,608 |
new function(){;}() | 619,606 | 299,100 | 407,104 | 816,903 |
delete function(){;}() | 4,816,225 | 12,135,960 | 572,694 | 2,693,524 |
var i = function(){;}() | 4,984,774 | 12,135,960 | 565,982 | 2,602,630 |
1 && function(){;}() | 5,307,200 | 4,393,486 | 572,694 | 2,565,645 |
0 || function(){;}() | 5,000,000 | 4,406,035 | 572,694 | 2,490,128 |
1 & function(){;}() | 4,918,209 | 12,135,960 | 572,694 | 1,705,551 |
1 | function(){;}() | 4,859,802 | 12,135,960 | 572,694 | 1,612,372 |
1 ^ function(){;}() | 4,654,916 | 12,135,960 | 572,694 | 1,579,778 |
1, function(){;}() | 4,878,193 | 12,135,960 | 572,694 | 2,281,186 |
可见不同的方式产生的结果并不相同,而且,差别很大,因浏览器而异。
但我们还是可以从中找出很多共性:new方法永远最慢——这也是理所当然的。其它方面很多差距其实不大,但有一点可以肯定的是,感叹号并非最为理想的选择。反观传统的括号,在测试里表现始终很快,在大多数情况下比感叹号更快——所以平时我们常用的方式毫无问题,甚至可以说是最优的。加减号在chrome表现惊人,而且在其他浏览器下也普遍很快,相比感叹号效果更好。
当然这只是个简单测试,不能说明问题。但有些结论是有意义的:括号和加减号最优。
但是为什么这么多开发者钟情于感叹号?我觉得这只是一个习惯问题,它们之间的优劣完全可以忽略。一旦习惯了一种代码风格,那么这种约定会使得程序从混乱变得可读。如果习惯了感叹号,我不得不承认,它比括号有更好的可读性。我不用在阅读时留意括号的匹配,也不用在编写时粗心遗忘——
当我也这么干然后嚷嚷着这居然又节省了一个字符而沾沾自喜的时候,却忘了自己仓皇翻出一本卷边的C语言教科书的窘迫和荒唐……任何人都有忘记的时候,当再捡起来的时候,捡起的就已经不单单是忘掉的东西了。
2011-10-31更新:如果你使用aptana,那么在使用(!+-)时要注意一点,它们会让aptana的解析失效,导致Outline窗口没有任何显示。但是就代码本身而言,其运行没有任何问题。
相关推荐
- **$(document).ready(function(){...})**: 这是 jQuery 中的一个关键函数,确保页面完全加载后才执行其中的代码。这避免了因为元素尚未加载完毕而导致的错误。 - **$("button").click(function(){...})**: 为所有 ...
- `$(element).before(content)`:在元素外部的前面插入内容。 - `$(element).after(content)`:在元素外部的后面插入内容。 ### 3. 事件处理 - `$(element).click(function())`:为元素绑定点击事件。 - `$...
修改时,注意路径中不要包含中文字符、路径的分隔符必须是双杠符号! 修改、存盘后,将这个改好的rime.lua复制并覆盖替换小狼毫用户数据目录中的同名文件(默认目录在c:\Users\Administrator\AppData\Roaming\rime...
在正则表达式中,有一些基本的符号及其含义: - **字符类** - `\s`:匹配任何空白字符,包括空格、制表符、换页符等。 - `\S`:匹配任何非空白字符。 - `\d`:匹配数字(0-9)。 - `\D`:匹配非数字。 - `\w`:...
8. **排除特定符号的字符串:** `"[^%&,;=?$\x22]+"` - 用于验证不包含某些特殊字符(如%、&、,、;、=、?、$和")的字符串。 9. **仅包含中文字符:** `"^[\u4e00-\u9fa5]{0,}$"` - 用于验证一个字符串是否完全...
- `value`: 需要在前面添加符号的值。 **输出参数:** - `value`: 添加符号后的值。 **注意事项:** - 输入参数`value`应为字符类型的数据对象。 - 示例代码: ```abap DATA: i TYPE string, num TYPE i VALUE ...
正则表达式由一系列字符和特殊符号组成,用于描述字符串匹配的规则。以下是一些常见的正则表达式元素: - **元字符**:如`.`匹配任何单个字符;`*`表示前面的元素出现零次或多次;`+`表示前面的元素至少出现一次。 ...
在深入探讨具体的应用之前,先了解一些正则表达式的常用符号和概念是非常有帮助的: - **`.`**:匹配任何单个字符。 - **`^`**:表示字符串的开始位置。 - **`$`**:表示字符串的结束位置。 - **`*`**:零次或多次...
- 变量定义:在PHP中,变量名前面必须加上美元符号($)。 - 全局变量:如果希望在一个函数内部访问一个全局变量,可以通过 `global` 关键字来声明。 ```php $a = 3; $b = 4; function test() { global $a, $...
- **量词**:如`{n,m}`表示匹配前面的字符至少出现n次,最多出现m次。 - **分组**:使用圆括号`()`来分组,有助于实现更复杂的匹配逻辑。 #### 二、具体应用场景及正则表达式实现 1. **校验是否全由数字组成** `...
`符号表示前面的整个组(协议头加冒号双斜杠)是可选的。 2. **处理用户名和密码**: ```javascript "([([0-9a-z_!~*'().&=+$%-]+:)?[0-9a-z_!~*'().&=+$%-]+@)?"; ``` - 此处用于匹配可选的用户名和密码部分...
- `{1,20}` 表示匹配前面的模式至少1次,最多20次。 - `$` 表示字符串的结束。 - 使用`test()`方法而非`exec()`更简洁。 ##### 2. 校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串 ```...
- **综合电话号码**:`"((\d{3}-\d{8}|\d{3,4}-\d{7}){1})|(((\d+-)?(\d{4}-?\d{7}|\d{3}-?\d{8}|^\d{7,8})(-\d+)?){1})"`。这个正则表达式综合了固定电话和手机号码的验证。 ##### 4. Email地址验证 邮箱地址通常...
在JavaScript编程语言中,去除字符串中的特殊字符及标点符号是一项常见的需求,尤其是在处理用户输入、文本解析或数据清洗等场景下。本文将详细介绍如何利用正则表达式及其他方法实现这一功能,并探讨其应用场景与...
#### 一、正则表达式的概述与基础符号 正则表达式是一种强大的文本处理工具,在文本搜索、替换等操作中非常实用。下面是一些常见的正则表达式基础符号及其含义: 1. **`.` (点)**:匹配任意一个字符(除了换行符)...
- **元字符**:如 `.`(匹配任何单个字符)、`*`(匹配零次或多次前面的子表达式)、`+`(匹配一次或多次前面的子表达式)、`?`(匹配零次或一次前面的子表达式)、`[]`(字符集合,匹配其中任意一个字符)、`()`...