闭包的基本概念
我发现很多初学者不清楚什么是闭包(closure),闭包是怎么产生的,以及哪里可以查看闭包,所以我将自己的学习心得记录下来,希望可以帮助不懂的伙伴能够搞明白,如果有什么问题欢迎大家评论!!!如果有错误,也希望各位大佬能够指正!!
闭包的产生:
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(或函数)时就会产生闭包。
即闭包产生需要以下2个条件:函数嵌套 + 内部函数引用了外部函数的数据(数据可以是变量或者函数))
如下:
对于闭包到底是个什么东西,大家也有不同的解释,主要有以下2种:
- 闭包是嵌套的内部函数(即上图的fn2())大部分人认为
- 闭包是包含被引用变量的对象(即上图的小方框)少部分人认为
怎么理解看个人。
闭包的生命周期
产生:在嵌套的 内部函数定义执行 完时
死亡:当嵌套的内部函数成为垃圾对象时
注意:函数定义执行并不等于函数执行!!这个涉及到声明提升的问题,不懂可以暂时百度或者留下评论我来解答。(我后面也会慢慢把相关部分的知识补全的~~)。
闭包的作用
- 将函数作为另一个函数的返回值。
- 将函数作为实参传递给另一个函数调用。
所以,我们可以通过闭包来定义我们自己的js模块。即将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露 一个或n个方法 的对象或函数。而模块使用者只需要通过暴露的对象来调用方法实现相应的功能即可。
方法一:(向外暴露一个接口)
function module() {
var msg = "Hello"
function fn1() {
console.log("fn1:" + msg.toUpperCase())
}
function fn2() {
console.log("fn2:" + msg.toLowerCase())
}
// 如果只有一个则直接返回,多个则封装成对象
return {//向外暴露一个接口,其内封装了可以使用的函数
fn1: fn1,
fn2: fn2
}
var f = module() //使用时需要声明一个变量来存储该接口
f.fn1() //调用
f.fn2()
}
方法二:(将接口的方法或函数添加为window的属性)
// 匿名函数自调用,即IIFE
(function module() {
var msg = "Hello"
function fn1() {
//转为大写
console.log("fn1:" + msg.toUpperCase())
}
function fn2() {
console.log("fn2:" + msg.toLowerCase())
}
// 将要暴露的方法或函数添加为window的属性
window.fn = {
fn1: fn1,
fn2: fn2
}
})()
// 使用时直接调用(已经是window的属性了,即全局)
fn.fn1()
fn.fn2()
当然对于方法二可以做一些优化,方便后期压缩代码。以下所有的window后期都可以被压缩,但上一个代码块中的window则不可以压缩
// 匿名函数自调用
(function module(window) {
var msg = "Hello"
function fn1() {
console.log("fn1:" + msg.toUpperCase())
}
function fn2() {
console.log("fn2:" + msg.toLowerCase())
}
window.fn = {
fn1: fn1,
fn2: fn2
}
})(window)
闭包的优点
- 使函数内部的变量在函数执行完后仍然存活在内存中(延长局部变量的生命周期)。
- 让函数外部可以操作到函数内部的数据(变量/函数)。
闭包的缺点
内存泄露:没有及时释放的空间。(可以将不再使用的私有变量赋值为null即可解决)
在这里要搞清楚2个概念,即内存溢出和内存泄漏。
- 内存溢出:所需空间超过可分配的内存空间。
- 内存泄露:没有及时释放的空间。(所占用的空间日益增加,使其更容易内存溢出。)
当然还有一些也可能造成内存泄漏:意外的全局变量(直接赋值,没有使用var。a=1);没有及时清理的计时器或回调函数等。
最后有一个关于闭包的问题,大家可以尝试着做一下哦。
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);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);