被教育了之后跑过来翻犀牛书了- -
之前被问到一个问题,到底什么是闭包,如何理解和使用。
其实说实话,很模糊啊,我回答的很白痴,说函数就是闭包,说什么自己的一个独立内存空间,说什么可以保护局部变量,说什么不会被自动注销。其实根本没说到点子上或者说闭包真正强大的地方。最后教育者说犀牛书上有答案。
跑回来复习一下,说实话,犀牛书很久没看了,刚买的时候看了不少,但是一到高级用法就自动跳过……= =||
第8.8节讲的是函数作用域和闭包。
什么是作用域。我当时回答的很白痴- -。就是一个函数他能作用到的区域……= =||
好吧,我真的不是学院派……
小麦说javascript里的作用域是词法作用域。再形象一点,就是定义的时候,他的作用域就是被写好了的,而不是像别的语言,动态作用域,执行到你的时候给你开辟一个作用域。比如java?这里就又涉及到一个编译和执行的问题,我当时的理解其实很简单,我解释说作用域就是都有上下文,都有个parent。。- -||
犀牛书中说:
当定义了一个函数,他的作用域连就被保存了起来,并且成为函数内部的一部分。在最顶层,作用域链仅由全局对象组成,而并不和词法作用域相关。然而,当定义一个套嵌函数的时候,作用域链就包含了外围的包含函数。这意味着套嵌的函数可以访问包含函数的所有参数和局部变量。
注意:作用域链是活的,并且在函数被调用的时候,可以访问任何当前的绑定。
其实我理解的差不多,就是一个函数,定义的时候根据他的上下文,他的作用域就固定了,如果函数套函数的话,内部函数可以访问外部的变量和参数,当函数被调用,可以访问当前的对象的this等。
然后我再理解一下什么是函数调用对象。
ECMA里所说,当js解释器调用一个函数的时候,首先固定到他的定义时候的作用域,之后再在作用域的前面增加一个对象。叫call object,就是调用对象,包括了传入的参数,也就是Arguments对象。
再然后8.8.3说的很简单了,就是使用函数和匿名函数来控制变量的作用域,如(function(){})()的作用。
下面就是闭包了……
首先,函数是有返回值,而且返回值可以是函数的。这就让函数有了强大的力量。当你理解了作用域之后。
闭包我找到了书中我觉得最重要的一个解释:
有一个对套嵌函数的外部引用,并且套嵌的函数将它的引用保留给外围函数的调用对象。结果是,外围函数的一次特定调用的调用对象依然存在,函数的参数和局部变量的名字和值在这个对象中得以维持。
javascript函数是将要执行的代码以及执行这些代码的作用域构成的一个综合体。在计算机科学术语里,这种代码和作用域的综合体叫闭包。所有的javascript函数都是闭包。但是,当一个套嵌的函数以这种方式被使用【就上面那段那种方式】的时候,常常明确的叫做一个闭包。
然后8.8.4.1介绍真正强大的闭包的例子,我想在举他的例子之前,先举一个我回答小麦的例子- -||
事件循环绑定的时候,我们如果要保存当前循环到的index值,这里就要使用闭包。
创建一个匿名函数,在一个循环函数中,然后我们就满足了上面的条件,我们定义的++的index值,再每次加完之后被匿名函数所产生的真正闭包所保存,得以在事件触发时,就可以访问到~
这个算是我经常用闭包的一个地方。
然后看下书上更经典的几个例子。
书上第一个例子类似我那个循环的例子,直接跳过。。
第2个例子,是使用闭包创建一个真正私有的属性。
我把他的代码理解了一下,简单写出来。
var o={}
o.name='xiaojue'
那么我们访问这个o.name
可以得到xiaojue,但是这个name完全可以被任何人修改,覆盖,删除。
function makeProperty(o,name,predicate){
var value;
o['get'+name]=function(){return value;}
o['set'+name]=function(v){
if(predicate && !predicate(v))
throw "set"+name+":invalid value "+v;
else
value=v;
}
}
var o={};
makeProperty(o,"Name",function(x){return typeof x=="string";});
o.setName("xiaojue");
console.log(o.getName()); //xiaojue
console.log(o.getName); //function()
o.setName(0);
console.log(o.getName()); //setName:vinvalid value 0
最后的代码输出我给改了一下。
首先这个函数给任何一个带进去的对象,比如o,创造了2个方法,用以set和get属性值,并且作用是这个属性值的设置是使用私有接口的,就是必须要用get+XX和set+XX才可以,进而又设置了一个类型判断,必须是字符串,如果是数字就报错。
闭包用在哪了呢?哦。对呀,我们访问o.getName根本访问不到值,返回的是个函数。那么执行了才返回,这说明了什么?xiaojue根本没有存在对象o上呀!他存在了这个套嵌函数的闭包中。。实现了维持属性和私有。必须使用他的方法才可以设置,修改,并且增加了校验机制。
这个私有的维持属性变量就是value了。
果然闭包很强大。。这样一来其实看起来是,对o对象做了一些事情,其实,那些东西根本没有存在o上,而是存在了makeProperty的闭包里。
恩,更精彩的再后面,下面又说了一个例子,是对javascript打断点的一种闭包应用。
我可以说,之前我根本不知道这个方法。。和还有这个作用。。例子2其实我之前也是知道的。。
先说下什么是断点,用过firebug的都知道了,断点就是可以在任何位置阻止代码的执行,然后查看其中上下文作用域的变量名和值。
那么先看下如何使用:
//一个循环函数||书上是乘介,我修改成简单的循环相加了,其实一个道理。。
function foradd(s,n){
var inspector=function($){return eval($)};//这其实就是一个闭包,可以通过把他传入另外一个inspect函数里,实现断点。我在后面解释吧。
inspect(inspector,"begin add");
for(var i=0;i<n;i++){
s+=i;
inspect(inspector,"now loop");
}
inspect(inspector,"end add");
return s;
}
foradd(1,5);
下面是inspect函数的实现:
function inspect(inspector,title){
var expression,result;
if("ignore" in arguments.callee) return; //如果在参数中带一个ignore,就可以忽略掉这个断点。
//这里使用循环是为了让prompt弹出框不断的出现,直到内部return退出。
while(true){
var message="";
if(title) message=title+"\n";
if(expression) message +="\n"+expression+"==>"+result+"\n"; //累计输出
else expression="";
message+="Enter an expression to evaluate";
expression=prompt(message,expression);
if(!expression) return;
result=inspector(expression); //使用传进来的eval,访问一些变量
}
}
这里简单说一下,执行之后的结果我在firefox4.0里没有看到,可能是不允许eval这么做了还是什么机制,和prompt也没有关,要不就是我打错了代码。。。
但是其实看代码我们可以知道原理,和这货与闭包的关系吧。
先说这个inspect函数。它被传入inspector这个eval的执行器。我们可以输入一些函数内部的变量,利用prompt中断函数的执行,内部循环累加执行expression,把结果都打在prompt的标题框上~就达到了断点查看内部变量的实现。
这些代码事Steve Yen所发明的断点工具,发布在trimpath.com上。
Steve的断点技术使用一个闭包【inspector】来扑获一个函数中的当前作用域(包括局部变量和函数的参数),并将它与全局的eval函数组合起来,从而允许查看作用域。eval得到js代码的一个字符串并返回其结果。
看看inspector这个函数,是var在foradd里的,然后返回了一个全局的eva函数,不正是又符合最开始上面说的js里的闭包的要点了么~
唉。。。我也不知道自己理解的对不对。总之,先写了吧。希望对自己和一些其他也想理解js的闭包和函数作用域的人有所帮助……
(收起)