什么是闭包?它到底用来干什么?.md
什么是闭包
很多小伙伴对闭包的概念不够清晰,觉得知道一点,但是又不是很对或者不够全面,其实在平常的开发中很多时候都用到了闭包,只是剥离出他们的本质,导致经常一知半解。
闭包的本质就是在一个函数内部创建另一个函数,比较经典的就是一个函数可以访问其外部函数作用域中的变量。闭包是将函数内部和函数外部连接起来的桥梁!!!
在 js
中,函数是一等公民,因此函数可以作为参数传递给其他函数,也可以从其他函数中返回。当一个函数返回另一个函数时,返回的函数可以访问其父函数的作用域中的变量,这就是闭包的实现。
我的理解是:跨作用域访问变量就形成了闭包
闭包的常用用途
- 闭包的一个常见用途是创建私有变量。
- 创建一个一直存在的引用
- 作为参数被传入或者是作为返回值被返回 下面是一个简单的闭包示例:
function calculate() {
var count = 0; // 私有化了
return { // 返回函数
add: function() {
count++;
},
sub: function() {
count--;
},
getCount: function() {
return count;
}
};
}
var counter = calculate(); // 一直存在的引用
counter.add();
counter.add();
console.log(counter.getCount()); // 输出 2
在这个例子中,calculate
函数返回一个对象,该对象具有 add
、sub
和 getCount
方法。这些方法都可以访问 count
变量,但是外部时无法直接访问 count
变量。因此, count
变量是私有的,只能通过返回的对象方法来访问和修改。这就是 私有变量
而且,由于 counter
一直被引用,所以不会被释放,每次调用 add
方法都是基于上一次计算之后的 count
再去做计算,所以调用两次 add
, count
就为 2
说到这儿,想起一个经典的面试题
for(var i = 1; i < 5; i++){
setTimeout(() => {
console.log(i)
}, i * 1000)
}
// 输出4个5
要求把上面的代码改一下,让其输入1、2、3、4
一般的就是直接把 var
改成 let
,但是闭包也可以做到
for (var i = 1; i < 5; i++) {
//闭包
(function (i) {
setTimeout(() => console.log(i), 1000 * i)
})(i)
}
// 1、2、3、4
闭包的特性
- 函数套函数(这儿不是必须的,因为闭包就是跨作用域访问变量,外部访问内部或者是内部访问外部,正常情况下,从外部访问不了内部变量,那么就需要
return
一个函数出来,让你在外部可以使用这个函数和变量,不然你无法使用这个闭包!return
出一个函数跟闭包本身是无关的,因为只要跨作用域访问了变量,就已经形成闭包了,所以return
出来仅仅是为了方便使用,也就是上面说的 闭包就是将函数内部和函数外部连接起来的一座桥梁) - 内部函数可以直接使用外部函数的局部变量或参数
- 变量或参数不会被垃圾回收机制回收(引用一直存在的情况下);
闭包的优点
- 跨作用域访问变量
- 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突(封闭性)
- 一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在(持久性)
闭包的缺点
- 被引用的私有变量不能被销毁,增大了内存消耗,在
IE
中可能导致内存泄露,解决方法是在退出函数之前,将不使用的局部变量全部删除或者是设置为null
; - 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(
object
)使用,把闭包当作它的公用方法(Public Method
),把内部变量当作它的私有属性(private value
),这时一定要小心,不要随便改变父函数内部变量的值
再来看看常见的一些闭包案例
function foo() {
var local = 1
function bar() {
local++ // 内部访问外部的 local
return local
}
return bar // return bar 函数 方便闭包被使用
}
var func = foo()
console.log('func()',func()); // 2
function fn() {
var i = 10;
return function (n) {
console.log(n + (++i));
}
}
var f = fn(); //首先把fn执行,然后把执行的结果赋值给f,函数执行只要看函数里面有没有return
f(15); //26 //引用,执行完不会立即释放
f(20); //32 // 为什么是32 因为 f(20)表示这个f函数还在调用,那个 var f = fn()就还在用,就不会销毁,就形成闭包,缓存变量
fn()(15); //26 //没有赋值给其他变量,也就没有引用,每次执行完内存释放,作用域销毁
fn()(15); //26 // 为什么是正常的?因为这个是直接fn()()调用 这杨没有赋值,执行完就销毁了
fn()(20); //31
fn()(30); //41
f(30); //43 // 原理一样 赋值给f,导致成为全局变量 有形成了闭包
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log('object.getNameFunc()()', object.getNameFunc()()); // The Window
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
console.log('object.getNameFunc()()', object.getNameFunc()()); // My Object
拓展:
谷歌浏览器:
我们开辟一个内存,可能有一些其他的变量等占用了这个内存,谷歌浏览器都看这个内存还有没有被占用,如果发现有没有被占用的内存了,就自己帮我们回收了(内存释放)
火狐和IE:
我们开个一个内存,当我们引用了它,就在内存中记录一个数,增加一个引用浏览器就把这个数+1,减少一个引用,浏览器就把这个数-1...当减到零的时候浏览器就把这个内存释放了;但是有些情况下(尤其是 IE
)记着记着就弄乱了,内存就不能释放了(浏览器的内存泄露)
var obj={}
当我们这个对象 obj
使用完成了,手动的搞一下 obj=null
内存就会释放掉
转载自:https://juejin.cn/post/7236739875533570104