一.前言
最近在看一本书,打算夯实一下基础,今天就看到了关于闭包的一些问题。闭包(closure)是 Javascript 语言的一个难点,面试时常被问及,也是它的特色,很多高级应用都要依靠闭包实现。本文尽可能用简单易懂的话,讲清楚闭包的概念、形成条件及其常见的面试题。
二.什么是闭包?
学习闭包,我们首先要先知道闭包到底是一个什么概念?入坑前端整整一年我都没有完完整整的弄明白闭包,总是零零散散的一些知识。要想弄明白闭包到底是什么?最重要的前提是弄明白 三个点 :函数作用域、内存回收机制、****作用域继承。
2.1.函数作用域
入手闭包的第一步,我们首先要搞明白 **函数作用域 ,**函数作用域的概念是JavaScript作用域的一部分。在JavaScript中,对象和函数同样都是变量。作用域的概念是:可访问变量、对象、函数的集合,在ES5中作用域分为两大类:全局作用域、局部作用域(函数作用域)。
2.1.1 全局作用域
变量在函数外定义,即为全局变量。全局变量有 全局作用域: 网页中所有脚本和函数均可使用。全局作用域就不用多说了!
2.1. 2局部作用域(函数作用域)
局部作用域就是函数作用域,因为局部作用域就是通过在函数内部声明的,函数内的变量就是局部变量,只能在当前函数内部访问。用我自己的话描述就是:函数作用域就是一个封闭的不允许外界访问的私有空间。在JS中,一个函数的执行就会在内存中创建一个私有作用域,局部变量也在此时创建,函数执行完毕之后当前作用域下的局部变量会被自动销毁。
// b变量在myFunction声明的局部作用于下,此处不能调用 b变量
function myFunction() {
var b= "tom"; // 函数内可调用 b变量
}
比如在函数中定义一个变量,只能在函数这个私有作用域中使用(也就是封闭空间)。只要超出了这个作用域,就找不到该变量了。
而且函数执行完成后,这个私有作用域(封闭的空间)就会销毁。有一种情况它是不会销毁的,那就是“闭包”,就是我们今天的主题。
2.2 内存回收机制
内存回收机制就是不在用到的内存,我们系统就自动进行回收从而清理出空间供其他程序使用。那回收的规则是什么?
内部函数引用着外部的函数的变量,外部的函数尽管执行完毕,作用域也不会销毁。从而形成了一种不销毁的私有作用域。
某一变量或者对象被引用着,因此在回收的时候不会释放它,因为被引用代表着被使用,回收器不会对正在引用的变量或对象回收的。请牢牢的记住这句话,当还在使用的时候就不会被当成垃圾。
2.3.作用域继承
所谓的作用域继承,就像是儿子可以继承父亲的财产一样。比如有一个大的盒子作为一个父级的作用域,然后在这个大的盒子里边放一个小的盒子,作为子作用域。我们规定可以在小盒子中获取到大盒子中的东西,大盒子不能获取小盒子里的东西就称为作用域继承。
但是有时候我们是需要在外部访问内部变量的,这个时候我们就可以借助作用域继承解决这个问题,可以通过在函数内部在定义一个函数,再将这个函数返回。
上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
了解完以上的三个概念之后,差不多也明白闭包大概是怎么回事了。我们来总结一波:一个函数A里定义另一个函数B,函数B一直保持有对函数A作用域的访问权限。
闭包就是函数中的函数(其他语言不能函数再套函数),里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。
三.闭包的作用是什么?
闭包的作用就来自于闭包的特性:实际上每个函数都是一个闭包,每个函数天生都能记住自己出生时的作用域。把一个函数从它定义的那个作用域,挪走,运行。这个函数居然能够记忆住定义时的那个作用域。不管函数走到哪里,定义时的作用域就带到了哪里。接下来我们用两个例子来说明这个问题:
例题1:
例题2:
所以当我们需要保存一些私有变量不被销毁的时候就可以利用闭包,闭包所在的作用域能够一直保存。
于是乎:我们再看几个例题:
例题3:执行以下代码,看看会输出什么结果?
输出:2
嗯~,这个似乎都不在话下,再来一个
例题4:
答案:答案是会报错,c is not defined
总结一下,闭包的作用:
- 可以读取函数内部的变量
- 可以使变量的值长期保存在内存中。(所以慎用,用多了会造成内存泄漏,导致网页性能问题)
- 可以实现JS模块化
如:
四.常见的闭包面试题?
我们需要实现这样一个需求:点击某一个按钮,提示 “这是第n个按钮”。
熟悉闭包的小伙伴们肯定知道最后我们并不会如愿以偿的得到我们想要的结果,而是不管点哪一个按钮都会输出 “这是第4个按钮”。那么这是为什么呢?
原因就是:btns[i].onclick
是异步,异步的特性就是,他会等所有的同步事件都执行完了之后才开始执行。所以上面的代码:当我们循环的时候去定义一个匿名函数赋值btns[i].onclick
,匿名函数中没有变量i,所以他回去全局作用域中找,然而他当跳出函数内部去找i变量的值时,i就已经被赋值成i+1了,这样循环下去直到i循环完毕变成3,异步事件才开始执行。
那么我们如何解决呢?有三种办法:
-
let声明,最简单的就是ES6中的let声明了。
-
使用自定义属性。我们给每个对象添加一个索引属性就 OK 了。
-
闭包的方式。用来保存私有的变量。
--let声明:
let声明的精髓在于,let每次被定义都会生成一个块级作用域,循环几次就有几次不同的i,而var自始至终都只有一个i,这是被赋予了不同的值。把let声明变形一下应该就是:
把for里面的let换成var也可以。
--使用自定义属性:
--闭包:
但是闭包解决又优点,也有缺点。优点就是通过创建私有作用域(闭包)方式解决,循环几次,就创建几个私有作用域(闭包),然后,每个私有作用域都有一个私有变量 i ,存的值分别是循环的值。
缺点是生成多个不销毁的私有作用域(堆内存),对性能有一定的影响。
以上均是个人学习笔记,如果有误请指正
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!