盒子
盒子
文章目录
  1. 函数的定义
  2. 匿名函数
  3. 理论讲完了,讲点重点
  4. 神奇的感叹号

js匿名函数与感叹号

关于javascript函数定义……

函数的定义

首先简单介绍一下函数的定义,大致介绍常见的两种
第一种

1
2
3
function double(x){
return 2 * x;
}

第二种:

1
var double = function(x) { return 2* x; }

注意=右边的函数就是一个匿名函数,创造完毕函数后,又将该函数赋给了变量square。

匿名函数

上面所讲的定义square函数的方式即为匿名函数,这也是最常用的方式之一。
还有一种方式,可能也是初学者比较困惑的方式:

1
2
3
(function(x, y){
alert(x + y);
})(2, 3);

这里创建了一个匿名函数(在第一个括号内),第二个括号用于调用该匿名函数,并传入参数,然后函数顺次执行,并返回undefined

理论讲完了,讲点重点

我之前看到别人这些写匿名函数很是困惑,匿名函数既然定义了之后就执行函数体,为什么不直接写代码呢,后来网上查阅了下,别人说的理由如下:

  • 避免占用全局变量名
  • 参数保护,函数是独立作用域,传递参数可以保护临时变量,闭包可以保存循环中需要保留的临时变量,还有组件开发时将命名空间传递到函数中用闭包保护起来,即使命名空间被后面的代码重置,原变量被闭包保护将仍然生存
  • 降低风险,因为没有变量名,中间代码又被闭包保护,js注入无法访问,减少被攻击风险

好吧,好处显而易见


神奇的感叹号

后来我又看到类似这样的代码!function(){alert('ohroot')}(),在函数定义前面加了个!,函数体内代码正常执行,返回值undefined,瞬间又达到了匿名函数的效果。

我们一般匿名函数长得像这样:
(function(){alert('ohroot')})()// true
或者:
(function(){alert('ohroot')}())// true

其实无论是括号,还是感叹号,让整个语句合法做的事情只有一件,就是让一个函数声明语句变成了一个表达式。

1
function a(){alert('ohroot')}// undefined

这是一个函数声明,如果在这么一个声明后直接加上括号调用,解析器自然不会理解而报错:

1
function a(){alert('ohroot')}()// SyntaxError: unexpected_token

因为这样的代码混淆了函数声明和函数调用,以这种方式声明的函数a,就应该以a();的方式调用。

但是括号则不同,它将一个函数声明转化成了一个表达式,解析器不再以函数声明的方式处理函数a,而是作为一个函数表达式处理,也因此只有在程序执行到函数a时它才能被访问。所以,任何消除函数声明和函数表达式间歧义的方法,都可以被解析器正确识别。比如:

1
2
3
var i = function(){return 10}();// undefined  
1 && function(){return true}();// true
1, function(){alert('ohroot')}();// undefined

赋值,逻辑,甚至是逗号,各种操作符都可以告诉解析器,这个不是函数声明,它是个函数表达式。并且,对函数一元运算可以算的上是消除歧义最快的方式,感叹号只是其中之一,如果不在乎返回值,这些一元运算都是有效的:

1
2
3
4
!function(){alert('ohroot')}()// true
+function(){alert('ohroot')}()// NaN
-function(){alert('ohroot')}()// NaN
~function(){alert('ohroot')}()// -1

甚至下面这些关键字,都能很好的工作:

1
2
3
void function(){alert('ohroot')}()// undefined  
new function(){alert('ohroot')}()// Object
delete function(){alert('ohroot')}()// true

最后,括号做的事情也是一样的,消除歧义才是它真正的工作,而不是把函数作为一个整体,所以无论括号括在声明上还是把整个函数都括在里面,都是合法的:

1
2
(function(){alert('ohroot')})()// undefined
(function(){alert('ohroot')}())// undefined

参考文章:function与感叹号