用js库过程中偶然发现他们代码中有很多类似于如下的情形:
于是就试了一下:
在控制台运行后得到的值时true,为什么是true这很容易理解,因为这个匿名函数没有返回值,默认返回的就是undefined,求反的结果很自然的就是true。所以问题并不在于结果值,而是在于,为什么求反操作能够让一个匿名函数的自调变的合法?
function ($) {} 这是一个函数的声明,当我们需要让它立即执行,就需要使用 () 操作符将函数调用,而 () 操作符需要前面是一个表达式,通常,我们会使用一对圆括号将匿名函数声明转换成表达式。所以,我们通常是这么写的:
虽然上述两者括号的位置不同,不过效果完全一样。
那么,是什么好处使得为数不少的人对这种叹号的方式情有独钟?如果只是为了节约一个字符未免太没有必要了,这样算来即使一个100K的库恐怕也节省不了多少空间。既然不是空间,那么就是说也许还有时间上的考量,事实很难说清,文章的最后有提到性能。
回到核心问题,为什么能这么做?甚至更为核心的问题是,为什么必须这么做?
其实无论是括号,还是感叹号,让整个语句合法做的事情只有一件,就是让一个函数声明语句变成了一个表达式。
这是一个函数声明,如果在这么一个声明后直接加上括号调用,解析器自然不会理解而报错:
因为这样的代码混淆了函数声明和函数调用,以这种方式声明的函数a,就应该以 a(); 的方式调用。
但是括号则不同,它将一个函数声明转化成了一个表达式,解析器不再以函数声明的方式处理函数a,而是作为一个函数表达式处理,也因此只有在程序执行到函数a时它才能被访问。
所以,任何消除函数声明和函数表达式间歧义的方法,都可以被解析器正确识别。比如:
赋值,逻辑,甚至是逗号,各种操作符都可以告诉解析器,这个不是函数声明,它是个函数表达式。至于为什么可以这么做呢,那是因为 () 操作符比 ! 操作符的优先级要高两级,
[参见]
。并且,对函数一元运算可以算的上是消除歧义最快的方式,感叹号只是其中之一,如果不在乎返回值,这些一元运算都是有效的:
甚至下面这些关键字,都能很好的工作:
最后,括号做的事情也是一样的,消除歧义才是它真正的工作,而不是把函数作为一个整体,所以无论括号括在声明上还是把整个函数都括在里面,都是合法的:
说了这么多,实则在说的一些都是最为基础的概念——语句,表达式,表达式语句,运算符优先级,这些概念如同指针与指针变量一样容易产生混淆。虽然这种混淆对编程无表征影响,但却是一块绊脚石随时可能因为它而头破血流。
1 | ! function ($) { |
2 | //do sth |
3 | }(window.jQuery); |
于是就试了一下:
1 | ! function (){alert( 'iifksp' )}() // true |
function ($) {} 这是一个函数的声明,当我们需要让它立即执行,就需要使用 () 操作符将函数调用,而 () 操作符需要前面是一个表达式,通常,我们会使用一对圆括号将匿名函数声明转换成表达式。所以,我们通常是这么写的:
1 | ( function (){alert( 'iifksp' )})() // true |
1 | ( function (){alert( 'iifksp' )}()) // true |
那么,是什么好处使得为数不少的人对这种叹号的方式情有独钟?如果只是为了节约一个字符未免太没有必要了,这样算来即使一个100K的库恐怕也节省不了多少空间。既然不是空间,那么就是说也许还有时间上的考量,事实很难说清,文章的最后有提到性能。
回到核心问题,为什么能这么做?甚至更为核心的问题是,为什么必须这么做?
其实无论是括号,还是感叹号,让整个语句合法做的事情只有一件,就是让一个函数声明语句变成了一个表达式。
1 | function a(){alert( 'iifksp' )} // undefined |
1 | function a(){alert( 'iifksp' )}() // SyntaxError: unexpected_token |
但是括号则不同,它将一个函数声明转化成了一个表达式,解析器不再以函数声明的方式处理函数a,而是作为一个函数表达式处理,也因此只有在程序执行到函数a时它才能被访问。
所以,任何消除函数声明和函数表达式间歧义的方法,都可以被解析器正确识别。比如:
1 | var i = function (){ return 10}(); // undefined |
2 | 1 && function (){ return true }(); // true |
3 | 1, function (){alert( 'iifksp' )}(); // undefined |
1 | ! function (){alert( 'iifksp' )}() // true |
2 | + function (){alert( 'iifksp' )}() // NaN |
3 | - function (){alert( 'iifksp' )}() // NaN |
4 | ~ function (){alert( 'iifksp' )}() // -1 |
1 | void function (){alert( 'iifksp' )}() // undefined |
2 | new function (){alert( 'iifksp' )}() // Object |
3 | delete function (){alert( 'iifksp' )}() // true |
1 | ( function (){alert( 'iifksp' )})() // undefined |
2 | ( function (){alert( 'iifksp' )}()) // undefined |