闭包这个东西,对js新手们来说确实不好理解,我自己在学习的时候,不夸张,看了不下20篇网上讲闭包的帖子,js的参考书也看了一堆,这里看懂点,哪里看懂点,最后综合起来,总算是搞清楚了。
在我自学的过程中,我觉得网上的帖子要么太晦涩,要么没讲清楚,总之就是没有一篇特别适合新手们学习的帖子,我今天就结合各种实例仔细讲解(我把w3cfun 中之前 讲解闭包的精华帖中的例子都拿出来讲解,因为我觉得之前的帖子没有把实例讲解清楚,我当初看时也不懂为啥会是这样的运行结果),也当是自己复习梳理。
我相信,看完我的帖子,你再去看 为之漫笔 先生翻译的《理解javascript闭包》,就不会那么吃力了(我刚开始最少看了10遍也不懂)
献给新手,老鸟们请飘过!! 文章中可能会有用词不严谨的地方,但是保证新手们一定能看懂。
ok,废话不多说,进入正题。
就想大家在n多讲解闭包帖子中看到的,要搞懂这个东西,作用域、执行环境(也叫运行上下文),作用域链的概念是必须先懂的,这个是前提,这个不懂,后面的就是在扯淡~~
第一个实例 (这也是之前精华帖中的实例,请大家仔细看我下面的分析,会很长,但是会说明白原理)
function outerFun()
{
var a =0;
alert(a);
}
var a=4;
outerFun(); //0
alert(a); //4
明白代码在执行前会预编译,然后再执行是关键。
outerFun();
alert(a);
上面这两行代码在运行之前,js的后台编译器会干下面的事情:
编译器会先看看在全局代码中有没有 var关键字定义的变量和函数声明 , 我们这个例子中是有的,所以就在全局对象(也就是window对象)添加上 相应的属性,具体到我们的例子,现在的全局对象就是下面的样子(我觉得这样写大家肯定能看懂,我第一次看到都懂了)
备注:( vo : Variable Object) 活动对象
globalContext.VO(这个就是全局对象的意思) = {
a: undefined
outerFun: <reference to function 这里是对象的意思 >
};
记住,在预编译阶段,所有的var关键字定义的变量,都被赋值:undefined
然后,把这个 globalContext.VO存入到函数 outerFun的内部属性中去,这个内部属性就叫做:[[scope]]
预编译就结束了,就开始执行了。
毫无疑问,执行的时候,会从上到下执行吧,所以
var a=4 第一个被执行,发生标识符解析,因为这个代码是在全局环境中,所以就到全局对象中找下有没有a这个符号,
发现globalContext.VO中有个叫 a的属性,在预编译的时候 globalContext.VO.a=undefined,所以 马上执行, globalContext.VO.a变成了4.
然后就轮到函数 outerFun 执行了,好,让人糊涂的事情又来啦。
在执行outerFun() 时,js会为这段代码创建一个 : 执行环境 (也叫执行上下文),然后函数outerFun中的代码就在这个执行环境中被执行。 这个执行环境在被创建时,会发生下面的事情:
一个叫做 活动对象(简称:AO) 的 家伙被建立了,这个家伙比较特殊,它拥有可访问的命名属性,但是它又不像正常对象那样具有原型(至少没有预定义的原型),而且不能通过 JavaScript 代码直接引用活动对象。
这个活动对象会看看函数outerFun 里面有没有下面这3样东西:
1 var关键字定义的变量
2 函数声明
3 函数形式参数
我们的例子中,是有var关键字定义的变量的,所以它会给自己添加个属性a,然后同样赋值:undefined 。
可以这样理解: AO.a=undefined.
然后就会为函数outerFun 的执行环境分配作用域链,
这个作用域链是这个样子的:outerFun.AO——outerFun的[[scope]]
意思就是:outerFun函数的活动对象在最前面,然后就是outerFun函数在被定义时保持在它内部的 [[scope]] 属性。
这也是我们老在网上看到的,javascript权威指南中老说的一句话:”JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.”
函数 outerFun的 [[scope]] 属性在预编译的时候就填入好了嘛,后面不管outerFun在哪里运行(调用),这个 [[scope]] 属性都不会变。
呼~~ 准备工作终于全部完毕了,就开始正式执行代码内啦!
首先执行这句 var a =0; 发生标识符解析,
js会首先在outerFun函数的活动对象中看看有没有a这个符号,如果没有,就到outerFun的 [[scope]] 中去找,outerFun的 [[scope]] 中又存入的是它定义时的 globalContext.VO,
所以在目前的情况下 这个标识符解析查找顺序就是 outerFun.AO——globalContext.VO
很显然,a符号在outerFun.AO 就被找到了,所以a立刻被赋值为0 ,变成这个样子:outerFun.AO .a=0;
然后就执行alert(a),a标识符被解析,同样执行一遍查找:outerFun.AO——globalContext.VO
outerFun.AO中找到a,值为0
所以 alert(a)会弹出0.
outerFun 函数就执行完了,然后执行 outerFun()后面的那句 alert(a)
a标识符解析,因为句代码是定义在全局环境中的,同理,a符号只能在globalContext.VO中找吧,
找到了,globalContext.VO.a=4.
所以这个alert(a)就弹出4.
ok 原理就是这样,下面来大量的看例子吧。
function outerFun()
{
//没有var
a =0;
alert(a);
}
var a=4;
outerFun(); //0
alert(a); //0
同样,先预编译,globalContext.VO和上面的一摸一样。
预编译完毕,执行var a=4,标识符解析,在globalContext.VO中找到a,执行赋值,完毕后:globalContext.VO.a=4
执行函数 outerFun(),创建执行环境,分配作用域链(同样还是outerFun.AO——globalContext.VO)
前面说了,函数 outerFun 的活动对象会在它自己内部查看3样东西:
1 var关键字定义的变量
2 函数声明
3 函数形式参数
这里都没有吧!!(a =0 这里的a没有用var定义,所以outerFun.AO中就没有a这个属性了)
然后开始 执行代码:a =0;标识符解析:先在outerFun.AO中找,没找到,就跑到globalContext.VO中找,找到了,发现之前有个值是4, 然后就修改globalContext.VO的值,变成这样:globalContext.VO.a=0;
这里非常重要,解释了为啥在函数内部定义的变量不用var 关键字定义就是全局变量的意思,而且还会修改到全局变量中同样名字的属性值。
然后执行代码:alert(a),标识符解析:outerFun.AO中没有,globalContext.VO中找到,值为0
所以弹出0
outerFun函数执行完毕,执行下面的alert(a) 标识符解析:同理,代码定义在全局环境中,只能在globalContext.VO中找,找到 值为0, 弹出0
先写到这,分析的肯定是透彻的,所以长了点。
看看大家反应如何!!! 后面的帖子好继续
在我自学的过程中,我觉得网上的帖子要么太晦涩,要么没讲清楚,总之就是没有一篇特别适合新手们学习的帖子,我今天就结合各种实例仔细讲解(我把w3cfun 中之前 讲解闭包的精华帖中的例子都拿出来讲解,因为我觉得之前的帖子没有把实例讲解清楚,我当初看时也不懂为啥会是这样的运行结果),也当是自己复习梳理。
我相信,看完我的帖子,你再去看 为之漫笔 先生翻译的《理解javascript闭包》,就不会那么吃力了(我刚开始最少看了10遍也不懂)
献给新手,老鸟们请飘过!! 文章中可能会有用词不严谨的地方,但是保证新手们一定能看懂。
ok,废话不多说,进入正题。
就想大家在n多讲解闭包帖子中看到的,要搞懂这个东西,作用域、执行环境(也叫运行上下文),作用域链的概念是必须先懂的,这个是前提,这个不懂,后面的就是在扯淡~~
第一个实例 (这也是之前精华帖中的实例,请大家仔细看我下面的分析,会很长,但是会说明白原理)
function outerFun()
{
var a =0;
alert(a);
}
var a=4;
outerFun(); //0
alert(a); //4
明白代码在执行前会预编译,然后再执行是关键。
outerFun();
alert(a);
上面这两行代码在运行之前,js的后台编译器会干下面的事情:
编译器会先看看在全局代码中有没有 var关键字定义的变量和函数声明 , 我们这个例子中是有的,所以就在全局对象(也就是window对象)添加上 相应的属性,具体到我们的例子,现在的全局对象就是下面的样子(我觉得这样写大家肯定能看懂,我第一次看到都懂了)
备注:( vo : Variable Object) 活动对象
globalContext.VO(这个就是全局对象的意思) = {
a: undefined
outerFun: <reference to function 这里是对象的意思 >
};
记住,在预编译阶段,所有的var关键字定义的变量,都被赋值:undefined
然后,把这个 globalContext.VO存入到函数 outerFun的内部属性中去,这个内部属性就叫做:[[scope]]
预编译就结束了,就开始执行了。
毫无疑问,执行的时候,会从上到下执行吧,所以
var a=4 第一个被执行,发生标识符解析,因为这个代码是在全局环境中,所以就到全局对象中找下有没有a这个符号,
发现globalContext.VO中有个叫 a的属性,在预编译的时候 globalContext.VO.a=undefined,所以 马上执行, globalContext.VO.a变成了4.
然后就轮到函数 outerFun 执行了,好,让人糊涂的事情又来啦。
在执行outerFun() 时,js会为这段代码创建一个 : 执行环境 (也叫执行上下文),然后函数outerFun中的代码就在这个执行环境中被执行。 这个执行环境在被创建时,会发生下面的事情:
一个叫做 活动对象(简称:AO) 的 家伙被建立了,这个家伙比较特殊,它拥有可访问的命名属性,但是它又不像正常对象那样具有原型(至少没有预定义的原型),而且不能通过 JavaScript 代码直接引用活动对象。
这个活动对象会看看函数outerFun 里面有没有下面这3样东西:
1 var关键字定义的变量
2 函数声明
3 函数形式参数
我们的例子中,是有var关键字定义的变量的,所以它会给自己添加个属性a,然后同样赋值:undefined 。
可以这样理解: AO.a=undefined.
然后就会为函数outerFun 的执行环境分配作用域链,
这个作用域链是这个样子的:outerFun.AO——outerFun的[[scope]]
意思就是:outerFun函数的活动对象在最前面,然后就是outerFun函数在被定义时保持在它内部的 [[scope]] 属性。
这也是我们老在网上看到的,javascript权威指南中老说的一句话:”JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.”
函数 outerFun的 [[scope]] 属性在预编译的时候就填入好了嘛,后面不管outerFun在哪里运行(调用),这个 [[scope]] 属性都不会变。
呼~~ 准备工作终于全部完毕了,就开始正式执行代码内啦!
首先执行这句 var a =0; 发生标识符解析,
js会首先在outerFun函数的活动对象中看看有没有a这个符号,如果没有,就到outerFun的 [[scope]] 中去找,outerFun的 [[scope]] 中又存入的是它定义时的 globalContext.VO,
所以在目前的情况下 这个标识符解析查找顺序就是 outerFun.AO——globalContext.VO
很显然,a符号在outerFun.AO 就被找到了,所以a立刻被赋值为0 ,变成这个样子:outerFun.AO .a=0;
然后就执行alert(a),a标识符被解析,同样执行一遍查找:outerFun.AO——globalContext.VO
outerFun.AO中找到a,值为0
所以 alert(a)会弹出0.
outerFun 函数就执行完了,然后执行 outerFun()后面的那句 alert(a)
a标识符解析,因为句代码是定义在全局环境中的,同理,a符号只能在globalContext.VO中找吧,
找到了,globalContext.VO.a=4.
所以这个alert(a)就弹出4.
ok 原理就是这样,下面来大量的看例子吧。
function outerFun()
{
//没有var
a =0;
alert(a);
}
var a=4;
outerFun(); //0
alert(a); //0
同样,先预编译,globalContext.VO和上面的一摸一样。
预编译完毕,执行var a=4,标识符解析,在globalContext.VO中找到a,执行赋值,完毕后:globalContext.VO.a=4
执行函数 outerFun(),创建执行环境,分配作用域链(同样还是outerFun.AO——globalContext.VO)
前面说了,函数 outerFun 的活动对象会在它自己内部查看3样东西:
1 var关键字定义的变量
2 函数声明
3 函数形式参数
这里都没有吧!!(a =0 这里的a没有用var定义,所以outerFun.AO中就没有a这个属性了)
然后开始 执行代码:a =0;标识符解析:先在outerFun.AO中找,没找到,就跑到globalContext.VO中找,找到了,发现之前有个值是4, 然后就修改globalContext.VO的值,变成这样:globalContext.VO.a=0;
这里非常重要,解释了为啥在函数内部定义的变量不用var 关键字定义就是全局变量的意思,而且还会修改到全局变量中同样名字的属性值。
然后执行代码:alert(a),标识符解析:outerFun.AO中没有,globalContext.VO中找到,值为0
所以弹出0
outerFun函数执行完毕,执行下面的alert(a) 标识符解析:同理,代码定义在全局环境中,只能在globalContext.VO中找,找到 值为0, 弹出0
先写到这,分析的肯定是透彻的,所以长了点。
看看大家反应如何!!! 后面的帖子好继续