您的位置:首页 > Web前端 > JavaScript

【js学习笔记-048】-- 闭包

2013-08-13 23:49 288 查看

JS采用记法作用域(lexicalscoping),也就是说,函数的执行依赖于变量的作用域,这个作用域是函数定义时决定的,而不是函数调用时决定的。

为了实现词法作用域,js函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为"闭包"。

复习【作用域链】

l 同名变量,函数体内(包括参数名与全局变量同名)的优先于全局的。

l 声明提前(非正式说法),即js函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部。

l 作用域链:

js是基于词法作用域的语言:通过阅读包含变量在内的数行源码就能知道变量的作用域。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。

如果把局部变量看做是自定义实现对象的属性。可以换个角度来解读变量作用域。每段js代码(全局代码或函数)都有一个与之相关联的作用域链。这个作用域链一个对象或链表,这组对象定义了这段代码“作用域中”的变量,当js需要查找变量x值的时候,它会从链表中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,js会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推,如果在作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x.并抛出一个引用错误异常。

说明作用域链

在js的最顶层代码中,作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个对象定义了函数参数和局部变量的对象;第二个是全局对象。

一个嵌套的函数体内在作用域链上至少有三个对象 ( 个人理解第三个对象为新的作用域链对象)。当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用链上,

同时创建一个新的更长的表示函数调用作用域的“链”。

对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会被重新定义一遍。因此每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都有微妙的差别-----在每次调用外部函数时,内部代码相同,但关联这段代码的作用域链不相同(个人觉得这个句话要好好理解)。

从技术角度讲,所有js函数都是闭包:它们都是对象,它们都关联到作用域链。定义的大多数函数的作用域链在调用函数时依然有效,但这并不影响闭包。当调用函数时闭包所指向的作用域链和定义函数时的作用域链不是同一个作用域链时,事情就有了微妙的变化。

理解闭包首先要了解嵌套函数的记法作用域规则,如下

var scope = "global scope";

function checkscope(){

var scope ="local scope";

function f(){return scope;} //

return f();

}

checkscope() ; //==>"local scope"

我们清楚的知道该函数返回的是“local scope”,下面将代码稍做改动如下

var scope = "global scope";

function checkscope(){

var scope ="local scope";

function f(){return scope;} //

return f;

}

checkscope()(); //这个会返回什么???

回想一下记法作用域的基本规则:js函数的执行用到的作用域链,这个作用域链是函数定义的时候创建的。嵌套的函数f()定义在这个作用域链里,其中的变量scope定义的是局部变量,不管在何时执行函数f(),这种绑定在执行f()时依然有效。因此其返回值应该是"local scope",而不是"globalscope"。

说明(个人理解):嵌套在内部的函数在其嵌套的函数外部有调用,所使用的作用域 变量会保存下来。即函数定义时的作用域链,到函数调用时依然有效

简而言之,闭包的这个特性强大到让人吃惊,它们可以捕捉到局部变量(参数),并一直保存下来。

进一步说明:如果一个函数的局部变量定义在cpu的栈中,那么当函数返回时它们的确就不存在了。

但回想一下,我们之前定义的作用域链。我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用js函数的时候,都会为之创建一个新的对象对来保存局部变量,并把这个对象添加至作用域链中。当函数返回时,将这个变量从作用域链中删除。如果不存在嵌套函数,也没有其它引用指向这个对象,它就会被当做垃圾回收掉。如果定义了嵌套函数,每个函数都有各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。如果这些嵌套的函数对象在外部函数中没有保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。但如果这个函数定义了嵌套的函数,并将它做为返回值返回或存储在某处的属性里,这时就有一个外部引用指向这个嵌套的函数。它就不会被垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。

function counter(){

var n = 0;

return {

count:function(){return n++;},

reset:function(){n=0;}

}

}

var c = counter(), d = counter(); //创建两个计数器

c.count(); //=>0

d.count();//=>0

c.reset(); //

c.count();// =>0 重置c

d.count();//=>1 而没有重置d

counter函数返回一个“计数器”对象,这个对象包含两个方法:count()和reset()

首先要理解,这两个方法都可以访问私有变量n。再者,每次调用counter()都会创建一个新的作用域链和一个新的私有变量。因此,如果调用counter()两次,则会得到两个计数器对象,而且彼此包含不同的私有变量,

在同一个作用域链中定义两个闭包,这两个闭包共享同样的私有变量或变量。这是一种非常重要的技术,但还是要特别小心那些不希望共享的变量往往不经意间共享了。

理解如下两段代码:

function constfunc(v){return function(){return v;}}

var funcs = [];

for(var i=0;i<10;i++)funcs[i] = constfunc(i);

//funcs[5]() ; //==>5

这段代码是循环创建很多闭包,当写类似这种代码的时候往往会犯一个错误:那就是试图将循环代码移入定义这个闭包的函数之内,如下

fucntion constfuncs(){

var funcs = [];

for(vari=0;i<10;i++)funcs[i] = function(){return i;};

return funcs;

}

var funcs = constfuncs();

funcs[5]() //返回值是什么?

上述代码创建了10个闭包,并将它们存储到一个数组中。这些闭包都是在同一个函数调用中定义的,因此它们可以共享变量i。当constfuncs()返回时i值是10,所有闭包都共享这个值。所以这个不是我们想要的值。

记住嵌套函数不会将作用域内的私有成员复制一份,也不会对所绑定的变量生成静态快照。

书写闭包的时候还需要注意一件事情,this是js的关键字,而不是变量。每个函数调用都包含一个this。闭包无法直接访问外部函数的this。除非将外部的this转存为一个变量如

var self = this

绑定 arguments的问题与之类似。它不是关键字,但在调用每个函数时都会自动声明它,由于闭包具有自己所所绑定的arguments,因此闭包无法直接访问外部函数的参数数组。除非赋值给另一个变量如

var outerArguments = arguments;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: