主要参考:https://juejin.cn/post/7055221100354994183 和 https://segmentfault.com/a/1190000006813113
1 定义
立即执行函数是一种语法,可以让你的函数在定义后立即被执行,这种模式本质上就是函数表达式(命名的或匿名的),在创建后立即执行。
立即执行函数(immediate function)术语不是在ECMAScript标准中定义的,但它很短有助于描述和讨论模式
这种模式有以下几部分组成:
-
使用函数表达式定义一个函数(函数声明不能起作用)
-
在结尾加上一对括号,让函数立即被执行
-
将整个函数包裹在一对括号中(只有在你不将函数赋值给一个变量的时候才需要)
1 定义
立即执行函数是一种语法,可以让你的函数在定义后立即被执行,这种模式本质上就是函数表达式(命名的或匿名的),在创建后立即执行。
立即执行函数(immediate function)术语不是在ECMAScript标准中定义的,但它很短有助于描述和讨论模式
这种模式有以下几部分组成:
-
使用函数表达式定义一个函数(函数声明不能起作用)
-
在结尾加上一对括号,让函数立即被执行
-
将整个函数包裹在一对括号中(只有在你不将函数赋值给一个变量的时候才需要)
2 函数声明、函数表达式和匿名函数
2.1 函数声明
function fnName() { ... }
使用function关键词声明一个函数,再指定一个函数名,叫函数声明
2.2 函数表达式
var fnName = function() { ... }
使用function关键词声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。
2.2 匿名函数
function() { ... }
使用function关键词声明一个函数,但为给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个时间则成为时间处理程序或创建闭包等等。
2.3 函数声明与函数表达式的不同
-
JavaScript引擎在解析JavaScript代码时会函数声明提升(Function declaration Hoisting)当前环境(作用域)上的函数声明。而函数表达式必须等到JavaScript引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。
函数声明提升:在编译阶段函数声明会优先于变量被提升,并使其在执行任何代码之前可访问
-
函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName() 形式调用。
3 立即执行函数的几种写法
-
在函数表达式后加括号,可以立即执行函数
var foo = function() { console.log("立即执行"); }(); console.log(foo); // undefined
注意这里执行完之后foo的为undefined,因为这个立即执行函数没有返回值
-
将函数放在括号中,后面再加括号
(function() { console.log("立即执行"); })();
JavaScript中,括号内不允许包含语句,但可以时表达式,引擎先遇到关键词 function,就自动把括号里面的代码识别为函数表达式,而不是函数声明,所以可以立即执行。
4 立即执行函数的作用
JavaScript中没有私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其它人不小心用同名的变量给覆盖掉,根据JavaScript函数作用域链的特性,可以使用这种技术模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称匿名包裹器或命名空间。
5 闭包
JavaScript中可以把函数作为返回值,也可以把函数作为参数传递。
5.1 函数作为返回值
function fn() { var max = 10; return function bar(x) { if (x > max) { console.log(x); } }; } var f1 = fn(); f1(15);
如上代码中,bar 函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。
5.2 函数作为参数被传递
var max = 10; var fn = function(x) { if (x > max) { console.log(x); } }; (function(f) { var max = 100; fn(15); })(fn)
如上代码中,fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。
闭包和作用域、执行上下文栈有着密不可分的关系我们提到当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。
有些情况下,函数调用完成之后,其执行上下文环境不会接着别销毁。这就是需要理解闭包的核心内容
分析如上代码:
-
代码执行前生成全局上下文环境,并在执行时对其中的变量进行赋值,此时全局上下文环境时活动状态。
-
当执行到41行时,调用fn(),产生fn()执行上下文环境,全局上下文入栈中,并设置为活动状态。
-
当执行完41行。fn()调用完成,理论上应该销毁fn()执行上下文环境,但是这里并不是。因为执行fn()时,返回的是应该函数。函数的特别之处在于可以创建一个独立的作用域。而返回的bar函数中有一个自由变量max要引用fn()上下文环境中的max,因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。因此这里的fn()上下文环境不会被销毁。即当执行到41行时,全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。另外,执行完第18行,全局上下文环境中的max被赋值为100。
-
执行到行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态。执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10。
这里的重点就在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。
6 闭包的优点和缺点
6.1 优点
减少全局变量的使用,保证了内部变量的安全,同时外部函数也可以访问内部函数的变量,在内存中维持一个变量,也可以用作缓存
6.2 缺点
被引用的内部变量不能被销毁,增大了内存消耗,使用不当易造成内存泄露,解决办法可以在内部变量不使用时,把外部的引用置为 null 闭包就是函数间的跨作用域访问,会导致性能损失 立即执行函数和闭包的区别 立即执行函数和闭包没有关系,虽然两者会经常结合在一起使用,但两者有本质的不同
立即执行函数只是函数的一种调用方式,只是声明完之后立即执行,这类函数一般都只是调用一次(可用于单例对象上),调用完之后会立即销毁,不会占用内存
闭包则主要是让外部函数可以访问内部函数的作用域,也减少了全局变量的使用,保证了内部变量的安全,但因被引用的内部变量不能被销毁,增大了内存消耗,使用不当易造成内存泄露
7 立即执行函数于闭包结合使用
var single = (function() { let name = "图图"; let age = 20; return { getName: function() { return name; }, getAge: function() { return age; } } })(); console.log(single.getName()); //图图 console.log(single.getAge()); //20 console.log(single.name); //undefined
这里自执行函数返回的是一个对象字面量,即一个对象
这里的 single 是一个对象,对象包含两个方法:getName() 和 getAge()