LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

闭包

2023/3/12 前端

闭包是一种特殊的对象/特殊的机制。

闭包由两部分组成:执行上下文(A)和在该执行上下文中创建的函数(B);

当B执行时访问了A中变量对象中的值,就产生了闭包;

另一个解释:闭包是一项技术或者一个特性,函数作用域中的变量在函数执行完之后就会被垃圾回收,一般情况下访问一个函数作用域中的变量是无法访问的,只能通过特殊的技术或者特性来实现,就是在函数作用域中创建内部函数来实现,这样就不会使得函数执行完成变量被回收,这种技术或特性被称为闭包。像是把变量包裹了起来,形象的称为闭包。

经典应用

  • 利用闭包,修改下面的代码,让循环输出的结果依次为1,2,3,4,5

源代码:

for(let i = 1; i <= 5; i++){
    setTimeout(function timer(){
        console.log(i);
    },i*1000)
}

在这个例子中,首先会运行for循环,每趟运行到setTimeout的时候,就将它放入任务队列,当5次for循环完成之后,再去运行任务队列中的定时任务,此时定时任务的词法环境中不存在i,就向上寻找到for循环结束时的i为5,然后定时输出五个5。
**其实这个可以正确输出,因为let形成了块级作用域

修改:

for(let i = 1; i <= 5; i++){
    (function(i){
        setTimeout(function timer(){
            console.log(i);
        })
    },i*1000)(i)
}

原理:借助闭包的特性,使用一个自执行函数提供闭包条件,每次传入i值保存在闭包中。

还可以写成:

for(let i = 1; i <= 5; i++){
    setTimeout((function(i){
        return function(){
            console.log(i);
        }
    })(i),i*1000)
}
  • 柯里化

柯里化(currying)是一种关于函数的高阶技术,它不仅用于Javascript,还被用于其他编程语言。

柯里化是一种函数的转换,它是指将一个函数从可调用的f(a,b,c)转换为可调用的f(a)(b)(c)。他不会调用函数,只是对函数进行转换。

一个柯里化的函数首先会接收一些参数,接受了这些参数之后,该函数不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数中形成闭包被保存起来,待到函数真正需要求值的时候,之前传入的参数会被用于一次性求值。

简单例子:

function sum(a){
    return function(b){
        return a+b;
    }
}
console.log(sum(1)(2));
//3

在这个例子里,sum首先接受了一个参数a,将其存在闭包里,在接收另一个参数b之后才进行计算。

将柯里化过程封装成柯里化函数:

function curry(f){
    return function(a){
        return function(b){
            return f(a,b);
        }
    }
}
function sum(a,b){
    return a+b;
}
let curriedSum = curry(sum);
console.log(curriedSum(1)(2));
//3

在上面这个例子里,curriedSum实际上是一个包装器function(a),当他被curriedSum(1)调用时,返回一个包装器function(b),并将a存在词法环境中,当function(b)以参数2被调用时,就会把参数传递给原始的sum函数。闭包体现在function(b)能够取到参数a的值。

高级柯里化:

function curry(f){
    return function curried(...args){
        //f.length是函数f定义时的参数个数
        if(args.length >= f.length){
            return f.apply(this,args);
        }else{
            return function(...args2){
                return curried.apply(this,args.concat(args2));
            }
        }
    }
}
function sum(a,b,c){
    return a+b+c;
}
let curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3));
//6
console.log(curriedSum(1,2)(3));
//6

当运行柯里化函数时,有两个if分支,如果传入的args长度与原始定义的f.length相同或更长,那么只需要使用f.apply将调用传递给他即可;否则返回一个包装器,重新应用curried,将之前传入的参数与新的参数一起传入。