工作中发现立即执行函数还能这么写:var a=function(){console.log(123)}()
,OMG,还能这么玩,感觉对这一块的理解还需要加强,因此补习了这一块的知识。
函数调用
函数调用通常有如下两种方式:
//函数声明语句写法
function test(){};
test();
//函数表达式写法
var test = function(){};
test();
但有时需要在定义函数之后,立即调用该函数。这种函数就叫做立即执行函数,全称为立即调用的函数表达式IIFE(Imdiately Invoked Function Expression)
JavaScript引擎规定,如果function关键字出现在行首,一律解释成函数声明语句
错误写法
- 函数声明语句需要一个函数名,由于没有函数名,所以报错
//SyntaxError: Unexpected token ( function(){}();
- 函数声明语句后面加上一对圆括号,只是函数声明语句与分组操作符的组合而已。由于分组操作符不能为空,所以报错
//SyntaxError: Unexpected token ) function foo(){}(); //等价于 function foo(){}; ();//SyntaxError: Unexpected token )
- 函数声明语句加上一对有值的圆括号,也仅仅是函数声明语句与不报错的分组操作符的组合而已
function foo(){}(1); //等价于 function foo(){}; (1);
常用写法
解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式。
- 最常用
写法区别:(function(){ /* code */ }()); (function(){ /* code */ })();
(function(){})(); 是 把函数当作表达式解析,然后执行解析后的函数 相当于 var a = function(){}; a(); a得到的是函数
(function(){}()); 是把函数表达式和执行当作语句直接执行、 相当于 var a = function(){}(); a得到的是结果 - 其他写法
var i = function(){ return 10; }(); !function(){ /* code */ }(); void function(){ /* code */ }(); // 等同于(function (){})
传统写法
function foo() {...}
foo();
- 为什么用IIFE
- 传统的方法啰嗦,定义和执行分开写;
- 惰性载入,直接上代码,比如浏览器事件绑定
var addEvent = (function () { if (document.addEventListener) { return function (type, element, fun) { element.addEventListener(type, fun, false); } } else if (document.attachEvent) { return function (type, element, fun) { element.attachEvent('on' + type, fun); } } else { return function (type, element, fun) { element['on' + type] = fun; } } })();
- 传统的方法直接污染全局命名空间,自调用匿名函数就用来防止变量弥散到全局(包括函数本身);
- 隔离作用域,用这种方式return接口会很安全。以免各种js库冲突。基本代码如下:
var add = (function(){ var counter = 0; return function(){ return ++counter; } })();
- 如果需要全局对象呢?这里很奇怪,我不传入在匿名函数内部同样可以访问window对象哇。
那么为什么要传入呢?在网上查到一个非常典型的例子,代码如下:void function (global) { // 在这里,global 就是全局对象了 }(this) // 在浏览器里,this 就是 window 对象
(function (window, $, undefined) { play=function(){ $("#demo").val("This is a demo."); } window.wbLogin = play; })(window, jQuery);
- 为什么传入window?
通过传入 window 变量,使得 window 由全局变量变为局部变量,当在代码块中访问 window 时,不需要将作用域链回退到顶层作用域,这样可以更快的访问 window;同时将 window 作为参数传入,可以在压缩代码时进行优化。 - 为什么传入jQuery?
jQuery传入后将参数写成$可以保证在此函数内$为jQuery而不是其他类似使用$符号的库。 - 为什么传入undefined?
在自调用匿名函数的作用域内,确保 undefined 是真的未定义。因为 undefined 能够被重写,赋予新的值。
由于没有传入第三个参数,自然就是undefined。由于JavaScript中undefined是一个变量,可以被改变,所以这样可以保证undefined判断时的准确性。有时判断时使用typeof xxx === 'undefined'也是因为这个原因。
- 为什么传入window?
- 为什么如下写是错的?
因为 function foo(...){} 这个部分只是一个声明,对于解释器来说,就好像你写了一个字符串 "function foo(...){}",它需要使用解析函数,比如 eval() 来执行它才可以。所以把 () 直接放在声明后面是不会执行,这是错误的语法。function foo(...){}();
用途
IIFE一般用于构造私有变量,避免全局空间污染,隔离作用域目的。
假设有一个需求,每次调用函数,都返回加1的一个数字(数字初始值为0) 。
全局变量,但变量a实际上只和add函数相关,却声明为全局变量,不太合适。
var a = 0; function add(){ return ++a; } console.log(add());//1 console.log(add());//2
自定义属性,用户有可能在不知情的情况下重置add.count
function add(){ return ++add.count; } add.count = 0; console.log(add());//1 console.log(add());//2
IIFE
var add = (function(){ var counter = 0; return function(){ return ++counter; } })(); console.log(add())//1 console.log(add())//2
总结
看上去一个很小的知识点,在查找资料学习的过程中,可以延伸出许多的知识哇,骚年,好好努力吧。