日了哈士奇系列之JavaScript闭包

标题为什么叫日了哈士奇?因为闭包这个东西已经在我脑海里萦绕了很久,大概有多久呢?(掰手指头和脚指头ing….) 大概是笔者从事前端工作的第一个月开始吧……仍记得那个时候还请教了公司的大神,不过只怪当时脑袋不好使,反正就是没听懂啊喂!不过最近项目没那么忙,就回头研究这个前端亘古不变得话题之一:闭包。诚然,闭包对于JavaScript初学者而言理解起来稍许有点吃力,不过我相信即使是初学者看了本篇博客,也会被闭包有更深的认识(“王婆卖瓜,自卖自夸”),hahah…废话不扯了,赶紧开始吧。

话题引入

在开始介绍闭包之前呢,笔者先墨迹下。大家先来思考这样一个问题:

如何从
函数A外部访问
函数A内的变量?

不了解闭包的朋友可能会认为这是一个愚蠢得像土拨鼠系列的问题,就是一个字:扯淡 (手动滑稽)。即使这样子,笔者还是不知廉耻得贴出上一篇博客的地址,因为这篇博客对解决这个问题有很大的帮助。下面就一起思考如何回答上述的问题:
根据上篇博客的描述,要想访问一个变量,那么必定要在这个变量的作用域内访问;其次,对于访问函数中的变量,那只能在函数中做点事(手)情(脚),比如这样:

function closure() {
    let name = 'Husky';
    let age = 2;

    //access variable
}

那么既然已经在函数中访问到了,那又如何才能实现在函数外访问到呢?答案是肯定是,只要再做点事(手)情(脚)就好了,不过先不急着思考怎么办,下面给大家形象化一个非(丧)常(心)有(病)趣(狂)的情景:
隔壁老王啊是个房东,今天有个美女要租他的房子。美女真是一(胸)身(大)正(臀)气(翘),于是老王想借此机会观(偷)察(窥)一番。无奈每天晚上房间的门窗都被关得严严实实根本没从下眼,老王就偷偷在里面安装了摄像头,然后就肆无忌惮了…(手动滑稽)
情景描述完了,不过朋友们不要想入非非,毕竟笔者一身正气啊喂!
改一下上面的代码来将上述情景表达出来:

function Room() {
    let beauty = '如花';

    //摄像头
    
}

那么摄像头可以在房间里捕(访)捉()房()间()内()的影()像(),同时还能把捕捉到的情景传回到老王的屏幕上。因此可以联想到,我们在函数中放置一个对象然后在对象中访问变量并且把这个对象return出去,我们再接收返回回来的对象后不就可以在外面访问变量了嘛!Bingo…就是这样。
俗话说,JavaScript世界中万物皆对象(函数也是对象)。所以我们可以这样补充上面的代码:

function Room() {
    let beauty = '如花';

    //摄像头
    let camera = function() {
        return beauty;
    }
}

这样我们就能获取到一个函数,相当于摄像头的信号线,通过这个就可以获取到影像咯:

function Room() {
    let beauty = '如花';

    //摄像头
    let camera = function() {
        return beauty;
    }

    return camera`请输入代码`;
}

let camera = Room();

let beauty = camera();

console.log(`老王看到 ${beauty} 啦`);

看下运行结果:

《日了哈士奇系列之JavaScript闭包》

然后我们就帮助老王如愿以偿得看到了如花并且继续每晚不可描述的事。(看我一脸正气…)

说到这,闭包这个玩意儿已经浮出水面。

闭包

闭包,目前还没有统一的定义。单就笔者的理解就是:闭包是一个体系,由函数体中的变量和访问该变量的函数组合而成。

对应上述的代码就是:beautycamera
闭包只是这类体系的名字,其英文名是Closure;闭包其实并没有关闭的意思,相反它还向外暴露内部的变量只不过是通过一定的约束方式(函数)。举个例子:

function closure(){
    let fronted=['js','css'];

    return function(){
        return fronted;
    }
}

let fronted = closure()(); <----运行两次,分别运行closure和返回的匿名函数

console.log(fronted);
fronted.push('html');
console.log(fronted);

我们在函数中声明一个数组并且初始化它,最后通过匿名函数(推荐使用匿名函数)返回。这样我们可以在函数外面获得内部的数组并且可以进行一些操作,如上运行结果如下:

《日了哈士奇系列之JavaScript闭包》

Chrome浏览器是如何来识别一个闭包的

不同的浏览器对闭包也有不同的理解。下面笔者使用世界上最好的浏览器Chrome浏览器来演示下它是如何去识别一个闭包的,代码还是用的上面那个代码:

首先我们要打两个断点

《日了哈士奇系列之JavaScript闭包》

第一步运行,并且请观察截图中红框框的地方

《日了哈士奇系列之JavaScript闭包》

第二步运行,并且也请观察截图中红框框的地方,比较和上一步的不同

《日了哈士奇系列之JavaScript闭包》

没错,多了一个“Closure”对象 并且指向了(closure)这个函数,说明到这里Chrome已经识别出了这是一个闭包。展开这个对象:

《日了哈士奇系列之JavaScript闭包》

果然是世界上最好的浏览器,给我们展示了闭包中访问的变量。神器啊有木有…

闭包的原理

说了那么多,那么闭包的原理到底是什么呢?且听我慢慢道来。
看过我上一篇博客的朋友都知道,想访问一个变量那么必须要处在这个变量的作用域中。比如fronted这个变量在closure函数中定义,那么closure函数中的匿名函数就可以访问这个fronted变量。因为只能访问父域或者同域的变量。当匿名函数在自己内部访问了这个变量的时候就相当于持有了该变量的引用,所以当和这个匿名函数被 return 出去的时候,仍可以访问该变量。

闭包的能用来干什么

技术的出现时为了解决一类问题,闭包能用来干什么呢?

隐藏变量

通过闭包,我们可以向外界输出一个变量,不过并不是直接暴露出去而是通过类似于接口的函数向外暴露,这样既可以在外界访问这个变量又保证了这个变量不会被破坏。

模块化的始祖

据笔者所了解,JavaScript模块化就是借用闭包来实现的。通过定义一个module变量,并且向外暴露相关操作module的方法,来实现导入和导出。

使用闭包的注意点

通常来说,当一个函数运行结束后,函数中的变量内存会被回收掉,但是上一篇博客特地强调了闭包并不会这样。实际上如例子所示,即使closure函数运行完了,但是它返回的匿名函数中仍持有变量fronted的引用,因此这个变量会一直存在于内存中并且不会被回收(初非人为解除引用)。因此可以预见性得猜到,如果我们在程序中大量得使用闭包,那么大量的变量存在于内存中而占用前端内存资源会导致性能的下降。所以我们在开发过程用要慎用闭包。

    原文作者:风吹过的夏夜
    原文地址: https://segmentfault.com/a/1190000015833838
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞