其中说到了 self-executing anonymous functions,有几种方式,最常见也比较容易理解的是:
还有一种方式为:
在这里开头要加上 ! 或者 ~ , - 和 +。(note:都是英文符号)
请问第二种方式可以执行是什么原因?为什么去掉符号就会有语法错误?
收藏•没有帮助•举报•作者保留权利
收藏•没有帮助•举报•作者保留权利
收藏•没有帮助•举报•作者保留权利
收藏•没有帮助•举报•作者保留权利
(function(){
//do something here;
})();
!function(){
// do something
}();
请问第二种方式可以执行是什么原因?为什么去掉符号就会有语法错误?
1 条评论
默认排序按时间排序
13 个回答
我来回答一下题主“为什么去掉符号就会有语法错误?”。
原因是,“function(){}()”并不是合法的EcmaScript字符串,而“!function(){}()”是。
我们从规范出发,看看EcmaScript的上下文无关文法。
参考《ECMA-262, 3rd edition, December 1999》。
ES3简单一些,ES6同理。
这几个产生式表明了以Program为根的语法树结构。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(1)首先,“function(){}()”无法归约为CallExpression。
失败的原因是,
Expression要想归约为ExpressionStatement,就不能以左大括号“{”或“function”开头。
(2)那么,我们只能认为“function(){}()”是由两部分构成的了。
把“funtion(){}”和“()”都分别向SourceElement归约。
然而,这又不行。
(3)“funtion(){}”无法归约为SourceElement。
因为,无论是先归约为Statement,还先归约为FunctionDeclaration都不行。
归约为Statement要求不能以function开头,(与上面的失败原因相同)
归约为FunctionDeclaration要求必须指定函数名。
(4)然而,我们又没有其他归约方法了,只能失败了。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
而加上符号以后,
例如“!function(){}()”,并不是以“function开头”的,
所以通过了Expression到ExpressionStatement的归约。
同理可以推导(function(){}()),(function(){})()
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
值得指出的是,
“function(){}()”就算指定了函数名,也不行。
因为后面的“()”同样不能归约为SourceElement。
而“function f(){}(1)”就是合法的,
“function f(){}”归约为FunctionDeclaration,
而“(1)”可以归约为Expression,视为括号运算符,或称为分组运算符。
原因是,“function(){}()”并不是合法的EcmaScript字符串,而“!function(){}()”是。
我们从规范出发,看看EcmaScript的上下文无关文法。
参考《ECMA-262, 3rd edition, December 1999》。
ES3简单一些,ES6同理。
Program :
SourceElements
SouceElements :
SouceElement
SouceElements SourceElement
SouceElement :
Statement
FunctionDeclaration
Statement :
Block
VariableStatement
EmptyStatement
ExpressionStatement
IfStatement
IterationStatement
ContinueStatement
BreakStatement
ReturnStatement
WithStatement
LabelledStatement
SwitchStatement
ThrowStatement
TryStatement
ExpressionStatement :
[lookahead ∉ {{, function}] Expression ;
FunctionDeclaration :
function Identifier ( FormalParameterList[opt] ) { FunctionBody }
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(1)首先,“function(){}()”无法归约为CallExpression。
CallExpression
<- LeftHandSideExpression
<- PostfixExpression
<- UnaryExpression
<- MultiplicativeExpression
<- AdditiveExpression
<- ShiftExpression
<- RelationalExpression
<- EqualityExpression
<- BitwiseANDExpression
<- BitwiseXORExpression
<- BitwiseORExpression
<- LogicalANDExpression
<- LogicalORExpression
<- ConditionalExpression
<- AssignmentExpression
<- Expression
[失败了] <- ExpressionStatement
<- Statement
<- SourceElement
<- SourceElements
<- Program
Expression要想归约为ExpressionStatement,就不能以左大括号“{”或“function”开头。
(2)那么,我们只能认为“function(){}()”是由两部分构成的了。
program
-> SourceElements
-> SourceElements SourceElement
-> SourceElement SourceElement
然而,这又不行。
(3)“funtion(){}”无法归约为SourceElement。
因为,无论是先归约为Statement,还先归约为FunctionDeclaration都不行。
归约为Statement要求不能以function开头,(与上面的失败原因相同)
归约为FunctionDeclaration要求必须指定函数名。
(4)然而,我们又没有其他归约方法了,只能失败了。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
而加上符号以后,
例如“!function(){}()”,并不是以“function开头”的,
所以通过了Expression到ExpressionStatement的归约。
!function(){}()
<- ! function Identifier[opt] ( FormalParameterList[opt] ) { FunctionBody } Arguments
<- ! FunctionExpression Arguments
<- ! MemberExpression Arguments
<- ! CallExpression
<- ! LeftHandSideExpression
<- ! PostfixExpression
<- ! UnaryExpression
<- UnaryExpression
<- MultiplicativeExpression
<- AdditiveExpression
<- ShiftExpression
<- RelationalExpression
<- EqualityExpression
<- BitwiseANDExpression
<- BitwiseXORExpression
<- BitwiseORExpression
<- LogicalANDExpression
<- LogicalORExpression
<- ConditionalExpression
<- AssignmentExpression
<- Expression
<- ExpressionStatement
<- Statement
<- SourceElement
<- SourceElements
<- Program
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
值得指出的是,
“function(){}()”就算指定了函数名,也不行。
因为后面的“()”同样不能归约为SourceElement。
而“function f(){}(1)”就是合法的,
“function f(){}”归约为FunctionDeclaration,
而“(1)”可以归约为Expression,视为括号运算符,或称为分组运算符。
(Expression)
<- PrimaryExpression
<- MemberExpression
<- NewExpression
<- LeftHandSideExpression
<- PostfixExpression
<- UnaryExpression
<- MultiplicativeExpression
<- AdditiveExpression
<- ShiftExpression
<- RelationalExpression
<- EqualityExpression
<- BitwiseANDExpression
<- BitwiseXORExpression
<- BitwiseORExpression
<- LogicalANDExpression
<- LogicalORExpression
<- ConditionalExpression
<- AssignmentExpression
<- Expression
长天之云前端开发话题优秀回答者head, body { content }
有些人的确会用「自执行的匿名函数」(self-executing anonymous function)这个术语,
但是 Ben Alman 推荐了一个更准确的叫法:「立即调用的函数表达式」(IIFE,
Immediately-Invoked Function Expression http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife )——虽然更长了点,我喜欢这个术语,缩写漂亮,含义更清晰。
@leo shawn 有个地方说错了——“Javascript不允许在函数声明的后面直接使用小括号”。
看看 Ben Alman 引用的 Dmitry A. Soshnikov 的例子如下:
我做了点小修改,你可以尝试运行一下。函数 f 的定义被提升了,但并没有被立即调用——后面一行相当于在花括号闭合后,加入分号断行了。
Ben Alman 已经解释得很清楚了,我再复述一下。
语法错误的两种原因:
1) function (){ }()
期望是立即调用一个匿名函数表达式,结果是进行了函数声明,函数声明必须要有标识符做为函数名称。
2) function g(){ }()
期望是立即调用一个具名函数表达式,结果是声明了函数 g。末尾的括号作为分组运算符,必须要提供表达式做为参数。
所以那些匿名函数附近使用括号或一些一元运算符的惯用法,就是来引导解析器,指明运算符附近是一个表达式。
按照这个理解,可以举出五类,超过十几种的让匿名函数表达式立即调用的写法:
另外值得再次注意的是,括号的含混使用——它可以用来执行一个函数,还可以做为分组运算符来对表达式求值。
比如使用圆括号或方括号的话,可以在行首加一个分号,避免被用做函数执行或下标运算:
但是 Ben Alman 推荐了一个更准确的叫法:「立即调用的函数表达式」(IIFE,
Immediately-Invoked Function Expression http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife )——虽然更长了点,我喜欢这个术语,缩写漂亮,含义更清晰。
@leo shawn 有个地方说错了——“Javascript不允许在函数声明的后面直接使用小括号”。
看看 Ben Alman 引用的 Dmitry A. Soshnikov 的例子如下:
console.log( typeof f )
function f(){ console.log('called') }(1)
我做了点小修改,你可以尝试运行一下。函数 f 的定义被提升了,但并没有被立即调用——后面一行相当于在花括号闭合后,加入分号断行了。
Ben Alman 已经解释得很清楚了,我再复述一下。
语法错误的两种原因:
1) function (){ }()
期望是立即调用一个匿名函数表达式,结果是进行了函数声明,函数声明必须要有标识符做为函数名称。
2) function g(){ }()
期望是立即调用一个具名函数表达式,结果是声明了函数 g。末尾的括号作为分组运算符,必须要提供表达式做为参数。
所以那些匿名函数附近使用括号或一些一元运算符的惯用法,就是来引导解析器,指明运算符附近是一个表达式。
按照这个理解,可以举出五类,超过十几种的让匿名函数表达式立即调用的写法:
( function() {}() );
( function() {} )();
[ function() {}() ];
~ function() {}();
! function() {}();
+ function() {}();
- function() {}();
delete function() {}();
typeof function() {}();
void function() {}();
new function() {}();
new function() {};
var f = function() {}();
1, function() {}();
1 ^ function() {}();
1 > function() {}();
// ...
比如使用圆括号或方括号的话,可以在行首加一个分号,避免被用做函数执行或下标运算:
g()
// 可能放了几行注释——不知道是用自动化脚本合并的文件,还是哪里拷的函数。
;( function() {}() )
因为 JavaScript 文法明确规定表达式语句不得以 function 或者 { 开头(§12.4)
ExpressionStatement → [lookahead ∉ {{, function}] Expression;
接受@长天之云的纠正,附上英文原文的网址:
http://benalman.com/news/2010/11/immediately-invoked-function-expression/
弄清这个问题需要弄清函数表达式(function expression)和函数声明(function declaration)的区别:
函数表达式:
因此,执行匿名函数可以通过+,-,!,() 这样的形式来转化为函数表达式,就可以通过()来运行了。
匿名函数的好处在于:可以减少局部变量,以免污染现有的运行环境。jQuery等库都用到了这样的原理。
另外:通过+,-,!这三个符号运行的匿名函数比()运行的匿名函数可以减少一个字符的使用。
http://benalman.com/news/2010/11/immediately-invoked-function-expression/
弄清这个问题需要弄清函数表达式(function expression)和函数声明(function declaration)的区别:
函数表达式:
/* 函数表达式中的函数可以为匿名函数,也可以有函数名,但是该函数实际上不能直接使用,只能通过表达式左边的变量 a 来调用。*/函数声明:
var a = function(){
alert('Function expression');
}
var b = new a();
// 函数声明时必须有函数名而这段代码:
function a(){
alert('Function declaration');
}
a();
<script>实际上可以分为两个部分(黑体部分和斜体部分)。黑体部分的匿名函数通过一元操作符变成了函数表达式,因而可以通过 () 来调用。
~function() {
alert("hello, world.");
} ();
</script>
因此,执行匿名函数可以通过+,-,!,() 这样的形式来转化为函数表达式,就可以通过()来运行了。
匿名函数的好处在于:可以减少局部变量,以免污染现有的运行环境。jQuery等库都用到了这样的原理。
另外:通过+,-,!这三个符号运行的匿名函数比()运行的匿名函数可以减少一个字符的使用。
记住一种就够了
imChenJian我们可能不会觉得自己的代码写得多好,但…
回复tpanhh的评论: 1、函数声明必须有函数名,2、()之间的表达式不能为空,3、函数表达式可以匿名,4、函数声明表函数表达式
语句和表达式的区别
(js设计语句就是错误的,引入更多麻烦,一切皆表达式立刻少一大堆坑)
(js设计语句就是错误的,引入更多麻烦,一切皆表达式立刻少一大堆坑)
js中(function(){…})()立即执行函数写法理解
里面说得很详细了 看完你就懂了
里面说得很详细了 看完你就懂了
JavaScript的函数定义分为函数声明和函数表达式。
表达式语句不得以 function 或者 { 开头:
”裸写“成function fname() { }的,是函数声明,它不能是匿名的。
嵌套定义的函数也可以是函数声明。
函数作为表达式出现、或者作为其他表达式的一部分时才是函数表达式(有点绕。。),此时函数可以是匿名或者有名的。
比如赋值表达式的右边;()和[]里面;!等符号后面;return语句后面;。。
描述不同语法结构的语义时,主要和表达式的求值相关。比如赋值的时候需要对=右边的表达式求值,return的时候需要对return后面的求值。
函数表达式的值可以是一个函数对象,或者对这个函数对象进行调用(也就是执行它)产生的结果。
函数声明自身不能“求值”。
表达式语句不得以 function 或者 { 开头:
ExpressionStatement → [lookahead ∉ {{, function}] Expression;
”裸写“成function fname() { }的,是函数声明,它不能是匿名的。
var c = 1;
function test(x) {
console.log(x);
}
// 调用
test(x);
// innder、outer都是函数声明,只不过外界无法使用inner函数。。
function outer() {
var x = 1;
function inner() {
console.log("Hi");
}
}
函数作为表达式出现、或者作为其他表达式的一部分时才是函数表达式(有点绕。。),此时函数可以是匿名或者有名的。
比如赋值表达式的右边;()和[]里面;!等符号后面;return语句后面;。。
var foo = function() {
...
};
(function foo() {});
[Function bar() {}];
function outer() {
var x = 1;
return function inner() {
console.log(x);
}
}
描述不同语法结构的语义时,主要和表达式的求值相关。比如赋值的时候需要对=右边的表达式求值,return的时候需要对return后面的求值。
函数表达式的值可以是一个函数对象,或者对这个函数对象进行调用(也就是执行它)产生的结果。
// =右边表达式的”值“是一个函数对象,赋给t。不发生函数调用
var t = function () {
console.log(3);
};
// =右边表达式,先创建函数对象再调用它。t是5
var t = function (x) {
return x + 2;
}(3);
Immediately-invoked Function Expression(IIFE,立即调用函数),简单的理解就是定义完成函数之后立即执行。
比较常见的三种写法:
// Crockford's preference - parens on the inside
(function() {
console.log('Welcome to the Internet. Please follow me.');
}());
(function() {
console.log('Welcome to the Internet. Please follow me.');
})();
!function() {
console.log('Welcome to the Internet. Please follow me.');
}();
如果对于其返回值没有特别的要求的话,我们还可以这样写:
!function(){}(); // => true
~function(){}(); // => -1
+function(){}(); // => NaN
-function(){}(); // => NaN
或者也可以这样:
~(function(){})();
void function(){}();
true && function(){ /* code */ }();
15.0, function(){ /* code */ }();
甚至,还可以这样写:
new function(){ /* code */ }
31.new function(){ /* code */ }() //如果没有参数,最后的()就不需要了