代码1
<ul>
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
<li class="item">4</li>
<li class="item">5</li>
</ul>
// 有5个li标签
var items = document.querySelectorAll('li');
for (var i = 0; i < items.length; i++) {
items[i].onclick = function(){
console.log(i);
};
}
分析上面代码,依次点击li会输出什么?
答案:不管点击哪个li,都输出5
分析之前,需要明确的几个知识点:
- 首先,牢记:js中只有全局作用域和函数作用域
- 其次:牢记:变量的生命周期,分为三种情况,
- 对于全局变量而言,当页面被刷新或被关闭时,它就死了,也就是其所占的内存被释放;
- 对于函数内的局部变量(不考虑闭包),退出函数时,它就死了,也就是其占用的内存被释放;
- 对于函数内的局部变量(考虑闭包),当被返回的函数引用了局部变量,则只有引用该返回函数的变量被回收,局部变量才会被回收。
分析代码:
- var i 是全局变量
- 当for循环中给每个li添加了onclick的事件处理函数,注意:事件处理函数并未运行,所以当for执行完毕后,其实i并未被释放,i的值为5
- 当点击每个li时,调用的i还是原来那个i,也就是5, 所以不管点击哪个li,都打印出5
代码2
function outerFn() {
console.log("Outer function");
function innerFn() {
var innerVar = 0;
innerVar++;
console.log("Inner function\t");
console.log("innerVar = "+innerVar+"");
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
以上代码都输出:1
因为函数作用域的关系,每调用依次fnRef(),都将生成新的innverVar
改进
function outerFn() {
var outerVar = 0;
console.log("Outer function");
function innerFn() {
outerVar++;
console.log("Inner function\t");
console.log("outerVar = " + outerVar + "");
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();
输出: 1, 2,1, 2
var fnRef = outerFn(); 生成了outerVar变量,每次调用fnRef()时,获取的还是原来的那个outerVar;
var fnRef2 = outerFn(); 生成了另一个outerVar变量,这个变量与fnRef中的是不同的,因为它们处于不同的作用域
再次改进
function outerFn() {
var outerVar = 0;
function innerFn1() {
outerVar++;
console.log( outerVar);
}
function innerFn2() {
outerVar += 2;
console.log(outerVar);
}
return { "fn1": innerFn1, "fn2": innerFn2 };
}
var fnRef = outerFn();
fnRef.fn1();
fnRef.fn2();
fnRef.fn1();
输出: 1, 3, 4
当调用var fnRef = outerFn() 时,可以理解为“创建了一个属于fnRef执行环境”,之后访问的fnRef.fn1(); fnRef.fn2();均是在该执行环境下执行的
回到代码1,如何修改代码,能够实现点击li,打印出正确的数字呢?只需要为每一个li,创造出属于他们自己的独立的i,这个i与for循环中的i,是不同的i,也就是为每个li创造一个独立的执行环境,在js中只有通过函数能够创造一个独立的执行环境(也可以说是作用域),这时候,需要用到闭包来实现,代码如下:
var items = document.querySelectorAll('li');
for (var i = 0; i < items.length; i++) {
items[i].onclick = (function(i){
return function(){
console.log(i);
};
})(i);
}
几个知识点:
- 立即执行函数:为什么使用立即执行函数?因为我们需要将i传进去