****闭包****
1.闭包应该是指一个闭包域,每当声明一个函数时就产生了一个闭包域——这里可以解释为每个函数都有自己的函数栈——每个闭包域(Function对象)都有一个function scope(这里的function scope不是属性),function scope内默认有一个Globe的全局引用,这个引用可以直接调用Globe的属性或者方法。
2.外部无法访问在闭包域内声明的变量或者方法,闭包域可以访问外部的变量或者方法。
3.当一个闭包域内包含另一个闭包域时(一个函数内有另一个函数,内部函数的生命周期依赖于外部函数),若子闭包域(内部函数)使用了父闭包域(外部函数)的私有变量(在父闭包域中声明的变量,父闭包域的外部空间无法直接访问,但子闭包域可以访问),子闭包域(当前的子函数)的function scope会产生一个closure对象属性,这个对象属性内包含的是子闭包域对父闭包域的所有引用(只要子闭包域还存活,则父闭包域就依旧存活)如果在父闭包域存活期间对私有变量进行修改,那么子闭包域对父闭包域私有变量的引用中function scope的closure对象属性的内容也会发生变化。
以下代码中原本是想每次点击对应的目标弹出对应的数字下标0-4,但实际上无论点击哪个目标都会弹出数字5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
function onMyLoad(){
var arr = document.getElementsByTagName("p");
for(var i = 0; i < arr.length; i++){
arr[i].onclick = function(){
alert(i);
}
}
}
</script>
</head>
<body onload="onMyLoad()">
<p>0</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
</body>
</html>
上面点击时,arr中每一项的onclick均为一个函数实例(Function对象),这个函数实例产生一个闭包域,这个闭包域引用了外部闭包域的变量,其function scope的closure对象有个名为i的引用,外部闭包域的私有变量内容发生变化时内部闭包得到的值就会改变。
1)上面代码出现的问题第一种解决办法:增加若干个对应的闭包域空间(采用匿名函数实现)专门用来存储原先需要引用的内容(下标值),只限于基本类型(基本类型值传递,对象类型引用传递)
//声明一个匿名函数,若传进来的是基本类型则为值传递,不会对实参影响
for(var i = 0; i<arr.length; i++){
(function (arg){//这个函数对象有一个本地私有变量arg(形参),该函数的function scope的closure对象属性有两个引用:arr和i。i的值随外部改变,但是本地的私有变量(形参)arg不会受影响,其值在一开始被调用时就决定了
arr[i].onclick = function () {//onclick函数实例的function scope的closure对象属性有一个引用arg
alert(arg);//只要外部空间的arg不变,这里的引用值就不会改变
}
})(i);//立即执行匿名函数,传递下标i(实参)
}
2)第二种解决方法:将下标作为对象属性(name:"i",value:i的值)添加到每个数组项(p对象)中
for(var i=0; i<arr.length; i++){
//为当前数组项(当前p对象)添加一个名为i的属性,值为循环体i变量的值
//此时当前p对象的i属性并不是对循环体的i变量的引用,而是一个独立p对象的属性,属性值在声明的时候就确定了
arr[i].i = i;
arr[i].onclick = function (){
alert(this.i);
}
}
3)第三种解决方法:增加若干个对应的闭包域空间用来存储下标。新增的匿名闭包空间内完成事件绑定。
//绑定的函数中的function scope中的closure对象的引用arg是指向将其返回的匿名函数的私有变量arg
for(var i = 0; i<arr.length; i++){
arr[i].onclick = (function(arg){
return function () {
alert(arg);
}
})(i);
}
4)第四种解决方法:跟第一种方法类似
for(var i = 0; i<arr.length; i++){
(function(){
var temp = i;
arr[i].onclick = function (){
alert(temp);
}
})();
}
5)第五种解决方法:跟第三种方法和第四种方法类似
for(var i = 0; i<arr.length; i++){
arr[i].onclick = (function () {
var temp = i;
return function () {
alert(temp);
}
})();
}
6)第六种解决方法:将下标添加为绑定函数的属性
for(var i = 0; i<arr.length; i++){
(arr[i].onclick = function () {
alert(arguments.callee.i);//arguments参数对象
}).i = i;
}
7)第七中解决方法:通过new使用Function构造函数创建Function实例,传入函数体的内容是字符串,所以Function得到的是一个字符串拷贝,而没有得到i的引用。先获取i.toString()然后与前后字符串拼接成一个新的字符串,Function对其方向解析成JS代码。
for(var i = 0; i<arr.length; i++){
arr[i].onclick = new Function("alert("+i+");");//每new一个Function得到一个Function对象(一个函数),有自己的闭包域
}
8)第8种解决方法:直接通过Function返回一个函数。第七种解决方法中使用了new,使用了new,那么Function函数就被当成构造器可以用来构造一个Function实例返回。这种方法中不使用new,将Function函数当成一个函数,传入参数返回一个新函数。
//使用了new,Function函数充当构造器,由JS解析器产生一个新的对象,构造器内的this指向该新对象
//不使用new,Function函数依旧是函数,由函数内部自己产生一个实例返回
for(var i = 0; i<arr.length; i++){
arr[i].onclick = Function("alert("+i+");");
}
9)第九种解决方法:使用ES6的let关键字(有些浏览器不支持)
"use strict";
var arr = document.getElementsByTagName("p");
for(var i = 0; i<arr.length; i++){
let j = i;//块级变量
arr[i].onclick = function () {
alert(j);
}
}