什么是闭包?
闭包可以这样来理解:闭包是一个函数,它与普通函数之间区别在于,闭包可以访问另一个函数的作用域中的变量对象。闭包可以是一个匿名函数(lambda函数),任何函数都可以被理解为是闭包。
我们来看一个简单的例子:
function CSSer(title){ return function (description){ return title + "," + description; } } CSSer("CSSer")("关注web前端技术"); // CSSer,关注web前端技术
上面的例子中定义了一个函数CSSer,该函数的执行结果将返回一个匿名函数(闭包)。该匿名函数可以访问CSSer函数中的变量对象title,即使CSSer函数执行完毕,其返回的匿名函数仍然可以访问CSSer函数环境中的变量。
闭包与变量
从闭包的概念中可以了解到,闭包可以访问其外部函数的变量对象,既然是变量对象,必然是变量最终的值,而不是变量某一状态的值,下面看一个很常见的例子:
function captureElemIndex(elems){ for(var i = 0, len=elems.length;i < len; i++){ elems[i].onclick = function(){ console.log(i); } } } var elems = document.getElementsByTagName("div"); captureElemIndex(elems);
上述例子用于实现当div被鼠标点击时捕捉其在整个DOM树中的索引位置,乍看是没有问题的,但当实际运行时,就会发现每次捕捉的索引值都是elems.length,而不是我们想要的结果。我们了解了闭包机制之后,很容易解释这个问题,并且给出解决方案:
function captureElemIndex(elems){ for(var i = 0, len=elems.length;i < len; i++){ elems[i].onclick = function(n){ return function(){ console.log(n); } }(i); } }
执行环境
当函数被调用时,会创建一个执行环境。
当进入执行环境时,创建其作用域链,并把作用域链赋给函数的特殊内部属性[[scope]]。
然后使用this、arguments和形参初始化函数的活动对象(即执行环境的变量对象)。这里对于变量对象的初始化过程是有顺序的:
- this :所有活动的执行环境都有this值,它依赖并取决于代码执行时的caller。
- arguments :创建arguments对象作为函数的arguments属性,arguments对象是一个类数组集合,其length即函数实参的个数,其每一个索引的值与对应的函数实参都指向同一个值的引用。 函数的arguments属性不可被delete动态删除 。
- 函数参数 :创建同名标识符作为property添加到变量对象中。如果调用者提供的参数少于形参,则其它形参值为undefined;如果遇到多个同名形参,最后一个形参值将被保留,如果最后的形参也未被提供值,则其值同样是undefined。
- 函数声明 :创建以函数名字为标识符作为property添加到变量对象中,其值为创建的函数对象。如果存在同名的property,则替换掉。
- 变量声明 :创建以变量名字为标识符作为property添加到变量对象中,其值为undefined,如果之前变量对象中存在同名property,则其值不变。
从进入执行环境到退出执行环境,完成了函数中代码语句的执行过程,一般情况下,函数执行完毕其执行环境的变量对象将被销毁以释放内存。由于闭包仍保留着对其外部环境活动变量对象的引用,所以存在特殊性。即使其外部环境的作用域链被销毁,但它的活动对象仍保留在内存中直至闭包被销毁才会被释放。
延长作用域链
Javascript中有两个语句可以延长作用域链:try-catch语句的catch语句块、with语句。所谓延长作用域链即是说在作用域的最前端临时增加变量对象(unshift),该变量对象会在代码执行完毕后被移除(shift),不再深究。【完】