上文回顾:高阶函数即接收函数作为参数并且(或者)返回函数作为输出的函数。

1.什么是闭包?

闭包的概念与JavaScript的作用域有关。简而言之,闭包就是一个内部函数。

function outer(){
    function inner(){
        console.log('inner')
    }
}

函数inner()就是闭包函数。
闭包函数

2.闭包的作用

闭包强大的原因在于它对作用域链(或作用域层级)的访问。

闭包的3个可访问作用域

(1) 在它自身声明之内声明的变量
(2) 对全局变量的访问
(3) 对外部函数变量的访问!闭包还可以访问外部函数的参数!

let global = "global"
function outer(){
    let outer = "outer"
    function inner(){
        let a = "余浩"
        console.log(a) //作用域(1);打印结果:余浩
        console.log(global) //作用域(2);打印结果:global
        console.log(outer) //作用域(3);打印结果:outer
    }
    inner() //调用inner函数
}

闭包函数能够访问外部函数的变量。此处外部函数的定义是包裹闭包函数的函数。

闭包可以记住它的上下文

let fn = (arg) => { //有参函数
    let outer = "看得见"
    let innerFn = () => { //无参函数
        console.log(outer)
        console.log(arg)
    }
    return innerFn //高阶函数应用:返回一个函数
}

函数innerFn()对于fn()来说是一个闭包函数,并且fn被调用时返回了innerFn。

运行fn:

var closureFn = fn(5)
closureFn()
//结果打印出:看得见 5

分析:

  1. 函数fn(5)执行后返回innerFn()函数
  2. 当innerFn()函数被返回时,js执行引擎把innerFn()视为一个闭包,并设置了前面所说的三个作用域。
  3. 在作用域内闭包记录了arg参数和outer变量的值
  4. 打印出正确结果

3.闭包的应用场景

回顾sort()函数:

let points = [40, 100, 1, 5, 25, 10];
points.sort((a,b)=> b - a);
//Array(6) [ 100, 40, 25, 10, 5, 1 ]

闭包应用sortBy()函数举例:

const sortBy = (property) => {
    return (a,b) => {
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result;
    }
}

sortBy()接收一个property参数并返回一个接收两个参数的新函数。

4.闭包应用于真实的高阶函数

4.1 tap()函数

此处tap()函数接收一个value参数并返回一个包含vale的闭包函数。
在JavaScript中,使用逗号运算符,(exp1,exp2)的含义是它将执行两个参数并返回最后一个逗号之后的表达式结果,即exp2
此处运用了柯里化,把多个参数函数转化为一个嵌套的一元函数的过程。tap函数接收两个参数,参数一为value,参数二为一个函数

const tap = (value) => (fn) => (
    typeof(fn) === 'function' && fn(value),
    console.log(value)
)

调用tap函数:
tap("fun")((arg) => console.log("value is ",arg))
打印结果:
value is fun
fun

4.2 unary()函数

unary()函数的任务是接收一个给定多个参数的函数,并把它转化为一个只接受一个参数的函数。
先检查传入的fn是否为一个长度为1的参数列表(可以通过length查看),如果有就什么也不做;如果没有就返回一个新函数,它只接受一个参数arg,并用该参数调用fn

const unary = (fn) =>
  fn.length === 1
    ? fn
    : (arg) => fn(arg)

4.3 once()函数

实际运用过程中我们可能遇到只运行一次给定的函数的情况,比如发起一次性的银行支付请求等。
once()函数接收一个参数fn并通过它的apply方法返回结果。
定义了done变量,初始值为false;
返回的函数会形成一个覆盖done变量的闭包作用域;
返回的函数会检查done是否为true,如果为true则表示已执行,则返回undefined;
如果done为false则表示函数没有被执行过,设done为true(如此就组织了下一次执行)

const once = (fn) => {
  let done = false;

  return function () {
    return done ? undefined : ((done = true), fn.apply(this, arguments))
  }
}

调用once()函数:
let doPayment = once(() => {console.log("Payment is done")})
第一次调用:doPayment() 打印Payment is done
第一次调用:doPayment() 打印undefined

4.4 memoized()函数

回顾纯函数:纯函数只依赖它的参数运行,它不依赖外部环境,其结果完全依赖于它自己的参数。
memoized()函数: 它可以使函数能够记住其计算结果。即可以重用函数中的计算结果。例如在阶乘中使用。

const memoized = (fn) => {
  const lookupTable = {};

  return (arg) => lookupTable[arg] || (lookupTable[arg] = fn(arg));
}

解析:

函数中定义了一个lookupTable的局部变量,它在返回的函数闭包上下文中;
返回函数首先接收arg参数并检查它是否存在于lookupTable的局部变量中;
如果在,则返回对应值;
如果不在,则使用新输入的值作为key,fn的结果作为value,更新lookupTable对象。

调用:

//slow factorial
var factorial = (n) => {
  if (n === 0) {
    return 1;
  }

  // This is it! Recursion!!
  return n * factorial(n - 1);
}

console.log("Factorial of 2 is", factorial(2))
console.log("Factorial of 3 is", factorial(3))

//memoized factorial
let fastFactorial = memoized((n) => {
  if (n === 0) {
    return 1;
  }

  // This is it! Recursion!!
  return n * fastFactorial(n - 1);
})

console.log("Fast Factorial of 2 is", fastFactorial(2))
console.log("Fast Factorial of 3 is", fastFactorial(3))
console.log("Fast Factorial of 7 is", fastFactorial(7))