做为一个前端程序员,js中有一个重要的概念:闭包。闭包对于大部分的初学者来说理解起来十分的困难和晦涩,它成了新手成长的一个瓶颈,所以如何理解闭包尤为关键,还有理解了闭包如何去应用又十分的重要,今天呢就让我们好好的分析闭包。
概念
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
显然大部分人都能不喜欢这种官方的解释,ok,那直接上代码
function a () {
var b = 1;
return function () {
b++;
console.log(b);
}
}
var c = a();
c();//b=2
c();//b=3
console(b)//undined
这是一个十分简答的关于闭包的例子,在函数a中返回了一个函数赋值个变量c执行c发现b累加了,再次执行发现b在原来的基础上又加了1,且外部访问不到b变量,这个例子就非常直观的说明了闭包的特点:
- 返回函数内部函数
- 形成闭包内的变量保存在内存中不被销毁
- 形成闭包的需要外部变量对函数内部函数的引用
到这里相信大部分人还是能够理解闭包,感觉也并不复杂,那为什么会困扰许多新手盆友呢?
原因之一就在于闭包复杂的高级应用:
- 柯里化
柯里化是指这样一个函数,他接收函数A作为参数,运行后返回一个新的函数,并且这个函数能够处理函数A的剩余参数,上代码
function createCurry (func, args) {
var arity = fucn.length;
var args = args || [];
return function () {
var _args = [].slice.call(arguments);//将参数转化为数组
[].push.apply(_args, args);
if(_args.length < arity) {
return createCurry.call(this, func, _args);
}
return func.apply(this, _args);
}
}
这里运用了闭包和递归实现了一个柯里化函数,这个函数传入一个函数,传入的函数就被柯里化了,这个被柯里化的函数A能够实现一个参数收集的过程,并在参数收集完成之后执行该函数,若参数未收集完毕则返回一个函数。
柯里化函数在许多地方都有应用,他增加了函数的复杂度但是也加大了函数的自由度,在传参自由度方面有许多独特的设计。
- 模块化
现如今js模块化有许多标准,node端的commonjs ,浏览器端的requirejs,最初的模块化就是用闭包来实现,jQuery就是通过闭包给window对象暴露一个$对象。 - 函数作为参数
当一个函数作为另一个函数的参数时可以保存当前你存入的参数例如
class _lazyMan {
constructor(name) {
let _this = this
this.tasks = [];
let fn = (function(n) {
let name = n;
return function() {
console.log('HI! This is ' + name + '!');
_this.next();
}
})(name)
this.tasks.push(fn);
setTimeout(function() {
_this.next()
}, 0);
}
next() {
let fn = this.tasks.shift();
fn && fn();
}
eat(name) {
let _this = this
let fn = (function(name) {
return function() {
console.log('Eat ' + name + '~~');
_this.next();
}
})(name);
this.tasks.push(fn);
return this;
}
sleep(time) {
let _this = this;
let fn = (function(time) {
return function() {
setTimeout(function() {
console.log('Wake up after ' + time + 's!');
_this.next();
}, time * 1000);
}
})(time);
this.tasks.push(fn);
return this;
}
sleepFirst(time) {
let _this = this;
let fn = (function (time) {
return function () {
setTimeout(function () {
console.log('Wake up after ' + time + 's!');
_this.next();
}, time * 1000);
}
})(time);
this.tasks.unshift(fn);
return this;
}
}
为了实现这个lazyMan类,使用闭包保存当前参数并推入任务数组,这样就实现了一个任务列表的管理,这种任务列表的操作很多都用到了闭包缓存参数,最典型的例子就是观察者模式,在设计这种模式的时候回调函数的时候就运用到闭包。
- 节流和防抖
function debounce (func, delay) {
let timer
return function (...arg) {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, arg)
}, delay)
}
}
防抖和节流都是利用闭包缓存上一次调用函数的信息,然后根据这些信息作出判断给出相应的处理。
总结
闭包有许多高级应用,理解好闭包的作用对于理解一些常用框架的设计等十分有益