什么是闭包
红宝书上解释:
闭包 是指有权访问另外一个函数作用域中的变量的函数.闭包就是能够读取其他函数内部变量的函数
MDN 上解释:
闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。
要理解闭包必须先理解什么是作用域以及作用域链。
作用域及作用域链
作用域分为全局作用域和函数作用域,es6 后又引入了块级作用域的概念。
作用域在代码定义的时候就产生了且不会改变,全局变量作用在全局作用域中,而函数内声明的局部变量是在函数作用域中,外部作用域无法访问内部的函数作用域中的变量。
作用域作用: 限制变量的访问权限和访问顺序,隔离变量使不同作用域下的同名变量不会产生冲突。
作用域链: 访问变量时,先查找自身函数作用域内是否有该属性,没有再向外查找父级作用域中是否含有该属性,直到最外层全局作用域,直到即可以访问,没有则报错 ReferenceError,访问变量查找作用域的这个顺序及其层级关系称为作用域链,访问变量时是在作用域链中自下而上(由内到外)查找。
看下面两个作用域的例子:
例子1:
var x = 10
function fn() {
console.log(x)
}
function show(f) {
var x = 20
f()//即调用函数 fn(),访问变量 x,其自身函数作用域没有向外访问全局作用域,x 值为10
}
show(fn)//10
调用 show(fn),执行 show 函数,在其内部调用了 fn 函数,但是 fn 函数是在show 函数外部定义的,所以 fn 函数作用域与 show 函数作用域不嵌套,是同级的,访问 x 变量,fn 函数作用域中没有 x 变量,因为向外查找,外部全部作用域中有,因此 打印 x 值为10。
注意: 作用域在代码定义的时候就产生了且不会改变。看下图理解作用域。
例子 2:
var name='global name'
var obj = {
name:'lily',
fn2:function () {
console.log(this.fn2)
console.log(this.name)
console.log(name)
console.log(fn2)//当前函数作用域中没有fn2变量,向外到全局作用域中查找,没有则ReferenceError
}
}
obj.fn2()//Uncaught ReferenceError: fn2 is not defined
function f() {
var localname='localname'
}
console.log(localname)
打印结果为:
ƒ () {
console.log(this.fn2)
console.log(this.name)
console.log(name)
console.log(fn2)//当前函数作用域中没有fn2变量,向外到全局作用域中查找,没有则ReferenceError
}
lily
global name
Uncaught ReferenceError: fn2 is not defined
ReferenceError: localname is not defined
由此可见,外部作用域是无法访问函数内部作用域的,当访问的变量在作用域链中没有找到时会报错,定义的全局变量 obj 作用在全局作用域中,其值是引用对象类型的数据,因此 console.log(name) 无法直接访问到 obj 里的私有属性name。而 console.log(this.name) 则是通过this 指向调用 fn2 函数的obj 对象来访问 name 属性的。
闭包的使用及作用
通过作用域了解到,函数内部的变量在外部是无法访问到的,但是通过闭包可以达到这一目的。
具体看下面例子:
例子1 函数作为另一函数的返回值,f 和 f2 都是闭包
var a=100
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f=fn1()
f()//3
f()//4
var f2=fn1()
f2()// 3
例子 1 中 fn2 作为 fn1 函数的返回值,在全局作用域中可以被调用,且函数体中使用了外部函数的局部变量 a 值为 2,因此本应该执行完 fn1就立马销毁的局部变量 a 被保存了下来,且通过外部也可以改变其值。
注意: f 和 f2 是两个闭包,彼此不会互相影响。闭包的数量与外部函数被调用的次数有关。
闭包的作用:
- 1、 使函数内部的变量在函数执行完毕后仍然存活在内存中(延长了局部变量的生命周期)
- 2、 让函数外部可以操作(读/写)到函数内部的数据(变量/函数)
闭包不会产生内存泄漏:
先简单了解一下内存泄漏和内存溢出:
- 1、 内存泄漏:不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
- 2、 内存溢出:一种程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时就会抛出内存溢出的错误,如例子4,举内存溢出的例子。
此例子浏览器会报错: paused before potential out-of-memory crash
var obj={}
for(var i=0;i<10000;i++){
obj[i]=new Array(1000000)
console.log('-----------')
}
正是由于闭包会使函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集,从而导致内存无法进行回收,这是IE的问题,所以闭包和内存泄漏没半毛钱关系。
参考文章:
闭包的应用:
自定义js模块
可以封装具有特定功能的js文件
将所有数据和函数方法都封装在一个函数内部(私有的)
只向外暴露一个包含 n 个方法的对象或函数
引入该js文件,通过调用函数或者立即调用函数返回的对象或函数来实现对应的功能
如下面例子中,新建一个 myModule.js 实现字母大小写的转换,将方法写在一个立即调用函数中,并向外暴露一个全局对象,方便js模块的管理和使用。
index.html
<script src="myModule.js"></script>
<script>
/**/
myModule.upperCase('bjt')
myModule.lowerCase('BJT')
</script>
myModule.js
;(function () {
function upperCase(msg) {
console.log(msg+'转换为大写字母:'+msg.toUpperCase())
}
function lowerCase(msg) {
console.log(msg+'转换为小写字母:'+msg.toLowerCase())
}
//以对象的形式暴露一个全局变量
window.myModule={
upperCase:upperCase,
lowerCase:lowerCase
}
})()
运行结果:
bjt转换为大写字母:BJT
myModule.js:6 BJT转换为小写字母:bjt
闭包练习题
/*闭包面试题*/
function fun(n,o) {
console.log(o)
return {
fun:function (m) {
return fun(m,n)
}
}
}
var a=fun(0); a.fun(1); a.fun(2); a.fun(3); //undefined 0 0 0
var b=fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
var c=fun(0).fun(1); c.fun(2); c.fun(3); //undefined 0 1 1
参考文章:
来源:CSDN
作者:onlyoneLIJINGYI
链接:https://blog.csdn.net/onlyoneLIJINGYI/article/details/104673992