关于闭包那些事儿
Posted blogbyhuer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于闭包那些事儿相关的知识,希望对你有一定的参考价值。
本文目录:一、引入
二、定义
三、作用
四、深入理解
五、使用场景
一、引入
看下面代码:
代码一:function a(){
var n=0;
function inc(){
console.log(n++);
}
inc();
inc();
}
a();//先输出0,再输出1
代码二:function a(){
var n=0;
this.inc=function(){
console.log(n++);
}
}
var c=new a();
c.inc();//输出0
c.inc();//输出1
代码三:function a(){
var n=0;
function inc(){
console.log(n++);
}
return inc;
}
var c=a();
c();//输出0
c();//输出1
上面三段代码中的inc函数都是闭包,即有权访问另一个函数作用域内变量的函数都是闭包,而js中定义一个函数可以用来创建函数作用域,区别在于代码一和二中inc函数被调用时,使用的作用域和它们被定义时使用的作用域是同一个作用域,这种情况下,闭包的作用没有显示出来,代码三中inc函数被调用时,使用的作用域不同于定义时使用的作用域,闭包就起了作用。
二、闭包的定义
广义:闭包是指可以包含自由变量(未绑定到特定对象)的代码块,这些变量不是在这个代码块内或者任何全局上下文定义的,而是在定义代码块的环境中定义的(局部变量)。所谓闭包就是要执行的代码块加上为自由变量提供绑定的作用域,由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放,在许多高级语言中都能找到对闭包不同程度的支持。
js中的闭包:在js中,闭包就是函数和该函数作用域的结合,而js中函数都是对象且函数都有和它们相关联的作用域链,也就是说在js中,所有函数都是闭包。
三、闭包的作用
主要有两个:读取函数内部的变量
让变量的值始终保存在内存中
四、深入理解闭包的过程
看上面的代码三:function a(){
var n=0;
function inc(){
console.log(n++);
}
return inc;
}
var c=a();
c();//输出0
c();//输出1
var c=a();实际上相当于var c=inc;这里只是把inc函数标识赋值给了c变量
而c();则相当于inc();在这里才是真正的调用了函数inc();
本来js中一个函数调用完之后所有的变量都会被垃圾回收机制回收,不会存在于内存当中,但这里变量n由于还在被inc引用着,而inc还在被c引用着,因此它仍然会存在于内存当中,此时n的值为1,因此第二次调用的时候输出1,这也是闭包之所以能够让变量保存在内存中的原因。
看下面一段代码:function createFunctions(){
var result=new Array();
for(var i=0;i<10;i++){
result[i]=function(){
return i;
};
}
return result;
}
var funcs=createFunctions();
for(var i=0;i<funcs.length;i++){
console.log(funcs[i]());
}
这里输出了十个10
这里实际的执行过程:
var result=new Array(),i;
result[0]=function(){return i;};-->i=1
...
reault[9]=function(){return i;};-->i=10
funcs=result;
result=null;-->result被内存回收
console.log(i);-->10
...
console.log(i);-->10
第一个循环只是把函数代码块赋值给了result,实际并没有执行,该循环结束之后i的值变为10,调用funcs[0]()的时候输出了10,由于闭包的作用,变量i没有被回收,仍然存在于内存当中,因此之后的每一次调用函数都会输出10。
五、闭包的使用场景
场景一:通过循环给页面上多个DOM节点绑定事件
页面上有五个按钮:<button>button1</button>
<button>button2</button>
<button>button3</button>
<button>button4</button>
<button>button5</button>
js: var btns=document.getElementByTagName(‘button‘);//获取页面上的五个按钮对象,返回一个数组
for(var i=0, len=btns.length; i<len; i++){
btns[i].onclick=function(){
alert(i);
}
}
这段代码的本意是想点击某个按钮就alert这个按钮的号码,结果是不论点击哪个button,都alert5
因为onclick是异步触发函数,当事件被触发时,for循环早已结束,此时变量的值为5,因此当onclick事件函数沿着作用域链由内向外查找变量i时,找到的值总为5。
如何实现保留for循环的每一次循环中的i值呢?
答案是闭包。
通过立即执行函数的方式创建函数作用域:
for(var i=0, len=btns.length; i<len; i++){
(function(){
btns[i].onclick=function(){
alert(i);
}
}())
}
这里创建了新的函数作用域,每次循环都会立即执行嵌套函数的代码,都会查找一次变量i的值,而此时i的值就是当时循环的i值,就能获得准确的i值了。
场景二:封装变量+延续局部变量的寿命
有一个计算乘积的函数,接收number类型的参数并返回它们相乘的结果,假如在接收到相同的参数时,则不需要再次计算,直接返回之前计算得出的结果。用一个变量来存放每次计算得到的结果,这个变量要求是私有变量,且在函数执行完之后不被回收。
var multi=function(){
var cache={};//存放计算结果的变量
var calculate=function(){
var a=1;
var argu_length=arguments.length;
for(var i=0;i<argu_length;i++){
a=a*arguments[i];
}
return a;
}
return function(){
var args=Array.prototype.join.call(arguments,”,”);//这里用call方法把arguments转换为一个真正的Array
//var args=arguments.join(“,”);这样是不行的,arguments是一个类数组对象,除了length属性和索引元素之外没有任何Array属性和方法
if(args in cache){//通过判断属性名是否存在的方法判断转换为字符串的参数是否已存在于cache对象中,若存在,则返回通过属性名查找到的属性值作为结果
return cache[args];
}
//return cache[args]=calculate(arguments);//这里如果这样写的话是将整个arguments对象传递给了calculate方法作为一个参数,则在calculate方法中使用arguments的时候它的值不是一个数组,而是一个arguments对象,这个对象的内容才是我们要的数组参数,因此需要在calculate中需要使用arguments[0]来获取到我们要的数组参数
return cache[args]=calculate.apply(null, arguments);//这里是apply方法的妙用,它可以将一个数组默认的转换为一个参数列表,即将calculate(arguments)转换为了calculate(1,2,3),正好把数组参数传递给了calculate方法.这一行代码不仅成功且巧妙的调用了calculate函数,而且把参数数组转换为一个字符串作为cache对象的属性名,而函数返回的结果作为属性值添加进了cache对象中保存了下来
}
}
var result =multi()(1,2,3);//这里multi方法没有立即执行,所以需要先执行multi方法获取到嵌套函数,再去执行嵌套函数取得结果,若multi函数是立即执行的,则是这样调用的:multi(1,2,3)
console.log(result);//输出6
参考文章:https://www.jianshu.com/p/132fb6d485ee
以上是关于关于闭包那些事儿的主要内容,如果未能解决你的问题,请参考以下文章