立即执行函数详解

要知道这几种写法之间的区别,我们要先聊些题外话——js 中函数的两种命名方式,即表达式和声明式。

函数的表达式和声明式

函数的声明式写法为:function foo(){//},这种写法会导致函数提升,所有 function 关键字都会被解释器优先编译,不管是声明在什么位置,都可以调用它,但是它本身不会被执行,定义只是让解释器知道其存在,只有在被调用的时候才会执行。如下图:

声明式函数

函数的表达式写法为:var foo=function(){//},这种写法不会导致函数提升,于是就必须先声明,再调用,否则会出错,如图:

表达式函数

声明式立即执行函数

现在,回到正题,(function(){}()),(function(){})()这两种是 js 中立即执行函数的写法,函数表达式后加上 () 可以被直接调用,但是把整个声明式函数用 () 包起来的话,则会被编译器认为是函数表达式,从而可以用 () 来直接调用,如 (function foo(){//})(),但是如果这个括号加在声明式函数后面,如 function foo(){//}(),则会报错,很多博客说这种写法 () 会被省略,但实际是会出错,因为不符合 js 的语法,所以想要通过浏览器的语法检查,就必须加点符号,比如()、+、! 等,如图:

立即执行函数

总结一下就是:

function foo(){console.log("Hello World!")}()//声明函数后加()会报错
(function foo(){console.log("Hello World!")}())//用括号把整个表达式包起来,正常执行
(function foo(){console.log("Hello World!")})()//用括号把函数包起来,正常执行
!function foo(){console.log("Hello World!")}()//使用!,求反,这里只想通过语法检查。
+function foo(){console.log("Hello World!")}()//使用+,正常执行
-function foo(){console.log("Hello World!")}()//使用-,正常执行
~function foo(){console.log("Hello World!")}()//使用~,正常执行
void function foo(){console.log("Hello World!")}()//使用void,正常执行
new function foo(){console.log("Hello World!")}()//使用new,正常执行

倘若需要传值,直接将参数写到括号内即可,并使用短路或||当前一个参数为null时,这时会使用后一参数充当默认值,如图:

声明式立即执行函数传值

即:

(function foo(v){console.log(v)})('声明式立即执行函数传值') 
// 声明式立即执行函数传值
(function foo(v){console.log(v)})('NotNULL' || '声明式立即执行函数传值')
// NotNULL
(function foo(v){console.log(v)})(null || '声明式立即执行函数传值')
// 声明式立即执行函数传值
匿名式立即执行函数

立即执行函数一般也写成匿名函数,匿名函数写法为 function(){/…/},就是使用 function 关键字声明一个函数,但未给函数命名,倘若需要传值,直接将参数写到括号内即可,同样也可以使用||给参数设定默认值,如图:

立即执行函数的传参

将它赋予一个变量则创建函数表达式,赋予一个事件则成为事件处理程序等。但是需要注意的是匿名函数不能单独使用,否则会 js 语法报错,至少要用 () 包裹起来。上面的例子可以写成如下形式:

(function(){console.log("我是匿名函数。")}())
(function(){console.log("我是匿名函数。")})()
!function(){console.log("我是匿名函数。")}()
+function(){console.log("我是匿名函数。")}()
-function(){console.log("我是匿名函数。")}()
~function(){console.log("我是匿名函数。")}()
void function(){console.log("我是匿名函数。")}()
new function(){console.log("我是匿名函数。")}()

​ 需要注意的是:立即执行函数其实就是执行括号前返回的函数的引用,所以调用括号的前面表达式返回的必须是函数的引用,而不能是函数的调用。(立即执行函数可以存在多级关系:爷、父、子、孙,只要保证前一个括号返回的是函数引用即可)即:

var fn = function(n) {
	function f(m) {
		return n + m;
	}
	return f;//这必须返回的是整个函数,而不是函数的调用f();
};
var s = fn(1)(2);
console.log(s); 
// 3
//或者
function fn(n) {
	function f(m) {
		return n + m;
    }
    return f;
};
var s = fn(1)(2);
console.log(s);
// 3

// 其它任何函数一样,一个立即执行函数也能存在返回值并且可以赋值给其它变量。只要保证调用括号前返回的是函数引用即可。
var num = (function () {
    return 4
})()
console.log(num)

// 父、子、孙 函数的调用
function fn() {
	function f() {
		function g(){
            console.log('孙 函数');
        }
        return g;
    }
    return f;
};
var s = fn()()();
console.log(s);
// 孙 函数

立即执行函数的作用是:1. 创建一个独立的作用域,这个作用域里面的变量,外面访问不到,这样就可以避免变量污染。2. 闭包和私有数据。提到闭包,不得不提下那道经典的闭包问题。

<ul id=”test”>
    <li>这是第一条</li>
    <li>这是第二条</li>
    <li>这是第三条</li>
</ul>

<script>
    var liList=document.getElementsByTagName('li');
    for(var i=0;i<liList.length;i++)
    {
        liList[i].onclick=function(){
            console.log(i);
        }
    };
</script>

很多人觉得这样的执行效果是点击第一个 li,则会输出 1,点击第二个 li,则会输出2,以此类推。但是真正的执行效果是,不管点击第几个 li,都会输出 3,如下图所示。因为 i 是贯穿整个作用域的,而不是给每个 li 分配了一个 i,用户触发的 onclick 事件之前,for 循环已经执行结束了,而 for 循环执行完的时候 i=3。

在这里插入图片描述

图 5 各自点击第 1,2,3 个

  • ,或是之后再次点了多少次,都会输出 3,可见,右边控制台输出了 8 次 3
  • 但是如果我们用了立即执行函数给每个 li 创造一个独立作用域,就可以改写为下面的这样,这样就能实现点击第几条就能输出几的功能。

    <script>
        var liList=document.getElementsByTagName('li');
        for(var i=0;i<liList.length;i++){
            (function(ii) {
            	liList[ii].onclick=function(){
            		console.log(ii);
        		}
            })(i)
        };
    </script>
    

    在立即执行函数执行的时候,i 的值被赋值给 ii,此后 ii 的值一直不变, 如下图所示。i 的值从 0 变化到 3,对应 3 个立即执行函数,这 3 个立即执行函数里面的 ii 「分别」是 0、1、2。

    在这里插入图片描述

    其实 ES6 语法中的 let 也可以实现上述的功能,仅仅是将 for 循环中的 var 换成 let,如下所示,有木有觉得很简单明了。

    <script>
    	var liList=document.getElementsByTagName('li');
    	for(let i=0;i<liList.length;i++){
            liList[i].onclick=function(){
                console.log(i);
            }
    	}
    </script>
    

    那很多人就觉得用 let 可以完全取代立即执行函数,到目前为止,可能是我眼界所限制,我所能用到的立即执行函数的确能被 let 替代,前提是你的运行环境(包括旧的浏览器)支持 ES2015。如果不支持,你将不得不求助于以前经典的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值