定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包 (你不知道的JavaScript)
闭包是指有权访问另一个函数作用域中的变量的函数(JavaScript高级程序设计)
特点
让外部访问函数内部变量成为可能;
局部变量会常驻在内存中;
可以避免使用全局变量,防止全局变量污染;
会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
DEMO:
function foo() { var a = 2; return function fun1() { console.log(a) } } var fun2 = foo() fun2() // 2
在上面的例子中,fun1能够访问foo的内部作用域,我们把fun1作为一个值返回。在foo()执行后,把foo()的返回值 fun1 赋值给fun2并调用fun2。打印出了结果2.
此时,我们可以说fun1记住并访问了所在的词法作用域 或者说 fun2访问了另一个函数作用域中的变量(fun2在全局作用域中声明,访问了foo的内部作用域)
由于引擎有自动的垃圾回收机制,在foo()执行后(不再使用),通常foo的整个内部作用域会被销毁,对内存进行回收。闭包的神奇之处正是可以阻止这件事情的发生,因为fun1依然持有对该作用域的引用,这个引用就叫做闭包。
无论使用何种方式对函数类型的值进行传递,当函数在别处调用时,都可以看到闭包。
DEMO2
function add(x){ return function(y){ console.log(x,y) return x + y; }; } var addFun1 = add(2); var addFun2 = add(9); console.log(addFun1) // ƒ (y){ // console.log(x,y) // return x + y; // } console.log(addFun2) // ƒ (y){ // console.log(x,y) // return x + y; // } console.log(addFun1(2))//2、2、4 console.log(addFun2(2))//9、2、11
直接传递
function foo() { var a = 2; function baz() { console.log(a); } bar(baz); //对函数类型进行值得传递 } function bar(fn) { fn(); // 闭包产生了 }
或者间接的传递
var fn; function foo(){ var a = 2; function baz() { console.log(a); } fn = baz; //对函数类型进行值得传递 } function bar(fn) { fn(); //闭包产生了 } foo() bar()
无论通过什么手段将内部函数传递到所在的词法作用域之外,它都会保持对定义时作用域的引用,这个函数无论在何处执行,都产生了闭包。
现在你理解闭包了吗?思考一下你的代码中产生了哪些闭包?
无处不在的闭包
定时器
function wait(message) { setTimeout(function timer() { console.log(message) }, 1000) } wait('hello 闭包')
循环和闭包
思考下面的代码
for (var i=1;i<=5;i++){ (function(j){ setTimeout(function timer() { console.log(j) },j*1000) })(i) }
正常情况下,我们对这段代码的预期是每隔一秒输出一个数字,1-5。但是实际上并不会这样,这段代码会每间隔一秒输出一个数字6。这是为什么?
首先6从哪里来,当timer执行时,for循环早已结束,终止条件是i=6。
有些小伙伴可能会不明白了,timer不是创建了闭包,保持了对i的引用吗?
没错。for循环了5次,创建了5个闭包,但是都是保持了对同一个i的引用(i是全局变量)。所以当timer执行时,i = 6,输出6,没毛病。
那么如何达到我们想要的效果,我们需要更多的闭包。循环过程中每个迭代都需要一个闭包
for (var i=1;i<=5;i++){ (function(j){ setTimeout(function timer() { console.log(j) },j*1000) })(i) }
立即执行函数创建了一个新的作用域,使得延迟函数的的回调可以将新的作用域封闭在每个迭代内部。问题解决。