1.变量在声明他们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。eg:
alert(tmp);
var tmp=123;//未定义而不会报错
2.作用域链的一个经典例子:
name="lwy";
functiont(){
var name="tlwy";
function s(){
var name="slwy";
console.log(name);
}
function ss(){
console.log(name);
}
s();
ss();
}
t();
在上述代码中第一个输出slwy,第二个输出是tlwy,原因是:当执行js时,将创建函数的执行环境(调用对象),并将该对象置于链表开头,然后将函数的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显name是”slwy”。
但在执行ss()时,作用域链是:ss()->t()->window,所以name是”tlwy”。
说到作用域链,不得不说with语句。with语句主要用来临时扩展作用域链,将语句中的对象添加到作用域的头部。eg:
person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};
with(person.wife){
console.log(name);
}
with语句将person.wife添加到作用域链的部,所以输出的是:”lwy”;
with语句结束后,作用域链恢复正常。显然with语句存在着将对象copy并移动到作用域的头部的过程,所以产生了性能问题。
3.作用域链:简单的说作用域链就是函数在定义的时候创建的,用于寻找使用到的变量的值的一个索引,而他内部的规则是,把函数自身的本地变量放在最前面,把自身的父级函数中的变量放在其次,把再高一级函数中的变量放在更后面,依次类推直至全局对象为止。当函数中需要查询一个变量的值的时候,js解释器会去作用域链去查找,从最前面的本地变量中先找,如果没有找到对应的变量,则到下一级的作用域链上找,一旦找到了变量,则不再继续。如果找到最后也没找到需要的变量,则解释器返回undefined。
4.闭包:了解了作用域链,再来看js的内存回收机制,一般来说,一个函数在执行的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了,对应的内空间也就被回收了。下次在执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用。但如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的,并且这个内部函数又使用了外部函数的某些变量的话。这种内存回收机制就会出现问题。如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了。所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来。也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用后(例如被删除了,或者没有了指针),才会销毁这个闭包。即当没有任何一个闭包引用变量存在时,才会被下一次内存回收启动时所回收。
也就是说,有了闭包,嵌套的函数结构才可以运作,这也是符合我们预期的。eg:
varresult=[];
functionfoo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=function(){
alert(i)
}
}
};
foo();
result[0]();// 3
result[1]();// 3
result[2](); // 3
这段代码中,程序员希望foo函数中的变量i被内部函数使用,并且能分别获得他们的索引,而实际上,只能获得该变量最后保留的值,也就是说,闭包中所有记录的自由变量,只是对这个变量的一个引用,而非变量的值,当这个变量被改变了,闭包里获取到的变量值,也会被改变。
解决的方法之一,是让内部函数在循环创建的时候立即被执行,并且捕捉当前的索引值,然后记录在自己的一个本地变量里。然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:
varresult=[];
functionfoo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=(function(j){
return function(){
alert(j);
};
})(i);
}
};
foo();
result[0]();// 0
result[1]();// 1
result[2](); // 2
5.闭包就是函数内部的函数,本质上闭包就是将函数内部和函数外部连接起来的一座桥梁
6.闭包的作用:一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
7.使用闭包注意的问题
1) 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决的方法是,在退出函数之前,将不使用的局部变量全部删除。
2) 闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当做对象使用,把闭包当做它的公用方法,把内部变量当做它的私有属性,这时一定要小心,不要随便改变父函数变量的值。
8.闭包:是一个拥有许多变量和绑定了这些变量的环境表达式(通常是一个函数),因而这些变量也是该表达式的一部分,通俗的来说这句话是说所有javascript中的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。
当函数的内部函数被函数被函数外的一个变量引用的时候,就创建了一个闭包。所谓闭包就是再构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象函数反过来引用外层函数体中的临时变量。这使得只要目标对象在生存期内始终能够保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内部却始终能引用到该变量的值,而且该值只能通过这种方法来访问。即使再次调用相同的构造函数。但只会生成新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立的。
9.闭包的底层解释:对函数
当定义函数a的时候,js解释器会将函数a的作用域链(scopechain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
当执行函数a的时候,a会进入相应的执行环境(excutioncontext)。
在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。
当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:
如图所示,当在函数b中访问一个变量的时候,搜索顺序是:
先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。
如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。
如果整个作用域链上都无法找到,则返回undefined。
上文提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤1和3)。用一段代码来说明这个问题:
function f(x) {
var g = function () { returnx; }
return g;
}
var h = f(1);
alert(h());
这段代码中变量h指向了f中的那个匿名函数(由g返回)。
假设函数h的作用域是在执行alert(h())确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。
假设函数h的作用域是在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。
如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。
运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。
10.闭包的作用:
1) 保护函数内的变量安全。
2) 在内存中维持一个变量。
3) 通过保护变量的安全实现js私有属性和私有方法