前言
闭包这东西都讲烂了,随便一搜就有大堆的文章在说,之所以在写一遍也是为了能加深记忆做好总结。平时用的时候就直接写,但出去面试的被问到的时候却不知道如何回答的周全,那怎么回答算是OK的呢?
来看下MDN定义:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域,通俗一点就是函数里面在嵌套函数。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
如果被问到的话就可以回答上述内容,在加深一点的话就是,函数在执行的会放执行栈中,当函数执行完成之后就会从栈中移除掉,但是如果使用是闭包的话,堆中作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数成员。
闭包
看下面代码深刻一下印象:
function makeFunc() {
var name = "Mozilla"; // name 是一个被 makeFunc 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
return displayName;
}
var myFunc = makeFunc();
myFunc();makeFunc() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 makeFunc() 里的内部函数,并且仅在 makeFunc() 函数体内可用。请注意,displayName() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 makeFunc() 中声明的变量 name 。
如果对闭包了解不深的情况下,以为执行了makeFunc() 之后会被移除,其实不然。在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。
内存泄漏
闭包如果在处理不当时,会造成内存泄漏,也就是说垃圾回收机制无法被销毁,最常见的是DOM元素形成的闭包。看下面例子
function closure(){
// div用完之后一直存在内存中,无法被回收
var div = document.getElementById('div');
div.onclick = function () {
console.log(div.innerHTML);// 这里用oDiv导致内存泄露
};
}
// 解除引用避免内存泄露
function closure(){
var div = document.getElementById('div');
var test = div.innerHTML;
div.onclick = function () {
console.log(test);
};
div = null;
}闭包的常见应用
节流简述,就是防止在一段时间内重复执行,相当于一个开关锁?
var throttle = function(fn, delay) {
var time = null
return function() {
var self = this
if(!time) {
time = setTimeout(function(){
fn.apply(self)
time = null
}, delay)
}
}
}防抖简述,就是重复执行时,清除之前的,一直使用的是最后一个。
var throttle = function(fn, delay) {
var time = null
return function() {
var self = this
clearTimeout(time)
time = setTimeout(function(){
fn.apply(self)
}, delay)
}
}once 一次执行的函数,通过返回一个函数,等第一次调用之后设置状态,下次调用获取状态再 return 掉。
function once (fn) {
let called = false
return function () {
if (called) {
return
}
called = true
fn.apply(this, arguments)
}
}总结几点
- 形成:函数中嵌套函数
- 作用:函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
- 优点:希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
- 缺点:无法回收闭包中引用变量,容易造成内存泄漏
函数科里化
接着上面的闭包,其实科里化可以说是闭包的基础实现,比如说之前经常出现的题:
实现一个函数,使 sum(1)(2) 得 3
function sum(x) {
return function(y) {
return x + y;
};
}
var s = sum(1)(2)
console.log(s) // 3
// 不使用科里化来得到两个数的和
function sum(x, y) {
return x + y;
}
var s = sum(1, 2)
console.log(s) // 3有什么区别吗?看起来相差也不大,但是为什么要使用上面的写法呢,