js闭包
闭包
1.开始之前我们要了解相关基础知识
作用域
作用域:一言以蔽之,“作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)的规则”
作用域分成①词法作用域和②动态作用域
词法作用域:函数的作用域在函数定义时决定
动态作用域:函数的作用域在函数执行时决定
而JavaScript采用的规则是词法作用域
1 var value = 1; 2 function foo() { 3 console.log(value); 4 } 5 function bar() { 6 var value = 2; 7 foo(); 8 } 9 bar();//输出?
执行过程如下:
1、执行foo()函数,先从foo函数内部查找是否有局部变量value,如果没有,就根据声明的位置沿着作用域链查找上一层,也就是value等于1,所以结果打印1。
然后我们将foo函数的声明位置修改一下
1 var value = 1; 2 function bar() { 3 var value = 2; 4 function foo() { 5 console.log(value); 6 } 7 foo(); 8 } 9 bar();//输出?
这个时候会输出什么呢?我们一起分析一下执行过程
1、执行foo()函数,先从foo函数作用域查找是否有局部变量value,嗯?!没有诶,那就根据声明的位置沿着作用域链查找上一层;
2、现在我们的位置在bar()的作用域,让我找找哈,诶,有一个叫value的人!那我就把它输出,K.O.完工。
综上所述,JavaScript采用词法作用域,即“函数的作用域在函数定义时决定”的作用域规则的。(!!各位谨记这句话!!)
好的,接下来我们进入正题
2.概念
什么是闭包,以下是我搜刮到的定义
函数对象可以通过作用域链相互联系起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。——《JavaScript权威指南》
闭包是指有权访问另一个函数作用域中的变量的函数。——《JavaScript高级程序设计》
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。——《你不知道的JavaScript》
闭包,允许使用内部函数(即函数定义和函数表达式位于另一个函数的函数体内),而且,这些内部函数可以访问他们所在的外部函数中的声明的所有局部变量丶参数和声明的其他内部函数,当其中一个这样的内部函数在包含他们的外部函数之外被调用时,就会形成闭包。即内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量丶参数以及其他内部函数。这些局部变量丶参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。——《锋利的jQuery》
闭包是函数和声明该函数的词法环境的组合——MDN
虽然所给的定义都不相同,而且看得也脑壳疼,但是慢慢地实践,你就会发现里面的真谛。
3.栗子
一般情况下,我们都是这样通过子函数来访问父函数中的变量的,
1 var value = 1; 2 function funcOuter() { 3 console.log(value); 4 } 5 func();//输出1
foo()函数找到父函数bar()中的value变量进行访问,而不能这样访问
function funcOuter() { var value = 1; } console.log(value);//输出undefined
这么看来,我们是不能在外部访问内部函数中的变量的,“但是我就是想这么做!”傲娇的程序员们说,然后闭包就派上了用场!程序员在上面的例子里面加了一些代码,居然真的在外部获取到子函数的局部变量!
1 function funcOuter() { 2 var value = 1; 3 function funcInner(){ 4 return value; 5 } 6 return funcInner; 7 } 8 var func = funcOuter(); 9 console.log(func());//输出1
嗯,这怎么做到的呢🤔我们也来分析一下执行过程:
1、执行funcOuter(),返回funcInner()函数存储到变量func里面
2、执行func():先找funcInner()中有没有value变量,咦,没有喔,那就沿着作用域链到达上一个作用域中
3、上一个作用域是啥呢?由于func()实际上是funcInner()函数,而这个函数是在funcOuter()的作用域内声明的,所以上一个作用域就是funcOuter(),value=1,好了,把它返回就可以了,K.O.
我们再看看下面的例子
1 function f1(){ 2 var n=999; 3 nAdd=function(){//注意,这个是全局变量哦 4 n+=1; 5 }; 6 function f2(){ 7 console.log(n); 8 } 9 return f2; 10 } 11 var result=f1(); 12 result(); //输出? 13 nAdd(); 14 result(); //输出?
执行过程如下:
1、执行f1(),返回f2,赋值给变量result
2、执行result(),查找f2()中有没有n变量,没有,根据声明位置沿着作用域链到达上一个作用域中
3、到达f1()的作用域,找到n变量,输出,999
4、执行nAdd(),查找nAdd()中有没有n变量,没有,根据声明位置沿着作用域链到达上一个作用域中
5、到达f1()的作用域,找到n变量,执行n+=1,此时n=1000;
6、同上,result()输出的是1000
总结一下其实就是JavaScript采用的是词法作用域,即函数作用域是在定义时决定的,不仅仅是闭包,对于所有的函数都是如此,所以某博主说“闭包是一种现象,而不是技术!”
4.应用
保护函数内的变量安全
比如说我们要做一个计数器来记录账户的游戏金币,像我就会这么实现:
1 var money = 0;//初始化 2 function increase(){ 3 money += 1; 4 } 5 function decrease(){ 6 money -= 1; 7 }
通过调用increase()和decrease()函数,我们的确可以实现对money的增减操作,但是因为你的money是全局变量,如果有一些恶意的人直接对money变量进行操作的话,比如money=99999,那这个人就会有取之不尽用之不竭的钱财,那就是作弊啦!我们对代码修改一下
1 function moneyCount(){ 2 var money = 0; 3 return { 4 getMoney:function (){ 5 return money; 6 }, 7 increase:function (){ 8 money += 1; 9 }, 10 decrease:function (){ 11 money -= 1; 12 } 13 }; 14 } 15 var moneyCount = moneyCount(); 16 money = 99999;//此money非彼money 17 moneyCount.getMoney();//0 18 moneyCount.increase(); 19 moneyCount.getMoney();//1
(⊙﹏⊙)修改了很多下,这样通过闭包,我们将money封装在moneyCount中,在外部我们只能通过moneyCount的方法来对money进行更改,想要作弊?不存在的!
5.可别滥用闭包
闭包虽然好,但是之所以我们能从外部访问到函数的内部,是因为函数内部的作用域没有被回收,而这部分没有被回收的作用域是需要占用内存的。所以当不需要用到声明的闭包的时候,我们应该对他进行显性标记。如上例我们可以执行moneyCount = null;来清空引用。
#本文有写的不好或不对的地方还请各位指出,一起学习!#