博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入了解JavaScript中的闭包
阅读量:6094 次
发布时间:2019-06-20

本文共 1967 字,大约阅读时间需要 6 分钟。

闭包在js语言中总是有些难以掌握其中的门道,然而js中的闭包无处不在;

MDN对闭包的定义为:闭包是指那些能够访问自由变量的函数。

自由变量: 自由变量是指在函数中使用的,但挤不上函数参数也不是函数的局部变量的变量。 闭包 = 函数 + 函数能够访问的自由变量(理论上的闭包)

ECMAScript中,闭包指的是:

从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。

从实践角度:以下函数才算是闭包: 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回) 在代码中引用了自由变量 小师傅曾告诉我说闭包只要死记一条:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是当前词法作用域之外执行

function foo() {    let a = 2;    function bar() {        console.log(a);    }    return bar;}let baz = foo();baz();//2 ----这就是闭包的效果复制代码

函数bar()的词法作用域能够访问foo()的内部词法作用域,然后我们将bar()函数本身当做一个值类型进行传递。bar()可以正常执行但是是在自己定义的词法作用域之外执行。

上述代码的执行过程:

1、创建全局执行上下文,全局执行上下文压入执行上下文栈; 2、全局执行上下文进行初始化; 3、执行foo函数,创建foo函数执行上下文,foo之执行上下文被压入执行上下文栈; 4、foo执行上下文初始化,创建变量对象,作用域链,this等; 5、foo函数执行完毕,foo执行上下文从执行上下文栈中弹出 6、创建函数bar,创建bar函数执行上下文,bar执行上下文被压入执行上下文栈 7、bar执行上下文初始化,创建变量对象,作用域链、this等; 8、bar函数执行完毕,bar函数从执行上下文栈中弹出;

foo()在执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收机制用来释放不在使用的内存空间,由于foo()的内容看上去不会在被利用所以会考虑对其进行回收。而闭包正是“阻止”这件事的发生,事实上上面的例子中foo()内部作用域依然存在,那这是为什么呢?

因为bar()所声明的位置所赐,他拥有涵盖foo()内部作用域的闭包,即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回),以供bar()在之后的任何时间进行引用。bar()依然持有对该作用域的引用,而这个引用就叫做闭包闭包使得函数可以继续访问定义时的词法作用域。

当bar函数执行的时候,foo()函数上下文已经被销毁(即从执行上下文栈中被弹出),了解具体的执行程序后,我们知道bar执行上下文维护了一个作用域链:

barContext = {    Scope:[AO,fooContext.AO,globalContext.VO]}复制代码

因为这个作用域链,bar函数依然可以读取到fooContext.AO的值,说明当bar函数引用了fooContext.AO中的值的时候,即使fooContext被销毁了,js依然关于让fooContext.AO或在内存中,bar函数依然可以通过bar函数的作用域链找到他,正因为JavaScript做到这一点,从而实现了闭包这个概念。

无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

function foo() {    let a = 2;    function baz() {        console.log(a)    }    bar(baz);}function bar(fn){    fn();//2 ----这里也是闭包的效果}复制代码

无论通过何种手段将内部函数传递到所在词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

面试题

var data = [];for(var i = 0; i<3; i++) {    data[i] = function() {        console.log(i)    };}data[0]() //3复制代码

data[0] = function(){console.log(i)},这里的i已经在循环结束的时候变成了3。

这个例子在ES6深入学习(一)块级作用域详解[]中也讲过,有兴趣可以看一看~

转载于:https://juejin.im/post/5ccf8de2f265da0380438189

你可能感兴趣的文章
ViewPager切换动画PageTransformer使用
查看>>
coco2d-x 基于视口的地图设计
查看>>
C++文件读写详解(ofstream,ifstream,fstream)
查看>>
Android打包常见错误之Export aborted because fatal lint errors were found
查看>>
Tar打包、压缩与解压缩到指定目录的方法
查看>>
新手如何学习 jQuery?
查看>>
配置spring上下文
查看>>
Python异步IO --- 轻松管理10k+并发连接
查看>>
mysql-python模块编译问题解决
查看>>
java 中getDeclaredFields() 与getFields() 的区别
查看>>
熟练掌握doc命令下的文件操作
查看>>
Oracle中drop user和drop user cascade的区别
查看>>
【Linux】linux经常使用基本命令
查看>>
Java 内存区域和GC机制
查看>>
STL之string
查看>>
更新代码和工具,组织起来,提供所有博文(C++,2014.09)
查看>>
HTML模块化:使用HTML5 Boilerplate模板
查看>>
登记申请汇总
查看>>
Google最新截屏案例详解
查看>>
2015第31周一
查看>>