学JS的心路历程-闭包closure
闭包是是纯函式语言的一个特性,也是JS的一个关键性的特色,虽然不了解也能开发程序,但我们不是这种人对吧?
闭包不仅可以减少某些高阶功能的代码数量和复杂度,并且可以让我们做到原本无法做的复杂功能。听到这还不想认识他吗!
那什么是闭包呢?它是一种数据结构,可以说是一种技术,能记住函式及函式被建立时当下环境,也就是说函式可以存取在建立时作用范围内的变数(jmcintoshcc)。
我们先来看一个最简单的示例:
var outer =“global”;
function outerFun(){
console.log(outer);
}
outerFun();//“global”
你可能会疑问这是闭包吗?平常就是这样用啊!
我们在同一个作用范围(也就是全局)中宣告一个变数outer和函式outerFun,并呼叫函式。
这个范围就是一个闭包,只是在程序结束时永远不会消失,让你没有感觉而已。
在JS中每当函式被建立时,一个闭包就会产生。虽然很多人经常拿巢状函式来作为示例来说明闭包,但并不是只有它才能产生,要记住这点!
虽然这么说,但我们还是会以巢状函式来举例,感觉有点打自己脸….
function outerFun(x){
function innerFun(){
x++
console.log(x);
}
return innerFun;
}
var num = outerFun(1);
num();//2
num();//3
在执行后会发现我的x怎么每一次执行都会在加一呢?就是因为闭包造成的。
它们在函式建立时为函式及作用范围内的变数建立了一个类似「安全气泡」的东西,让我们在执行时可以使用。
刚刚的代码,还可以简短成这样:
function outerFun(x){
return()=>{
x++;
console.log(x);
}
}
var num = outerFun(1);
num();//2
num();//3
不过这边有几点要先各位说明,避免产生误会:
函式呼叫和函式建立是两回事,在outerFun函式呼叫后,num才是函式建立,这时候num的闭包结构才会产生
闭包不是只会产生在巢状(内部)函式的回传时。所有函式在建立时都会产生闭包。
我们也可以透过下断点的方式观察闭包中的值:
所以我到底可以用哪些外部变数呢?基本上一个内部函式可以有三个作用域:
自已本身的
外部函式的
全局的
内部函式可以看到(或存取)外部函式,而形成一个Scope Chain(作用域连锁)。
这边我们来举一个实际的例子:
我们先建立两个按钮和一个显示区
<button id=“btn”>Click one</button>
<button id=“btn2”>Click two</button>
<div id=“display”></div>
然后写一个计数器的函式,并挂上监听事件(artwithbite)
var btn = document.getElementById('btn');
var btn2 = document.getElementById('btn2');
var display = document.getElementById('display');
function recordClick(name){
let count = 0;
return()=>{
count++;
console.log(count);
display.innerHTML = `${name} count:${count}`;
}
}
btn.addEventListener(“click”,recordClick(“Click one”));
btn2.addEventListener(“click”,recordClick(“Click two”));
执行后可以发现到两个按钮并不会有共享一个count问题,这是因为利用闭包产生了一个类似私有变数的功能。
这边附上codepen示例给大家玩玩。
那这边可能大家会有一个问题,到底闭包是复制了这些值还是只是参照而已呢?
答案是参照,最常拿来解说的就是利用非同步回呼函式来举例:
function counter(){
let i = 0;
for(i = 0;i< 5;i++){
setTimeout(function(){
console.log('counter is ' + i)
},1000);
}
}
counter();
可能很多人认为说结果会是1,2,3,4,5但是实际印出来怎么都是5?
这是因为「闭包结构中所记忆的环境值是用参照指向的」,setTimeout会先到队列中准备延迟执行,等到回来主程式时,循环早就执行完了,i也已经变成了5,要解决这个问题其实可以这样解:
function timer(index){
return setTimeout(function(){
console.log('counter is ' + index)
},1000);
}
function counter(){
let i = 0;
for(i = 0;i< 5;i++){
timer(i);
}
}
counter();
//counter is 0
//counter is 1
//counter is 2
//counter is 3
//counter is 4
创建一个函式timer让循环中的i值传入并形成一个闭包,因为每次传入timer的值都不同,所以每个setTimeout闭包中的值也会不同。
我们也可以这样解:
function showIndex(index){
return()=> console.log(index);
}
function counter(){
let i = 0;
for(i = 0;i< 5;i++){
setTimeout(showIndex(i),1000);
}
}
counter();
那么,以上就是闭包的用法,一样如果有错误及来源未附上也欢迎留言指正,那么我们明天见(recordcompanystartup)。
学JS的心路历程-闭包closure的更多相关文章
- 学JS的心路历程Day26 - PixiJS -入坑
后来知道也可以透过canvas让网页动起来! 而PixiJS是使用WebGL在canvas上绘制内容与制作动态 且同时有下列特色: 支持多点触控 掩码与混合模式 可外加WebGL滤镜 多装置支持 等等 ...
- 学JS的心路历程-JS支持面向对象?(二)
昨天讲了面向对象的继承,今天我们来谈谈多态和封装吧! 多态polymorphism 抽象讲法解释,就是使用单一界面操作多种型态的物件 继承父类别,定义与父类别中相同的方法,但实作内容不同,称为复写(o ...
- 学JS的心路历程 -函式(三)this
this是什么,取决于被呼叫的呼叫地点. 昨天有提到说,呼叫函式时候会传递隐含参数:arguments和this并讲解了arguments,今天我们就来探讨this吧! 什么是this 我们都会呼叫函 ...
- 学JS的心路历程-函式(二)arguments
参数(argument)与函式参数(parameter) 在讨论函式时,很多人都会把这两个搞混,我自己也不例外. 虽然讲错别人也听得懂,但是我们还是要搞清楚这两个的定义到底是什么! 参数是当我们呼叫函 ...
- 学JS的心路历程 - JS应用
各家电商网站都推出了各种活动和现今优惠券,当时在逛PTT时看到了有篇文章,提供代码教大家用JS的方式抢票,看了一下后发现好像很多人好奇这是怎么做的,于是就想说想一篇文章来讲解一下. 我们先来看一下折价 ...
- 学JS的心路历程 - JS的Class
没错,你没有看错,虽然前面说JS是原型继承,但在ES6以后新增了class关键字!!! 不过底层实作仍然是以原型继承方式进行,所以基本上算是一个语法糖. 今天我们就来看一下如何使用吧! class 首 ...
- 学JS的心路历程-物件与原型(三)
昨天有说明到函式与建构式的原型,及指定建构式函式原型为另一个建构式函式,但其实这会造成复写constructor的问题. 复写constructor的问题(vmwork) 我们昨天有提到「建构式函式可 ...
- 学JS的心路历程 -物件与原型(二)
昨天有提到说Object.setPrototypeOf可以指定一个物件为另一个物件的原型,但有想过到底这个原型,也就是[[Prototype]]最终会到何处吗? 答案是Object.prototype ...
- 学JS的心路历程-物件与原型(一)
前两天说明面向对象的三大特性及JS不符合面向对象,只能称作支持面向对象而已,今天我们来看看JS的原型继承. 首先我们先来看,什么是原型(vmwork): 两个物件之间的原型关系(prototype r ...
随机推荐
- CS229 6.11 Neurons Networks implements of self-taught learning
在machine learning领域,更多的数据往往强于更优秀的算法,然而现实中的情况是一般人无法获取大量的已标注数据,这时候可以通过无监督方法获取大量的未标注数据,自学习( self-taught ...
- Solr——Windows下部署Solr6.6.0至Tomcat8.5.28(一)
一.window 环境 solr 6.6.3 下载地址 http://archive.apache.org/dist/lucene/solr/ jdk 1.8 tomcat 8.5 本机tomc ...
- day19常用模块2
常用模块21 shelve模块 也是一种序列化方式 使用方法 1.open sl = shelve.open("shelvetest.txt") ...
- Python 文件的操作
新建 # ==================新建==================# 新建文件夹,若倒数第二层文件夹不存在则会报错os.mkdir(r"/home/python" ...
- android TextView 例子代码(文字图片、文字省略、文字滚动)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...
- Python学习笔记_week3_函数
一.介绍 1.面向对象(华山派)--->类(独门秘籍)--->class(定义的关键字) 2.面向过程(少林派)--->过程--->def 3.函数式编程(逍遥派)---> ...
- sql(Oracle)优化之索引
原文:https://www.cnblogs.com/oraclestudy/articles/5779210.html 建立索引的目的是:l 提高对表的查询速度:l 对表有关列的取值进行检查. 注意 ...
- .bat脚本基本命令语法 http://www.cnblogs.com/iTlijun/p/6137027.html
这个是我找到的非常好的一篇文章了: 目录批处理的常见命令(未列举的命令还比较多,请查阅帮助信息) 1.REM 和 :: 2.ECHO 和 @ 3.PAUSE 4.ERR ...
- PHP 时间相关操作
使用函式 date() 实现 <?php echo $showtime=date("Y-m-d H:i:s");?> 显示的格式: 年-月-日 小时:分钟:秒 获得当天 ...
- shell脚本判断执行用户
在脚本中,判断执行者是否为root. 判断方法1, #!/bin/bash if [ `whoami` != "root" ];then echo " only root ...