最近在做一个面试题时,无意中看到了一个闭包的题目,题目很简单,就是一个ul标签下面有多个li,给每个li添加点击事件,要求点击每个li弹出该li是第几个li。作为一个在前端面试中身经百战(lv zhan lv bai)的coder(lowser)来说,我当然知道要使用闭包来解决这个问题,具体代码如下:
var li=document.getElementsByClassName('liClass');
for(var i=0;i<li.length;i++)
{
(function(num){
li[num].onclick=function(){
alert(num);}
})(i)
}
或者使用let来声明循环变量,或者在循环类先用一个函数来先返回当前序号,然后在弹出,等等等等等方法都可以解决上述问题。
可是说上述类型的问题 ,遇到过不下于五六次了,虽然每次都能解决此类问题,但是心里一直有个结,就是老是感觉自己对这类问题的原因,有种一知半解的嫌疑,于是今天打算彻底fire掉它。那么问题来了:
什么是js的作用域链?
先展示一段代码:
var scope="global";
function t(){
console.log(scope); //undefined
var scope="local"
console.log(scope); //local
}
t();
出现这种情况的原因是,js语法不存在块级作用域,且存在着变量提升的机制。上述代码可以翻译成:
var scope="global";
function t(){
var scope;
console.log(scope); //undefined
scope="local" ;
console.log(scope); //local
}
t();
很明显了,js中var scope=’local’的声明和赋值,会被拆分为var scope;scope=‘local’;且声明会提升到当前作用域的最前面。
何为作用域?通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
何为作用域链?当一个函数作用域里面又声明一个函数时,子函数就会继承父函数的作用域,父函数又会继承父父函数的作用域,….一级一级的,直到最顶层的作用域,这就形成了一个作用域链。函数的作用域链是在函数的创建的时候创建。
什么是闭包?
回到最开始的那个题目,写一个错误的实现方法:
var li=document.getElementsByClassName('liClass');
for(var i=0;i<li.length;i++)
{
li[i].onclick=function(){
alert(i);
}
}
这是一个错误写法,结果是,不管点击那个li,弹出的都是li.length。通过以上对作用域链的理解,可以得知:
上述代码在页面加载完成后就会执行,for循环执行完成后,i的值变为li.length,当执行点击事件时,后面的匿名函数就是一个闭包,闭包i变量不会被销毁,所以每次都弹出li.length,没 毛病。
回到开头,如果for循环后紧跟使用一个自执行函数,又形成了一个闭包,使用num值保存每次循环的i值,这样就不会有问题了。
问题解决,