在谈闭包前,我们需要知道作用域和变量生存期
作用域和变量生命期
js中的常见作用域分为两种,全局作用域和函数作用域。
let a =1 //全局变量,整个js文件执行完才会被销毁 let test =()=>{ let b =2 //函数作用域变量,该函数被执行后被摧毁 console.log('b',b) //2 } test() console.log(a) //1 console.log(b) //not defined
从这个例子我们可以得出,函数内部声明的变量只能在函数内部使用,这就是作用域的限制.而b这个变量也在函数test执行后就被销毁了,我想再拿到这个变量也拿不到了.所以我们需要在调用test函数的时候将b保存起来,在后面就可以用了.
比如
let a =1 //全局变量,整个js文件执行完才会被销毁 let c = 0 //声明一个全局变量 let test =()=>{ let b =2 //函数作用域变量,该函数被执行后被摧毁 c=b //执行函数时将b的值赋值给全局变量c console.log('b',b) //2 } test() console.log(a) //1 console.log(c) //2 console.log(b) //not defined
这种方案就是将函数变量的值保存在全局变量里面,这样我在需要使用这个值的时候就可以从全局变量里拿.
优点:变量由函数作用域提升到了全局作用域,可以跨函数使用.
缺点:1.额外的变量声明,容易造成变量污染.
2.代码的耦合性变高,函数的独立性遭到了破坏
作用域链
let a =1 //全局变量,整个js文件执行完才会被销毁 let b= 0 //声明一个全局变量 let test =()=>{ let b =2 //函数作用域变量,该函数被执行后被摧毁 let fn= ()=>{ //这里打印一个b,虽然函数fn中没有b,但是有两个b,一个是函数作用域的b,一个是全局的b //就近原则,先在本函数中找,没有就往上面的找,上面没有就往更上面,直到找到一个 console.log(b) //2 } fn() } test() console.log(a) //1 console.log(b) //0
闭包
由作用域链可以知道,函数内部的函数可以获得函数的变量.搁这儿绕口令呢?
就是子函数可以获得父函数的变量.假如我需要获得一个函数的变量,那我就给他一个儿子.
但是呢.就算是子函数拿到数据了,执行了就又把内部的变量给摧毁了.所以,我们需要给他一个子函数,而且我还不直接执行子函数,但是又必须在我需要的时候能执行子函数.这时我们就可以返回这个子函数.
let a =1 //全局变量,整个js文件执行完才会被销毁 let b= 0 //声明一个全局变量 let test =()=>{ let b =2 //函数作用域变量,该函数被执行后被摧毁 let fn= ()=>{ //这里打印一个b,虽然函数fn中没有b,但是有两个b,一个是函数作用域的b,一个是全局的b //就近原则,先在本函数中找,没有就往上面的找,上面没有就往更上面,直到找到一个 return b //只要执行了这个函数,就可以拿到test这个函数内部的b } return fn //只要执行了test这个函数,就能获得test函数的子函数fn } let myFn = test() console.log(a) //1 console.log('b',myFn()) //2
所以,所谓的闭包,从目的的角度来分析就是:利用作用域链,延长函数内部变量生命期.从手段来说就是,在一个函数中返回一个函数,返回的这个函数中包含上层函数的变量.
应用
memo函数/reduce函数
即,我现在有一个函数add(1,2),是将1和2加起来的,但是我希望有个memo函数对这个函数进行处理,处理完之后返回一个新函数,这个函数会判断你的传参,假如你的传参已经在之前传过一次了,下次调用的时候直接给你结果而不去计算了.
1.返回一个新函数
2.每次调用都需要去对比以前的数据,所以需要数据保存
先看下以下的代码
let add =(x,y,z)=>{ return x+y+z } //这个函数是用来处理函数的,所以参数是一个函数 let curry =(fn)=>{ let list = [] return (x,y,z)=>{ list.push(fn(x,y,z)) console.log('list',list) return fn } } let curryAdd =curry(add) curryAdd(1,2,3) //list [6]
到这里为止,我们返回了一个函数,这个函数的入参和之前一摸一样,而且我们也把计算的数据存了起来.那是不是每次存都有用呢
let add =(x,y,z)=>{ return x+y+z } //这个函数是用来处理函数的,所以参数是一个函数 let curry =(fn)=>{ let list = [] return (x,y,z)=>{ list.push(fn(x,y,z)) console.log('list',list) return fn } } let curryAdd =curry(add) curryAdd(1,2,3) //list [6] curryAdd(1,2,4)//list [6,7] curryAdd(1,2,5)//list [6,7,8]
所以,我们现在做到了每次计算都能把数据存到一个共同的数组里.所以我们就可以优化一下内部逻辑,存的时候存对象,然后把结果也存起来,每次进来都判断一下,之前没调过相同参数就调用fn,有用过就将保存的结果输出.具体就不细写了.而reduce函数更简单了,每次只要保存上一次的值就行了.