前言:
问君能有几多愁,恰似答过闭包后面试官摇摇头 -- 沃 · 兹机硕德
闭包这个东西 , 面试之中必备的问题之一 , JavaScript 最关键的底层原理之一 ,无论是从哪个角度上来说,如果想要进阶为一个资深jser ,那么你都需要完全理解闭包这个东西。现在在网上存在非常多的闭包教程,但是我个人的感觉大多都是些在半路上进行讲解的教程,即使背下来也没办法理解,故而在开发中或者在面试官连续的提问之中真的把问题说出个所以然,那么本篇教程的主要目的就是从根源上把闭包的一整套知识体系梳理给大家,所以这篇文章闭包是标题,但是我更认为这是对一整套JavaScript程序底层体系的讲解。
运行一个函数
函数大家肯定熟悉,要是这个东西你真的不理解的话,请先买本高级程序看看然后再来看这篇文章,你以为我要说函数 ? 不不不, 我说要说的是你知道函数在被调用时发生了什么不? 这就是一个函数:
function foo(){
var hello = "hello world";
console.log(hello);
}
// 调用函数
foo()
ok , 就这么一段代码他做了什么,我不用说,你也知道,不就是打印了 hello world 的一个简单函数么,对但是 too yuang too easy , 你知道咱们浏览器的解析器做了些啥不 ? 每段程序的执行都是硬件配合软件最终呈现的结果 , 这之中有个非常非常非常重要的东西 内存。
什么是内存那 ?
这货就是内存
你所有的代码在执行的时候都会被塞进这里,在本篇文章中你只需要理解到这里就可以了,如果需要继续理解我会再单独写一篇关于内存的专题。
ok 我们接着来说,往里面塞数据这个事,究竟怎么塞, 内部的结构是什么样的?
栈你可以理解为一个开口向上的容器,我们将数据在开口处放置进去,数据则自然下落到结构底部,所以数据在栈中的顺序是从下至上的,从底至上的数据下标分别为 0 , 1 , 2 ... 当数据已经用完了,程序发出指令需要清空栈内存之中的数据时我们删除的顺序是自顶部向下删除的,顺序为 ... 2 , 1 , 0 。由此我们可以得到规律栈内存数据我们遵循的数据是先进来的数据最后释放出去,我们称此为 FILO (first in last out), 简称先后出。如果你感觉这个文字看起来非常难以理解,请看这个动图,动图中黑色的为栈结构,红色的表示数据,能更直观的帮你去理解这个结构,当然内存并不只是有一种结构还有一种树形结构叫做堆,我们在此不赘述其原理,因为暂时用不到。
@w=200h=200
活动对象-AO(Active Object)
上文说到一个非常重要的概念,就是所有程序在执行时都会和内存产生关联,JavaScript之中函数执行的时候也会和内存产生很多关联, 这个关联我们现在也只关注一点,就是函数执行时我们内存里面会储存些啥。
当函数执行的时候,我们的程序为了记录函数内部的变量,所以会找一块空间对这部分数据进行一个临时的存储,在使用结束之后,如果没用了就删除掉这部分数据。这部分数据如果我们用JavaScript进行表达的话,最好的结构就是对象,我们以对象的形式对其进行存储和表示 :
我们再用代码去说明一下:
function foo(){
var hello = "hello world";
console.log(hello);
}
foo()
// 只要是foo() 这段代码被解析器解析了,那么调用内存这事就开始了。
// 1. 解析器会在内存之中创建一个临时活动对象:
var _temp_Active_Object_foo = {}
// 2. 解析器在解析内部代码 var hello = "hello world"时,会在这个对象上添加一个属性,hello 值为 "hello world";
_temp_Active_Object_foo.hello = "hello world";
// 3. 解析器在解析内部代码console.log(hello),检索到了hello变量的引用,虽然看起来我们是在使用hello变量可是实际上我们引用的是活动对象之中的属性:
console.log(_temp_Active_Object_foo.hello);
//4. 函数执行完毕,在栈内存之中释放掉这个活动对象
delete window._temp_Active_Object_foo
好了,这下我们把这个内容大概说清楚了,如果没有看明白建议仔细阅读流程图和代码,直到你把这两部分的内容完全记忆下来,这样你就可以继续进行下面的学习了,你距离学会整套闭包的体系还有一步之遥。
两个函数的嵌套
ok,根据前面的知识讲解,我们已经明白了一个函数执行起来配合内存会做哪些事情,可是总会感觉学习这些东西有啥用,其实你可以把这些知识模块当成是一个又一个的乐高积木我们正努力把这些东西组装成我们想要的结构。
回到主题,我们现在要去研究的就是两个函数的嵌套,外部函数我们取名 outer , 内部函数我们取名inner :
function outer(){
function inner(){
}
}
outer()
结构上大体定在这里,我们根据上面所学简单发散一下我们的思维,在外部函数执行的时候我们做了哪些事情。
function outer(){
function inner(){
}
}
outer()
//1.在内存之中创建一个活动对象
var _temp_Active_Object_outer = {};
//2.在这个活动对象上去放一个属性
_temp_Active_Object_outer.inner = function(){}
总结下这段代码 : 一个活动对象之中放入了一个函数(引用类型),而不再是具体的值 , 这又有什么关系那。你可不能忘了,这个活动对象是怎么生成的,是由函数生成的,也就是说这个活动对象之中存在一个可以创建活动对象的玩意。可以这么理解,平日里你家人给你发生活费的时候就一个红包,点一下收一次钱就可以了,这次给你发的红包点完开之后不仅显示收钱了,又给你弹出一个新的红包,上面写着个开,你要不要点 ? 点了红包就打开了潘多拉魔盒,发现了新大陆,原来红包还可以这么玩?
是啊,函数还可以这么玩。
好的让我们再进一步 :
function outer(){
//新增一个外部函数中的变量
var freeVar = "something"
function inner(){
//在内部函数引用freeVar
console.log(freeVar);
}
// 最后将这个结果返回
return inner
}
//在外部接受代码
var inner = outer()
其实在这个时候,我们已经使用了闭包,为什么那?简单来说闭包其实就是:
- 在外部函数声明的变量在内部函数被引用了
- 在外部函数的时候,把内部函数返回出去,在全局接收。
简单的理解先到这里,下次我会谈一谈关于闭包的实际应用及学习内存部分知识的意义。