头疼的闭包
updated on 2017/8/25
前言:没怎么学javascript,结果这几次面试都肥佬了!只能先把javascript学好,再找工作啦!
一.官网的解释
闭包(closure)是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。——很抽象。
闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来了
·.· 在js中,只有函数内部的子函数才能读取局部变量
·.· 闭包就是能够读取其它函数内部变量的函数
.·.闭包是个子函数
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
二.如何理解
需要知识:
1.变量的作用域有哪些
2.undefined是什么
3.return有什么作用
4.变量的生命周期
5.内嵌函数是什么
6.自调用匿名函数又是什么
三.目标:最终理解如下例子
var add = (function () { var counter = 0; return function () {return counter += 1;} })(); add(); // 结果为 1
add(); // 结果为 2
add(); // 结果为 3
问题一:为什么每次调用add();它的结果不是1,而是发生累加呢?
先不急着理解这个例子,带着问题学习如下知识点。
1.变量的“作用域”有哪些
Js中变量的作用域分两种:全局变量和局部变量。
(1)函数内部可以直接读取全局变量,所以全局变量就会受每个独立函数块的影响。
var n = '叼你啦!死机';
function f1() {
console.log(n);
}
f1(); // 叼你啦!死机'
(2)在函数外部自然无法读取函数内的局部变量。 (导读:注意点1)
function f1() {
var n = '叼你啦!死机';
}
console.log(n); //error
有了作用域的概念就接着了解undefined
2.undefined是什么
这里插入一点小知识点:
2.1变量
2.1.1变量要先声明,再定义
var i;
i=10;
或者简写成var i=10;(一般都这样处理)
2.1.2声明变量时有var关键字,不用var关键字的话是全局变量
2.1.3如果声明一个变量x,却不给他赋值,则打印的结果是undefined(导读:注意点2)
.·. 一个变量,没有经历定义这一步骤step,它的定义值就默认是undefined
.·. 一个变量,没有定义,它的定义值传输给对象的值就默认是undefined
而已先不理解透,但是要记住我所的话,来学习下一个知识点:
3.return有什么作用
return就是函数返回值。函数可以没有返回值 当你需要把这个函数的最终结果返回出来,就要return
如:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <p>该实例调用函数并输出 PI 值:</p> <p id="demo"></p> <script> var x = myFunction(4, 3); function myFunction(a, b) { var c=a * b; } document.getElementById("demo").innerHTML = myFunction(); </script> </body> </html>
这个时候,没有return 它就会返回一个undefined
<script> var x = myFunction(4, 3); function myFunction(a, b) { var c=a * b; return c; } document.getElementById("demo").innerHTML = myFunction(); </script> //结果:NaN
现在
myFunction();有返回值,结果绝对不可能是undefined
接着还要理解:
4.生命周期这个概念。
全局变量因为可以处处引用,所以注定它是留在浏览器卸载页面才会结束
而局部变量的作用域是局部性的,使用完就解散了。
所以才会有如下情况
function add() { var counter = 0; return counter += 1; } add(); add(); add(); // 本意是想输出 3, 但事与愿违,输出的都是 1 !
问题二:那怎么才能使一个变量每次都像全局变量那样被某函数调用所影响同时满足变量是局部变量那样私有化的要求呢?
所有函数都能访问它们上一层的作用域。
5.内嵌函数
function add() { var counter = 0; function plus() {counter += 1;} plus(); return counter; }
add();
add();
//输出的都是 1 !
嵌套函数可以访问上一层的函数变量。现在内嵌函数 plus() 可以访问父函数add()的 counter 变量:
内嵌函数的使用可以让我们达到变量counter每次都像全局变量在外围被函数plus()调用所影响。
同时也满足了变量是add()的局部变量的定义。
上述的例子,只是只有全局变量的形,没有全局变量的貌。没有像全局变量那样累加。
还可以参考这个例子
function f1(){
n = '叼你啦!死机';
function f2(){
console.log(n);
}
}
当我们需要从外部读取函数内的局部变量,可以在函数的内部再定义一个函数。
这时候应该是我们全文中的主角闭包登场了。
既然plus()可以读取add()中的局部变量,那么只要把plus()作为返回值,我们就可以在add()外部读取它的内部变量(导读:注意点3)
function add() { var counter = 0; return function () {return counter += 1;} } add(); //结果是function () {return counter += 1;}
但是如果直接引用add();时,你会发现返回的结果是一个函数表达式,return把函数表达式即函数内容当值来返回了。
现在又要 插入一些小知识了。
js圆括号的使用:
function add() { var counter = 0; return function () {return counter += 1;} } add()(); //结果是1
只要在add()后面加上(),就可以将前面的值作为函数来使用。——暂时是这么理解
但是在加上
add()();
add()(); 结果还是1.这不是我们想要的。
奇怪的现象是:当这样声明时,
function add() { var counter = 0; return function () {return counter += 1;} } var result=add(); result(); //这个就是闭包
就算再加上
result();
result(); 结果就改变为累加了。这个结果就是我们想要的结果。
只要能理解上述的(),那么理解自调用就好理解了。
var add = (function () { var counter = 0; return function () {return counter += 1;} })(); add(); add(); add(); // 计数器为 3
其结构是
var add = ()();
在这段代码中,add实际上就是闭包f2函数。它一共运行了三次,第一次是:1,第二次是:2,第三次是:3。
这证明了,函数add中的局部变量counter一直保存在内存中,并没有在add调用后被自动清除。(导读:注意点4)
四.未修改前的内容
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"); })();
分析过程如下:
对于函数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中的所谓的高大上的闭包其实很简单,根本上还是变量解析。而之所以可以实现,还是因为变量解析会在作用域链中依次寻找对应属性的导致的。