前言
闭包这东西都讲烂了,随便一搜就有大堆的文章在说,之所以在写一遍也是为了能加深记忆做好总结。平时用的时候就直接写,但出去面试的被问到的时候却不知道如何回答的周全,那怎么回答算是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
有什么区别吗?看起来相差也不大,但是为什么要使用上面的写法呢,