自执行函数(IIFE)
JavaScript中存在一种写法:
1 | (function () { |
可以看到在()
内定义函数,然后又使用()
来执行该函数。执行函数的()
有两种位置,包裹函数的()
内或者外。
作用
了解了写法后,这种函数有什么意义或者作用?
避免污染全局变量
我们知道使用var
关键字声明的变量在当前作用域是全局的:
1 | var a = 'global'; |
如果项目需要多人合作,假设A实现一个功能,比如计算当天是本月第几周并打印:
1 | var time = new Date(); |
A 将代码写在 A.js 文件中,index.html 中引入该文件。OK,一切正常。此时B也需要实现一个功能,打印当前时间:
1 | var time = new Date(); |
B 觉得很简单啊,新建了B.js 文件,在 index.html 中 A.js 文件后面的位置引入自己的 B.js,打开网页一看,打印出了自己需要的数据,OK,完美。C 也有工作要做,根据当前是第几周来决定是否要给页面加一些特效,A 告诉他直接获取 time
变量就可以,不用自己去实现计算第几周。C 于是就直接拿来用,结果肯定不对,因为 time
被 B 给覆盖掉了。
这里是 B 覆盖掉了 A 的值,如果 B 采用自执行函数的写法:
1 | (function () { |
仍然可以实现“打印当前时间”的功能,同时 C 还是可以获取到 time
这个变量的值是第几周而非当前时间。
事实上,我们无法预知是否会覆盖掉别人的变量或者被别人覆盖。如果需要被别人使用,向外暴露最少的变量,以免被覆盖;如果不需要被别人使用,直接使用自执行函数将会更好。
OK,了解了用法,接下来了解下为什么可以实现不污染全局变量。
而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了他们的价值,他们都会随着函数调用的结束而被销毁。
对于上面的代码而言,定义了函数并立即执行内部的代码,执行完毕后,time
就被销毁了。
看到这里,那如果先定义函数,再执行这个函数不是一样的吗?
1 | function fnc() { |
的确没有污染time
这个变量了,但是很明显相比自执行函数,多出了fnc
变量。而这个变量又有覆盖其他变量的可能,所以最好变量越少越好。定义一个对象,在对象中定义函数进行计算,最后返回需要的值,这样似乎是“最佳实践”?
封装变量
其实封装变量的目的就是为了减少全局变量,避免污染全局变量。不过封装变量往往配合闭包使用。
1 | var cache = {}; |
书上的例子,cache
变量只在函数mult
中使用,但是又不能将其放在函数内部。针对这种其他,就需要使用到封装变量,将cache
变量封装,外部无法获取变量,只有mult
函数可以访问到cache
。
1 | var mult = (function () { |
很容易就想到for
循环中的变量i
刚好符合这种情况,如果同一作用域有多个for
循环,我们就要用到变量j
、k
…很显然这样不方便,所以可以将变量i
封装,只有for
循环可以拿到变量i
。
1 | (function () { |
惰性加载
在查找相关说明时,很多人提到可以实现惰性加载。惰性加载是指在某些需要很多if判断的情况但永远只匹配一种情况,在判断条件的同时,改写了函数本身为符合条件的情况。
1 | var addEvent = (function(el,type,handler){ |
根据浏览器的兼容性来判断使用什么方法来添加监听器。如果是 IE 就用attachEvent
,如果是其他浏览器就用addEventListener
。如果每次需要添加监听器都这么判断肯定很麻烦,所以只做一次判断,判断的同时改写了函数本身。
- 如果是IE 浏览器,addEvent = function () {…};
- 如果是其他浏览器,addEvent = function () {…};
这里用自执行函数的目的是什么?其实不用也可以啊,只要执行一次addEvent
函数,该函数就被改写了。
比如某网站,在浏览每个页面时会根据用户的类别来给用户打招呼。
- 如果是会员,就说“欢迎”
- 如果不是会员,就说“来买会员吧”
1 | var person = { |
可以看到只输出了一次”如果是会员”,这一次在立即执行时输出的,而不是在第一次调用say()
函数时输出的。第二次say()
函数的调用就直接打印了“欢迎”,而没有打印“如果是会员”表示没有经过判断的步骤。
会员的类型是确定的,不会访问这个网页时是会员,访问另一个网页又不是会员了。记住,虽然有很多判断,但是无论经过多少次判断,都是一种情况,所以干脆把这种情况保存下来,以后再用就不判断了,反正知道是一样的。
其实这里不用自执行函数也可以,只是会多出一个变量:
1 | var person = { |
可以看到和上面的写法输出是一样的。
JQ 插件/命名冲突
看到很多jquery插件是这么写的:
1 | (function ($) { |
还有这么写的
1 | (function (window) { |
有一个优点,即如果存在另一个第三方类库,同样是使用$
作为简写形式,就导致了冲突,而采用上面的写法,由于将jQuery
作为参数传递给了自执行函数,所以该函数内部的$
变量就肯定是jQuery
了。
##总结
IIFE 其实就是为了减少变量,避免污染全局空间。无论是封装变量还是惰性函数,其实不用 IIFE 也是可以实现的。
参考
- 《javascript设计模式与开发实践》