前言
深入了解闭包和作用域链就需先了解函数预编译的过程
一、预编译
JavaScript:运行三部曲:
语法分析–预编译–解释执行
预编译:
发生在函数执行的前一刻。
函数声明整体提升,变量只声明提升。
1.函数预编译的过程:
1.创建AO对象Activation Object(执行期上下文,其作用就是我们理解的作用域,函数产生的执行空间库)
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3.将实参值与形参统一
4.找到函数声明,将函数名作为属性名,值为函数体。
例:
function test (a, b){ console.log(a); c = 0; var c; a = 3; b = 2; console.log(b); function b (){}; function d (){}; console.log(b); } test(1);
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
2.全局预编译它和函数预编译步骤一样,但它创造的是GO(全局对象):
1.生成了一个 GO 的对象 Global Object(window 就是 GO)
2.找变量声明…
3.找函数声明…
任何全局变量都是 window 上的属性
变量没有声明就赋值了,归 window 所有,就是在 GO 里面预编译。
例 :
function test(){ var a = b =123; console.log(window.b); } test(); 答案 a 是 undefined,b 是 123 先生成 GO{ b : 123 } 再有 AO{ a : undefined }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
想执行全局,先生成 GO,在执行 test 的前一刻生成 AO
函数里找变量,因为GO和AO有几层嵌套关系,近的优先,从近的到远的, AO里有就看 AO,AO 没有才看 GO。所以函数局部变量和全局变量同名,函数内只会用局部。
二、作用域精讲
作用域定义:变量(变量作用于又称上下文)和函数生效(能被访问)的区域
全局、局部变量
作用域的访问顺序:函数外面不能用函数里面的。里面的可以访问外面的,外面的不能访问里面的,彼此独立的区间不能相互访问。
1.[[scope]]: 每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
2.执行期上下文: 当函数在执行的前一刻,会创建一个称为执行期上下文的内部对象(AO)。
一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
3.作用域链:[[scope]]中所存储的执行期上下文对象的集合(GO和AO),这个集合呈链式链接,我们把这种链式链接叫做作用域链。
4.查找变量: 在哪个函数里面查找变量,就从哪个函数作用域链的顶端依次向下查找(先查自己的AO,再查父级的AO,一直到最后的GO)。
函数类对象,我们能访问 test.name
test.[[scope]]隐式属性——作用域
作用域链图解:
function a (){ function b (){ var bb = 234; aa = 0; } var aa = 123; b(); console.log(aa) } var glob = 100; a();
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
0 是最顶端,1 是次顶端,查找顺序是从最顶端往下查
在全局预编译中函数a定义时,它的[[scope]]属性中有GO对象。
在函数a执行前先函数预编译,创建自己的AO对象,并存储在[[scope]]属性上,与之前存储的GO成链式。同时函数b被创建定义。
在b被创建时,它生成的[[scope]]属性直接存储了父级的[[scope]],它有了父级的AO和GO。
b函数执行前预编译,生成自己的AO,存储在[[scope]]属性中。
详解过程: 注意[[scope]]它是数组,存储的都是引用值。
b 中 a 的 AO 与 a 的 AO,就是同一个 AO,b 只是引用了 a 的 AO,GO 也都是同一个。
function b(){}执行完,干掉的是 b 自己的 AO(销毁执行期上下文)(去掉连接线),下次 function b 被执行时,产生的是新的 b 的 AO。b 执行完只会销毁自己的 AO,不会销毁 a 的 AO。会退回到b被定义时(仍有父级的AO和GO)。
function a(){}执行完,会把 a 自己的 AO 销毁【也会把 function b的[[scope]]也销毁】,只剩 GO(回归到 a 被定义的时候),等下次 function a再次被执行时,会产生一个全新的 AO,里面有一个新的 b 函数。。。。。。周而复始。
思考一个问题:如果 function a 不被执行,下面的 function b 和 function c 都是看不到的(也不会被执行,被折叠)。只有 function a 被执行,才能执行 function a 里面的内容a();不执行,根本看不到 function a (){}里面的内容,但我们想在a函数外面调用b函数怎么办呢,于是闭包出现了。
三、闭包
闭包的定义
当内部函数被保存到外部时,将会生成闭包。但凡是内部的函数被保存到外部,一定生成闭包。
闭包的问题:闭包会导致原有作用域链不释放,作用域中的局部变量一直被使用着,导致该作用域释放不掉,造成内存泄露(就是占有过多内存,导致内存越来越少,就像泄露了一样)
例:
function a(){ function b(){ var b=456; console.log(a); console.log(b); } var a=123; return b; } var glob = a(); glob();
答案 123,456。
function a(){ }是在 return b 之后才执行完,才销毁。而return b 把 b(包括 a 的 AO)保存到外部了(放在全局)当 a 执行完砍掉自己的 AO 时(砍掉对AO存储地址的指针),因为b还保存着对a的AO的引用,所以内存清除机制不会清除掉a的AO, b 依然可以访问到 a 的 AO。
闭包的作用:
1.实现共有变量
function test(){ var num=100; function a(){ num++; } function b(){ num--; } return [a,b]; } var myArr=test(); myArr[0](); myArr[1]();
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
答案 101 和 100。
思考过程:说明两个用的是一个 AO。
myArr[0]是数组第一位的意思,即 a,myArr0;就是执行函数 a 的意思;
myArr[1]是数组第二位的意思,即 b,myArr1; 就是执行函数 b 的意思。
test doing test[[scope]] 0:testAO
1:GO
a defined a.[[scope]] 0 : testAO
1 : GO
b defined b.[[scope]] 0 : testAO
1 : GO
return[a, b]将 a 和 b 同时被定义的状态被保存出来了
当执行 myArr0;时
a doing a.[[scope]] 0 : aAO
1 : testAO
2 : GO
当执行 myArr1;时
b doing b.[[scope]] 0 : bAO
1 : a 运行后的 testAO
2 : GO
a 运行后的 testAO, 与 a doing 里面的 testAO 一模一样
a 和 b 连线的都是 test 环境,对应的一个闭包
2.可以做缓存(存储结构)
function eater(){ var food=""; var obj={ eat : function (myFood){ console.log("i am eating"+food); food =""; }, push : function (myFood){ food = myFood; } } return obj; } var eater1 = eater(); eater1.push("banana"); eater1.eat();
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
答案 i am eating banana,eat 和 push 操作的是同一个 food
在 function eater(){里面的 food}就相当于一个隐式存储的机构
obj 对象里面是可以有 function 方法的,也可以有属性,方法就是函数的表现形式
3.可以实现封装,属性私有化
只能调用函数方法,不能修改函数的属性。
4.模块化开发,防止污染全局变量
蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请扫码蓝小助,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系。
分享此文一切功德,皆悉回向给文章原作者及众读者.
转自:csdn
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务