javascript闭包的形成图解
下面就来说说闭包的一些基本概念和具体的形成过程。
什么是闭包?
闭包就是既能重用一个变量,又可以保护变量不被污染的一种机制 。下面就通过一个小例子来帮助大家理解闭包的作用。
var i=1;
function add(){
console.log(i++);
}
add() //1
add() //2
i=1;
add() // 1
add() // 2
假如上面的程序是用来取号排队乘地铁的,由于程序小哥哥的粗心大意,在中间某个地方给全局的变量i重新赋值,导致最后的取号重复啦,这是会导致非常不友好的后果的,估计广州的3号线天天得有人干架!(这就是上面所说的变量受到污染)。
导致上面变量污染的最基本原因就是i是一个全局变量,那么如果把i变成了函数内部的局部变量,又会出现什么样的情况呢?
function add(){
var i=1;
console.log(i++);
}
add() //1
add() //1
i=2;
add() // 1
add() // 1
上面这种情况也很好理解,因为每次调用完add()后,add自己的函数作用域AO就会被释放,所以每次得到的结果都为1,这种情况估计地铁里面的工作人员都要被****。很明显,这是因为i为函数内容自己的局部变量,既不能重用的原因。所以,闭包就是为了解决上面这种问题的(重用一个变量,又可以保护变量不被污染);
闭包的书写步骤:
1.用外层函数包裹住受保护的变量和内层函数
2.外层函数将内层函数返回到外部
3.使用者调用外层函数获得返回的内层函数对象
function() outer{
var i=1;
return function(){
console.log(i++);
}
}
vat getNum=outer();
getNum();//1
getNum();//2
i=1;
getNum();//3
getNum();//4
由上面例子可以看出,变量i既能重用,又不会被污染。那到底闭包底层的原理是什么呢?说得概括一点就是,外层函outer的作用域AO无法释放。可能这句话大家看得很懵逼,下面就通过图解的方式,帮助大家简单明了地理解闭包的底层原理。
1.首先程序在刚开始执行之前,会创建ECS执行环境栈,用来存放一些变量或函数。
image.png
2.程序开始执行的时候,首先创建一个outer函数,和全局的getNum变量,所以此时window中会存放入outer和getNum
image.png
3.当outer函数被调用的时候,outer会放到ECS执行环境栈中,并且创建一个outer的执行环境AO,当执行到
var i=1这句代码的时候,outer的AO中就有了自己的局部变量i,
image.png
4.继续往下执行到return function() {console.log(i++);} 的时候,引擎就会创建一个函数,此时这个函数的祖籍为outer,不再是window,然后outer会把这个函数的地址返回给getNum,也就是说,这时候的getNum引用的就是outer内部return出来的小函数,而内部函数的祖籍又是outer,outer的parent又是window,此时就形成了一种相互抱团的情况。
image.png
图片中绿色的箭头,就是相互之间作用域链关系。
5.当outer调用完以后,outer会从ECS中释放,正常来说,outer的AO也会被跟着释放,但是请注意,这时候outer的AO 被内部的函数牵引着,因为内部函数的scope指向outer的AO,这就好比你手机上的微信一样,只要你还用得着,就不会把它卸载掉一样。也许你会问啦,那内部的function不会被释放吗?因为outer把内部的函数给return出来啦,所以此时的内部函数又被gerNum牵引着,故内部函数也释放不了,闭包就此形成。
image.png
6.当执行getNum()的时候,getNum就会被存放到ECS中,并且创造getNum的AO,但是因为getNum没有自己的局部变量i,当函数内部执行到console.log(i++)的时候,他就会沿着作用域链,往他的父集outer的AO中去寻找变量i,输出后并且执行i++,outer中的i就变了2
image.png
7.当执行到 i=1的时候,因为全局window中没有变量i,程序就会自动在全局中声明一个全局变量i,并且其值为1,此时并不会去操作outer函数AO中的局部变量i,所以outer执行环境中的变量i并不会受到污染。
以上就是对闭包的概念和形成原理的一些解释,希望可以帮助到一些需要的码友们。