保持对代码的热爱并保持怀疑态度
昨天写了一篇点击查看js防抖和节流的理解用到了闭包,刚刚好我也忘的差不多了,只知道闭包长什么,原理什么的早就忘到九霄云外了,所以准备重新梳理了一下闭包。
闭包的概念及原理
闭包是指有权访问另外一个函数作用域中的变量的函数。
也可以这么说,这样可能更能理解,也就是闭包的原理:
通过作用域的嵌套,触发计算机的垃圾回收机制(硬盘),将原本的局部变量进化成私有变量的环境,叫闭包
计算机的垃圾回收机制
-
硬盘:需要删除的数据,不直接删除,而是再是保存在一个临时区域,如果需要继续使用,可以从临时区域取出
-
内存:需要删除的数据,直接删除,不可恢复
-
程序,或代码的执行,默认使用的是内存的垃圾回收,闭包使用的是硬盘的垃圾回收
实例
首先来看这么一个需求, 要求多次执行a的值累加,不允许出现全局变量
function fn() {
var a = 10;
a++
console.log(a);
}
fn()
fn()
如果这么写的话,a只会输出11,因为函数每次都是重新执行。
但是这么写呢
function fn() {
var a = 10;
return function(){
a++;
console.log(a);
}
}
var f=fn();
f();
f();
f();
很显然是可以实现累加的,没错这就是闭包
实例分析
在分析上诉代码中,首先得知道这么几个知识
关于函数的定义作用域和执行作用域
- 定义函数时的作用域,叫定义作用域
- 执行函数时的作用域,叫执行作用域
- 无论函数在哪执行,都可以在自身内部使用自身定义作用域中的数据
拿上面的例子来分析
function fn() { 定义
var a = 10; 这个a相对于下面一个函数相当于是父级
return function(){ 定义返回值
a++; 每次都累加a,这里也没有声明a,所以会直接会改变a的值,执行一次就改变一次,所以就实现了累加。
console.log(a);
}
}
var f=fn(); 赋值
f(); 执行 ,函数在执行时,可以拿到自身的定义作用域的变量
f();
f();
也可以这么写,通过传参的方式
function fn(a) {
此时的a是fn的形参。通过作用域的嵌套,a此时相当于返回函数的父级作用域 ,也可以实现累加
return function(){
a++;
console.log(a);
}
}
var f=fn(10);
f();
f();
f();
闭包的特点
- 可以在作用域外部修改作用域内部的数据
- 连接了作用域内外部的桥梁
- 消耗内存
- 内存泄漏(内存溢出)(低版本浏览器以下)
闭包的应用
可以给内置的方法的回调函数传参
看实例
setTimeout(fn("hello"), 1000);
function fn(a){
console.log(a);
}
如果直接这么写的话,不会等到1秒钟以后再console.log,而是立即执行,为什么呢,涉及到了宏任务和微任务,点击查看异步,同步,宏任务,微任务理解但这不是重点,重点我们用闭包来解决这个问题
setTimeout(fn("hello"), 1000);
function fn(a){
return function(){
console.log(a);
}
}
这里就是通过传参的方式来解决的。
循环中的事件内使用循环的变量
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
</style>
</head>
<body>
<ul>
<li>link1</li>
<li>link2</li>
<li>link3</li>
<li>link4</li>
<li>link5</li>
</ul>
</body>
<script>
// 循环中的事件内使用循环的变量
var ali = document.querySelectorAll("li")
for(var i=0;i<ali.length;i++){
(function(i){
ali[i].onclick = function(){
console.log(i);
}
})(i);
}
for(var i=0;i<ali.length;i++){
ali[i].onclick = (function(i){
return function(){
console.log(i);
}
})(i);
}
for(let i=0;i<ali.length;i++){
ali[i].onclick = function(){
console.log(i);
}
}
</script>
</html>
以上的三种方式均可以获取到li的索引。