头疼的闭包
一.官网的解释
闭包(closure)是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来了
·.· 在js中,只有函数内部的子函数才能读取局部变量
·.· 闭包就是能够读取其它函数内部变量的函数
.·.闭包是个子函数
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
二.如何理解
变量的作用域
Js中变量的作用域分两种:全局变量和局部变量。
(1)函数内部可以直接读取全局变量。
var n = ‘叼你啦!死机‘; function f1() { console.log(n); } f1();
(2)在函数外部自然无法读取函数内的局部变量。
function f1() { var n = ‘叼你啦!死机‘; } console.log(n); //error
此外,函数内部声明变量时,一定要用var关键字来命名变量。否则,就声明了一个全局变量。
当我们需要从外部读取函数内的局部变量,可以在函数的内部再定义一个函数。
function f1(){ n = ‘叼你啦!死机‘; function f2(){ console.log(n); } }
如上,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2是可见的。(重点一)
这就是Javascript语言特有的“链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
function f1(){ n = ‘叼你啦!死机‘; function f2(){ console.log(n); } return f2; } var result=f1(); result(); // 叼你啦!死机‘
如上,既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它的内部变量(重点二)
三.用途
1.读取函数内部的变量
2.令这些变量的值始终保持在垃圾(内存)回收机制中。
(或者这样理解:让这些变量的值始终保持在内存中,不会在f1调用后被自动清除)
function f1(){ var n = ‘叼你啦!死机‘; add=function(){ n = ‘你知道就好‘; }; function f2(){ console.log(n); } return f2; } var result=f1(); result(); // 叼你啦!死机 add(); result(); // 你知道就好
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次是:叼你啦!死机,第二次是:你知道就好。
换另外一个例子会直接说明点
function f1(){ var n=999; add=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 add(); result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。(重点三)
为什么会这样呢?
原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
前面两个例子中:在add前面没有使用var关键字,因此add是一个全局变量,而不是局部变量。其次,add的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以add相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
引用其他文章
作者:大水
链接:https://www.zhihu.com/question/34547104/answer/59515735
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
每次定义一个函数,都会产生一个作用域链(scope chain)。当JavaScript寻找变量varible时(这个过程称为变量解析),总会优先在当前作用域链的第一个对象中查找属性varible ,如果找到,则直接使用这个属性;否则,继续查找下一个对象的是否存在这个属性;这个过程会持续直至找到这个属性或者最终未找到引发错误为止。
看个简单版的例子:
(function(){ var hello="hello,world"; function welcome(hi){ alert(hi); //解析到作用域链的第一个对象的属性 alert(hello); //解析到作用域链的第二个对象的属性 } welcome("It‘s easy"); })();
运行结果很简单,一个弹窗It‘s easy.一个弹窗hello,world。
分析过程如下:
对于函数welcome(),定义welcome的时候会产生一个作用域链对象,为了表示方便,记作scopechain。scopechain是个有顺序的集合对象。
- scopechain的第一个对象:为了方便表示记作sc1, sc1有若干属性,引用本函数的参数和局部变量,如sc1.hi ;
- scopechain的第二个对象:为了方便表示记作sc2,sc2有若干属性,引用外层函数的参数和局部变量,如sc2.hello;
- ...
- scopechain的最后一个对象:为了方便表示记作scn,scn引用的全局的执行环境对象,也就是window对象!,如scn.eval();
这里之所以可以弹出hello,world,原因就是变量解析时在welcome函数作用域链的第一个对象上找不到hello属性,然后就去第二个对象上找去了(结果还真找到了)。
所以,JavaScript中的所谓的高大上的闭包其实很简单,根本上还是变量解析。而之所以可以实现,还是因为变量解析会在作用域链中依次寻找对应属性的导致的。