相信很多前端小伙伴在工作和学习中, 都会或多或少的接触和了解到匿名函数和闭包. 也去网上搜索了不少的资料, 查到资料和解释都各有说辞, 甚至有些解释本身就是不正确的, 这更加让人头疼. 今天就来聊一聊匿名函数和闭包, 浅谈一下他们之间的关系 (实际上他们之间并没有什么直接关系!).
什么是匿名函数
与匿名函数相对应的是具名函数, 具名函数非常简单: function myFn(){}, 这就是个具名函数这个函数的 name 是 myFn. 可以测试一下:
- function myFn(){
- }
- cosnole.log(myFn.name);//myFn
特别说明一下, 函数表达式也是一种具名函数的定义方式. 比如 var myFn1 = function(){}, 打印 myFn1.name, 也会得到 myFn1.
再说匿名函数, 一般用到匿名函数的时候都是立即执行的. 通常叫做自执行匿名函数或者自调用匿名函数. 常用来构建沙箱模式, 作用是开辟封闭的变量作用域环境, 在多人联合工作中, 合并 JS 代码后, 不会出现相同变量互相冲突的问题. 立即执行的匿名函数有很多种写法, 常见的有以下两种:
- (function(){
- console.log("我是匿名方式 1");
- })();// 我是匿名方式 1
- (function(){
- console.log("我是匿名方式 2");
- }());// 我是匿名方式 2
- console.log((function(){}).name);//'' name 为空
两者的区别就是: 一个是发起执行的括号在匿名函数括号的外面, 另外一个发起执行的括号在匿名函数的里面. 实际中的书写方式个人推荐第一种, 这种写法更符合调用机制, 调用时的参数也比较明显, 如下:
- (function(i,j,k){
- console.log(i+j+k);
- })(1,3,5);
- //9
还有其他一些自执行匿名函数的写法, 如下:
- -function(){
- console.log("我是匿名方式 x");
- }();
- console.log(-function(){}.name);//-0
- +function(){
- console.log("我是匿名方式 x");
- }();
- console.log(+function(){}.name);//0
- ~function(){
- console.log("我是匿名方式 x");
- }();
- console.log(~function(){}.name);//-1
- !function(){
- console.log("我是匿名方式 x");
- }();
- console.log(!function(){}.name);//true
- void function(){
- console.log("我是匿名方式 x");
- }();
- console.log(void function(){}.name);//undefined
这几种操作符, 有时会影响结果的类型, 不推荐使用, 大家可以查下资料看看各种方式之间的差别. 具名函数其实也可以立即执行, 在此不做太多的伸展 (本文主要目的是为了说明匿名函数和闭包之间的关系).
实际上, 立即执行的匿名函数并不是函数, 因为已经执行过了, 所以它是一个结果, 这个结果是对当前这个匿名函数执行结果的一个引用 (函数执行默认 return undefined). 这个结果可以是一个字符串, 数字或者 null/false/true, 也可以是对象, 数组或者一个函数 (对象和数组都可以包含函数), 当返回的结果包含函数时, 这个立即执行的匿名函数所返回的结果就是典型的闭包了.
闭包是怎么定义的, 该如何理解
闭包本身定义比较抽象, MDN 官方上解释是:
A closure is the combination of a function and the lexical environment within which that function was declared.
中文解释是: 闭包是一个函数和该函数被定义时的词法环境的组合.
很多地方可以看到一个说法: JS 中每个函数都是一个闭包, 这样理解也是没有问题的, 不过会增加对闭包的理解难度, 这里先不这么理解, 可以按照闭包起的作用来理解它: 就是能在一个函数外部执行这个函数内部的定义方法, 并访问内部的变量
在此, 先看个经典的使用闭包的案例, 实现在函数外部访问函数内部的局部变量:
- function box(){
- var a = 10;
- function inner(){
- return a;
- }
- return inner;
- }
- var outer = box();
- console.log(outer());//10
正常情况, box 执行过后, 会被回收机制回收所占用的内存, 包括其内部定义的局部变量. 但是此时 box 执行过后返回一个内部的函数 inner, 这个 inner 引用了内部的变量 a,inner 又被外部 outer 给接收, 回收机制检查到内部的变量被引用, 就不会执行回收.
但是看到这里, 还是一脸蒙比, 哪里使用了闭包? 貌似有三个函数呀, 一个 box, 一个 inner 还有一个 outer = box().
这个案例中用到的闭包其实是 inner 和 inner 被定义时的词法环境, 这个闭包被 return 出来后被外部的 outer 引用, 因此可以在 box 外部执行这个 inner,inner 能够读取到 box 内部的变量 a.
使用这个闭包的目的是为了在 box 外部访问 a, 就是通过执行 outer().
JavaScript 中闭包和匿名函数的区别
闭包: 值有权访问另一个函数作用域的变量的函数
匿名函数: 没有名字的函数
区别: 匿名函数不涉及跨作用域的问题, 只是一种函数的简单类型, 而匿名函数常见的是在函数内部创建一个新的函数.
用匿名函数实现闭包
上面的例子是在具名函数 box 内部用一个具名函数 inner 实现了闭包, 那怎么使用匿名函数实现闭包呢, 也很简单:
- // 第一步直把内部 inner 这个具名函数改为匿名函数并直接 return, 结果同样是 10
- function box(){
- var a = 10;
- return function(){
- console.log(a) ;
- }
- }
- var outer = box();
- outer();//10
- // 第二步把外部 var outer = box() 改成立即执行的匿名函数
- var outer = (function(){
- var a=10;
- return function(){
- console.log(a);
- }
- })();
- //outer 作为立即执行匿名函数执行结果的一个接收, 这个执行结果是闭包, outer 等于这个闭包.
- // 执行 outer 就相当于执行了匿名函数内部 return 的闭包函数
- // 这个闭包函数可以访问到匿名函数内部的私有变量 a, 所以打印出 10
- outer();//10
这样我们就改写成了由匿名函数实现的闭包, 真正使用到的闭包是内部的被 return 的函数和这个函数所定义时的环境. 由此可以说明: 闭包跟函数是否匿名没有直接关系, 匿名函数和具名函数都可以创建闭包.
来源: http://www.css88.com/qa/javascript/14456.html