博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】深入浅出JavaScript之闭包(Closure)
阅读量:6296 次
发布时间:2019-06-22

本文共 3716 字,大约阅读时间需要 12 分钟。

闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。下面写下我的学习笔记~

闭包-无处不在 

在前端编程中,使用闭包是非常常见的,我们经常有意无意,直接或间接用到了闭包。闭包可以使传递数据更加灵活(比如处理一些点击事件)

1
2
3
4
5
6
7
!
function
() {     
  
var
localData =
"localData here"
;   
     
document.addEventListener(
'click'
,   
//处理点击事件时用到了外部局部变量,比如这里的localData      
        
function
(){             
           
console.log(localData);
    
});
}();

又比如下面这个例子:(是不是很亲切~~)

1
2
3
4
5
6
7
8
9
10
11
!
function
() {     
  
var
localData =
"localData here"
;     
  
var
url =
""
;     
  
$.ajax({
     
url : url,         
     
success :
function
() {             
        
// do sth...             
        
console.log(localData);
        
}
    
});
}(); 

再来看一个例子~~这种情况就是我们通常所说的闭包

1
2
3
4
5
6
7
8
function
outer() {  
  
var
localVal = 30;   
  
return
function
(){     
    
return
localVal;   
  
}
}
var
func = outer(); 
func();
// 30

这个例子中调用outer()返回匿名函数function(),这个匿名函数中可以访问outer()的局部变量localVal,在outer()调用结束后,再次调用func()的时候,仍然能访问到outer()的局部变量localVal

闭包的概念

闭包,不同于一般的函数,它允许一个函数在立即词法作用域外调用时,仍可访问非本地变量。 --维基百科 

闭包就是能够读取其他函数内部变量的函数。 --阮一峰

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

闭包的用途

这部分转自这篇

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

1
2
3
4
5
6
7
8
9
10
11
12
function
f1(){
    
var
n=999;
    nAdd=
function
(){n+=1}
    
function
f2(){
      alert(n);
    }
    
return
f2;
  }
  
var
result=f1();
  result();
// 999
  nAdd();
  result();
// 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

闭包-封装 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(
function
() {  
   
var
_userId = 23492;  
   
var
_typeId =
'item'
;   
   
var
export = {};
     
   
function
converter(userId) {         
     
return
+userId;
   
}
    
export.getUserId =
function
() {        
       
return
converter(_userId);    
   
}
   
export.getTypeId =
function
() {         
      
return
_typeId;
   
}        
   
window.export = export;  
//通过此方式输出
}());
 
  
export.getUserId();
// 23492
  
export.getTypeId(); 
// item
  
export._userId;   
// undefined 
  
export._typeId;   
// undefined      
  
export.converter;
// undefined

利用闭包的特性能让我们封装一些复杂的函数逻辑,在这个例子中调用export上的方法(getUserId,getTypeId)间接访问函数里私有变量,但是直接调用export._userId是没法拿到_userId的。这也是Node里面常用到特性吧~

常见错误之循环闭包 

下面这个案例,我们添加3个div,值分别为aaa,bbb,ccc,我们想实现的是点击aaa输出1,点击bbb输出2,点击ccc输出3

1
2
3
4
5
6
7
document.body.innerHTML =
"<div id=div1>aaa</div>"
+
"<div id=div2>bbb</div><div id=div3>ccc</div>"
for
(
var
i = 1; i < 4; i++) {     
  
document.getElementById(
'div'
+ i).        
    
addEventListener(
'click'
,
function
() {        
    
alert(i);
// all are 4!
    
}); 
}

结果点击aaa,bbb还是ccc都是alert(4)~~

产生这样的问题在于这个i的值在初始化完成的时候就已经是4了

要达到我们想要的点击aaa输出1,点击bbb输出2,点击ccc输出3,要用到闭包的技巧,在每次循环的时候,用立即执行的匿名函数把它包装起来,这样子做的话,每次alert(i)的值就取自闭包环境中的i,这个i来自每次循环的赋值i就能输出1,2,3了

1
2
3
4
5
6
7
8
9
document.body.innerHTML =
"<div id=div1>aaa</div>"
+
"<div id=div2>bbb</div>"
+
"<div id=div3>ccc</div>"
for
(
var
i = 1; i < 4; i++) {
  
!
function
(i){
//②再用这个参数i,到getElementById()中引用    
    
document.getElementById(
'div'
+ i).      
      
addEventListener(
'click'
,
function
() {        
      
alert(i);
// 1,2,3
     
}); 
  
}(i); 
//①把遍历的1,2,3的值传到匿名函数里面

思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。(来自阮老师)这题目总结得真妙~~

代码片段一。

1
2
3
4
5
6
7
8
9
10
var
name =
"The Window"
;
var
object = {
  name :
"My Object"
,
  getNameFunc :
function
(){
    
return
function
(){
      
return
this
.name;
    };
  }
};
alert(object.getNameFunc()());

代码片段二。

1
2
3
4
5
6
7
8
9
10
11
var
name =
"The Window"
;
var
object = {
  name :
"My Object"
,
  getNameFunc :
function
(){
    
var
that =
this
;
    
return
function
(){
      
return
that.name;
    };
  }
};
alert(object.getNameFunc()());

转载于:https://www.cnblogs.com/BruceWan/p/5964779.html

你可能感兴趣的文章
2019年-年终总结
查看>>
聊聊elasticsearch的RoutingService
查看>>
让人抓头的Java并发(一) 轻松认识多线程
查看>>
从源码剖析useState的执行过程
查看>>
地包天如何矫正?
查看>>
中间件
查看>>
Android SharedPreferences
查看>>
css面试题
查看>>
Vue组建通信
查看>>
用CSS画一个带阴影的三角形
查看>>
前端Vue:函数式组件
查看>>
程鑫峰:1.26特朗.普力挺美元力挽狂澜,伦敦金行情分析
查看>>
safari下video标签无法播放视频的问题
查看>>
01 iOS中UISearchBar 如何更改背景颜色,如何去掉两条黑线
查看>>
对象的继承及对象相关内容探究
查看>>
Spring: IOC容器的实现
查看>>
Serverless五大优势,成本和规模不是最重要的,这点才是
查看>>
Nginx 极简入门教程!
查看>>
iOS BLE 开发小记[4] 如何实现 CoreBluetooth 后台运行模式
查看>>
Item 23 不要在代码中使用新的原生态类型(raw type)
查看>>