JavaScript闭包

闭包是JavaScript很重要的知识点

闭包在JavaScript的世界中无处不在

掌握了它能够使我们对JavaScript理解的更加深入


闭包是基于词法作用域书写代码时所产生的必然结果

词法作用域大家可以理解为就是静态的作用域, 和它相对的是动态作用域

就是书写代码是时, 变量属于哪个作用域就已经定下来了


但其实闭包并不难, 只是被很多同学神话化了

其实我们写的程序中可能就有很多闭包, 只是你可能还不认识它

好了废话不多说, 现在我们来一起了解一下


首先给出闭包的定义:

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词法作用域之外执行


先来个简单的例子

function demo(){
    var a = 123;
    function foo(){
        console.log(a);
    }
    return foo;
}
var fn = demo();
fn();// 123

首先我定义了一个函数demo

demo里面声明了一个变量a和一个函数foo

demo返回了foo函数

在demo函数外声明了一个变量fn得到demo函数的返回函数引用

然后执行fn得到了结果123

这就是一个闭包小实例


当demo函数执行完毕后, 浏览器通常会想销毁内部作用域(引擎的垃圾回收机制)

但是fn把foo函数的引用保存了下来

使得作用域没有释放,所以可以取得a变量

fn的这个引用就叫做闭包


无论用什么手段把内部的函数传递到所在词法作用域的外部, 它都会紧握原有的作用域不放

无论在何处执行这个函数都会使用闭包


再看一个定时器的例子

function wait(msg){
    setTimeout(function timer(){
        console.log(msg);
    },1000)
}
wait('这就是闭包~');
setTimerout函数是一个定时器, 它的意思是1000ms之后执行timer函数
结果就是1s后浏览器打印了""这就是闭包~""

能看出来这是一个闭包吗?

再看看闭包的定义

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词发作用域之外执行

timer函数记住了他所在wait作用域的闭包

1s后浏览器引擎调用了这个函数

这个计时器函数可不是在那个位置上等1000ms再往下执行, 它并不是阻塞的

感兴趣的可以看看 JavaScript单线程

而是1000ms后, timer在wait函数外部执行的

这个过程中词法作用域保持了完整


JavaScript中函数是第一类公民

如果你将函数到处传递, 就能够看到很多闭包的应用

只要使用了回调函数, 实际上就是使用了闭包

回调函数就是当满足了一定条件时调用的函数执行

比如把函数当作参数传递到另一个函数这种高阶函数的使用


再来看一个循环产生闭包的例子

function demo() {
    var arr = [];
    for(var i = 0; i < 10; i++) {
        arr[i] = function () {
            console.log(i);
        };
    }
    return arr;
}
var retArr = demo();
for(var j = 0; j < retArr.length; j++) {
    retArr[j]();
}

输出结果是10个10

我们本来想要输出0~9

可是结果却和我们预期的不同


下面我们从闭包的角度来分析一下代码
在for循环中, 给arr数组保存了10个函数引用

但要注意,此时函数中的i还不知道是什么, 函数只有在执行时才会去查找作用域链去获取i

这个匿名函数持有涵盖demo函数的作用域

所以拆解for循环应该是这样的

arr[0] = function(){
    console.log(i);
};
arr[1] = function(){
    console.log(i);
}
arr[2] = function(){
    console.log(i);
}
//......


最后demo函数返回了一个数组包含了10个相同的函数引用

数组中是这样的

retArr = 
[function(){console.log(i)},
function(){console.log(i)},
function(){console.log(i)},
......]


这个数组保存在了变量retArr中

遍历这个数组,执行数组中每一个函数元素

此时每一个函数都会作用域链上去找i是什么

由于JavaScript中的词法作用域是函数作用域

i是属于demo函数的变量

此时的i经历了10次迭代, 已经变成了10

所以每一个函数都拿到了10

所有函数都共享一个i的引用

于是结果返回了10个10;

(@﹏@)~


那我们怎样解决这个闭包问题呢

上面的问题就是所有函数共享一个闭包作用域

不知道大家还记不记得IIFE(立即执行函数)

IIFE也是函数

JavaScript中只有函数作用域(暂且不考虑with等等)

IIFE可以创建作用域

所以我们来试试这个


function demo() {
    var arr = [];
    for(var i = 0; i < 10; i++) {
        (function(n){
            arr[n] = function () {
                console.log(n);
            };
        })(i);   
    }
    return arr;
}
var retArr = demo();
for(var j = 0; j < retArr.length; j++) {
    retArr[j]();
}


看, 输出了0 1 2 ... 8 9

嗯..这就是我们想要的(^-^)V

可是它是怎么解决闭包问题的呢

可能大家已经看明白了


加了一个立即执行函数

数组中的10个函数元素就不再共享一个闭包作用域了

而是各用各的, 谁也不打扰谁


我暂且给这个立即执行函数起名为IIFE

数组中确实还是存了10个长的一模一样的函数

但是它们查找的东西不同

每一个函数去查找作用域时找到IIFE

因为IIFE中保存着它们要的变量n

并且各自的n都不同

这样返回的值就是正确的值啦


其实就是

加立即执行函数之前是1对10的关系, 10个元素找的是那1个i

加立即执行函数之后是1对10对10的关系, 10个元素找了10个n


只要你愿意, 立即执行函数参数n也可以写成同名的i


立即执行函数解决了我们闭包的缺陷...


如果我们在做项目时发现, 发现bug找不到

调试发现连续输出多个相同值

那问题大概出在闭包了

解决问题的办法就是立即执行函数


一定要去理解这句话

只要使用了回调函数, 实际上就是使用了闭包


你可能感兴趣的:(JavaScript,函数,闭包,web前端,迭代)