JavaScript闭包

闭包是JavaScript很重要的知识点

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

掌握了它能够使我们对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找不到

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

那问题大概出在闭包了

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


一定要去理解这句话

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值