{(MainTitle)}JS小记:闭...闭包?-Bottle小站{(MainTitleEnd)}{(PostTitle)}JS小记:闭...闭包?{(PostTitleEnd)}{(PostDate)}20210912{(PostDateEnd)}{(PostContent)}最近在重打JavaScript基础...看廖雪峰博客的基础教程时看到了闭包这个字眼,但有点意思的是文章并没有更进一步去探讨闭包到底是什么,而是列出代码让大家去体会。 但既然是打基础,我觉着有疑惑的概念还是得摸清楚为好,遂决定写这样一篇小笔记。有可能会有理解错误的地方,各位请多指教。 <bblock style="display:none;"> { "title": "Masquerade", "artist": "Lindsey Stirling", "src": "https://x.bottle.moe/ne/url/1390430684", "cover": "https://x.bottle.moe/ne/cover/1390430684", "float": "right" } </bblock> ![FigureItOut](https://cdn2.imbottle.com/uploads/20210912closure/figureout.jpg!/scale/40) ## 0x01 我觉得闭包是... 闭包(Closure)的话其实一个简单的代码示例就能展示出来: ```javascript function test() { var testvar = 1; function innerTest() { console.log(testvar); } innerTest(); } test(); ``` **testvar**是**test()**函数作用域里的一个**局部变量**,而**innerTest()**这个函数可以访问到testvar这个**test()作用域内的局部变量**。 也就是说**testvar**这个变量对于**innerTest()**来说是外部的变量,而innerTest这个函数内部却又能**访问到**这个变量,由此,**innerTest**这个函数和其所访问的这个**testvar**变量便**形成**了一个闭包,innerTest就是一个**闭包函数**。 说起**形成**,我觉得闭包其实有点偏向一个**动词**,指的是**一个函数访问另外一个函数的_函数作用域_中的局部变量的过程**。 之所以中文翻译里有个**“闭”**,我认为是因为这样把变量给“闭”进去了,除非调用**能访问到这个变量的函数**,没有其他办法能改变这个变量,比如下面这个例子: ```javascript function test() { var testvar = 1; function innerTest() { console.log(testvar); testvar++; } return innerTest; } var inner = test(); inner(); //1 inner(); //2 inner(); //3 inner(); //4 ``` 除了调用test函数返回的作用域中的函数innerTest外,无法对testvar这个局部变量进行修改。 关于这个例子我整了个小动图(做的比较仓促): ![Closure](https://cdn2.imbottle.com/uploads/20210912closure/closure.gif) 了解到这些我再回头看了一下,发现本博客程序-O-[初始化源码](https://github.com/SomeBottle/-O-/blob/master/jcs/i.js#L106)中也是有用到闭包的: ![Closure of -O-](https://cdn2.imbottle.com/uploads/20210912closure/blogcode.jpg) 也就是说其实在很多不经意的时候我已经在写JavaScript代码时使用了所谓的“闭包”。为什么会这样呢?其实更进一步想,在看这些函数和变量的过程中我们脑中总是会模拟**变量作用的范围**。没错,更为重要的是**JS的作用域**相关的知识! 网上搜索了一下,闭包从来不是某个语言独有的概念,不如说是一种现象的统称。既然我在重摸JavaScript基础,其实打好作用域的知识便也足够了~ ## 0x02 “脑洞大开”一下 廖雪峰的这篇[闭包相关的JavaScript教程](https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016)还提到了个有趣的脑洞:只使用函数来完成简单的计算。 第一眼看上去心生疑惑,这算出来是什么单位呢...看到后面,原来是函数的嵌套重复次数。我把参数名改的详细了点,试试看能不能增加点可读性: ```javascript // 定义数字0: var zero = function (func) { return function (x) { return x; } }; // 定义数字1: var one = function (func) { return function (x) { return func(x); } }; // 定义加法: function add(para1, para2) { return function (func) { return function (x) { return para1(func)(para2(func)(x)); } } } ``` 这三个函数里后两个都非常典型地形成了闭包,因为**zero函数**内部的函数没有调用局部变量(参数func),所以zero内未形成闭包,由此通过参数传入的函数不会被执行。 暂且当这里的x参数是为了增加可读性的<del>(不过我是真的给这个x看花眼了)</del>,再改一下: ```javascript var zero = function (func) { return function () { return ''; } }; // 定义数字1: var one = function (func) { return function () { return func(); } }; // 定义加法: function add(para1, para2) { return function (func) { return function () { return para1(func)(para2(func)()); } } } ``` 很明显能发现,add函数内部嵌套的第一层匿名函数往后**function(func)**其实就是zero,one这类函数的构造模式。接下来咱试着算一下**"1"+"1"** ```javascript var two = add(one, one); ``` 实际上就等价于: ```javascript var two = function (func) { var exec_one = one(func); return function () { return exec_one(exec_one()); } }; var exec_two = two(function () { console.log('print 2 times'); }); exec_two(); //输出两次 ``` 所谓的输出两次其实就是把one函数再次执行嵌套了一次。可想而知如果再算一下**"1"+"2"**,得出的three函数核心代码将会是```exec_one(exec_one(exec_one()));```,<del>俗称套娃</del> 如果把x给还原进去,就相当于从零开始不停加1: ```javascript var exec_two = two(function (val) { return val + 1; }); console.log(exec_two(0)); //输出2 ``` 虽然但是,读清楚这段代码真的耗了我好长时间...看来用了太多return和闭包的结果就是代码可读性会降低,所以凡事要适度嘛... ![Yeah](https://cdn2.imbottle.com/uploads/20210912closure/yeah.jpg!/scale/40) 最后要感叹一下,我竟然把这篇博文写出来了,也是第一次尝试写这种小笔记~今后随着学习的感悟变多,我有可能会持续更新下去~加油吧!不要停滞不前! <script>/*bblock.s();*/</script>{(PostContentEnd)}{(PostTag)}咸鱼技术,JavaScript,学习,小记{(PostTagEnd)}{(PostID)}274{(PostIDEnd)}{(PostCover)}none{(PostCoverEnd)}
{(PageType)}post.otp.html{(PageTypeEnd)}