通过1个for循环理解闭包

场景:

在实际的开发过程中,经常会遇到这样的需求:批量添加节点,并且为每个节点添加事件绑定。看起来好像也没什么难度,一个for循环就可以搞定

是这样吗?下面一起来看一段代码(批量创建<div>,并且绑定click事件,打印其序号)

function addDivs(){
    var len = 10;
    for(var i=0;i<len;i++){
    	var div = document.createElement('div');
    	div.innerText = i;
  		div.addEventListener('click',function(){
    			alert(i)
    	})
    	document.body.appendChild(div)
    }
}
addDivs();
复制代码

会有一部分人给出跟上面差不多的代码,但是这段代码是不能实现需求的,这段代码实际上点击任意div,弹出的都是数字10,这是为什么呐? 想要彻底搞清楚其中的缘由,我们首先要了解一些JavaScript的一些知识

1. javaScript中是没有块级作用域(ES6中除外)的概念。下面进行一下简单的对比,更加具象的了解:

如果你有想c语言这种编程经历的人来说应该可以很好的理解块级作用域这个概念。

#include <stdio.h> 
void main() 
{ 
    int i=2; 
    i--; 
if(i) 
{ 
    int j=3; 
} 
    printf("%d/n",j); 
}
复制代码

运行这段代码,会出现“use an undefined variable:j”的错误。可以看到,C语言拥有块级作用域,因为j是在if的语句块中定义的,因此,它在块外是无法访问的

但是用javaScript改写之后并不会报错,因为js中并没有块级作用域的概念(但是可以使用匿名函数的方式实现)

var i =2;
i--;
if(i){
    var j = 3;
}
console.log(j)
复制代码
2. 执行环境(执行上下文)和作用域链

执行环境:定义了变量或者函数有权访问的数据,并决定了他们各自的行为。每个执行环境都有一个与之相关联的变量对象,执行环境中所有的变量和函数都保存在这个对象中。

忘记这些抽象的概念吧,我们来说人话:

执行环境分两种,全局执行环境和局部执行环境

先说全局执行环境: 它是一个最外层的执行环境。在web浏览器中,它对应的变量对象是window,这也是为什么所有的全局变量和函数都是作为window对象的属性和方法创建的。

再说函数执行环境: 当执行某个函数时,会创建一个活动对象,并把这个对象作为与该函数的执行环境关联的变量对象,从而创建出函数的执行环境。

那什么叫作用域链?

首先一点,我们要明确ECMAScript的执行流的工作过程:当执行流进入一个函数时,函数的执行环境就会被推入到一个环境栈中,函数执行完之后,栈将其环境弹出,将控制权返还给之前的执行环境

当代码在一个执行环境中执行时,会创建变量对象的一个作用域链。作用域链的前端始终都是当前执行的代码所在环境的变量对象,作用域链的末端始终都是全局执行环境的变量对象。这就是标识符解析的基础——沿着作用域链一级一级的搜索标识符的过程。 作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有权访问 上一个javasc高级程序设计中经典的?

var color="blue";
function changeColor(){
    var anotherColor="red";
    function swapColors(){
        var tempColor=anotherColor;
        anotherColor=color;
        color=tempColor;
        //这里可以访问color、anotherColor和tempColor
    }
    //这里可以访问color、anotherColor,不能访问tempColor
    swapColors();
}
//这里只能访问color
changeColor();
复制代码

作用域链图下图:

3.闭包

Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.

在理解闭包之前你需要知道:变量的作用域

无非就是两种:全局变量和局部变量

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

var i=1;
function f(){
    alert(i);
}
f() //1;
复制代码

另一方面,在函数外部自然无法读取函数内的局部变量。

function f(){
    var n=999;
}
alert(n); // error
复制代码

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量。

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

function f(){
	var n=999;
	function f1(){
		alert(n);
	} 
	return f1;
}
var result=f();
result();// 弹出999
复制代码

  f1()可以看成闭包。闭包在我看来就是可以访问另一个函数作用域下变量的函数。 主要作用有模拟块级作用域,创建私有变量等。说白了就是为了收紧权限,有限封装

有了这些知识以后 我们回头看刚才的代码,又发现什么问题吗?
function addDivs(){
    var len = 10;
    for(var i=0;i<len;i++){
    	var div = document.createElement('div');
    	div.innerText = i;
  		div.addEventListener('click',function(){
    			alert(i)
    	})
    	document.body.appendChild(div)
    }
}
addDivs();
复制代码

我们为每一个div标签绑定了一个函数实例(Function),表面上看是每个函数都展示当前的索引值,第一个div展示0,第二个展示1,一次类推。但是实际上,每个函数展示的都是10。 这是因为每个函数作用域链中都保存着addDivs()的活动对象,所以它们引用的都是同一个变量i,当addDivs()执行完之后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象(跟执行环境相对应),所以每个函数内i的值都是10。 #####解决方法 通过新建另一个匿名函数强制让闭包的行为符合预期

1.增加若干个闭包域空间,专门用来存储原先需要引用的内容,这里采用的是匿名函数

var len = 10;
for(var i=0;i<len;i++){
	var div = document.createElement('div');
	div.innerText = i;
	(function(arg){
		div.addEventListener('click',function(){
			alert(arg)
		})
	}(i))
	document.body.appendChild(div)
}
复制代码

2.将事件绑定在新增的匿名函数返回的函数上

var len = 10;
for(var i=0;i<len;i++){
	var div = document.createElement('div');
	div.innerText = i;
	div.addEventListener('click',(function(arg){
		return function(){
			alert(arg)
		}
	}(i)))
	document.body.appendChild(div)
}
复制代码

1,2方法相似又不同:

  • 相似点:同样是增加若干个对应的闭包域空间用来存储数据
  • 不同点:办法1 是在新增的匿名闭包空间内完成事件的绑定,而此例是将事件绑定在新增的匿名函数返回的函数上

3.es6

var len = 10;
for(var i=0;i<len;i++){
	var div = document.createElement('div');
	div.innerText = i;
	let j = i;
	div.addEventListener('click',(function(arg){
		alert(j)
	})
	document.body.appendChild(div)
}
复制代码
如文章有任何疏漏,请各位大声斧正
未经作者授权 禁止转载
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值