【JavaScript】06. 闭包

112 阅读4分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

闭包

01. 闭包

(1)含义

  • 闭包:就是由嵌套函数形成的一个环境

    • 定义在内部的函数可以访问外部函数的变量
    • 这个内部函数就是闭包
  • 这个变量可以重复使用,且不会造成全局污染

    function father(){
        var money = 1
        function son(){
            console.log(money++)
        }
        return son
    }
    

(2)创建闭包

  • 构成闭包经典三步:
    • 外层函数嵌套内层函数
    • 内层函数使用外层函数的局部变量
    • 外层函数将内层函数作为返回值返回

var fun1 = father()
fun1() // 1
fun1() // 2
fun1() // 3

var fun2 = father()
fun2() // 1
fun2() // 2
fun2() // 3

每调用一次外层函数就得到一套新的闭包

多次调用形成多个闭包,不会互相影响

(3)闭包的特点

  • 特点:
    • 构成闭包经典三步
      1. 外层函数嵌套内层函数
      2. 内层函数要使用外层函数的局部变量
      3. 内层函数作为外层函数的返回值
    • 闭包的好处就是变量可以重复使用而且不会污染全局
    • 闭包可以在一个作用域里访问另外一个作用域的局部变量
    • 闭包的原理用一句话表示就是外层函数的活动对象不能被释放
    • 但是,正是因为外层函数活动对象不能被释放,所以会占用过多的内存,并且有内存泄漏的风险
      • (内存泄漏:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收)

02. 应用

模拟私有属性
  • 返回一个闭包函数,只能由这个函数去访问内部变量
// 模拟私有属性
function getGeneratorFunc () {
    var _name = 'ruovan';
    var _age = 24;

    return {
        getName: function () {
            return _name
        },
        getAge: function() {
            return _age
        }
    };
}

var obj = getGeneratorFunc();
let a = obj.getName(); // ruovan
let b = obj.getAge(); // 24
let c = obj._age; // undefined -- 不能直接访问

console.log(a,b,c) // ruovan 24 undefined

柯里化
  • 将接收多个参数的函数变成接收一个单一参数的函数

    • 且,返回一个函数

    • 这个函数还可以接受参数,并返回结果

    举个例子:carry(1,2,3,4,5) == carry(1)(2)(3)(4)(5) == carry(1)(2,3)(4,5)

  • 其优势在于:参数的复用

    • 可以在传入参数的基础上,在生成另一个新的函数

  • 具体思路:

    • (1)首先我们要实现这个函数,用于累加(或者其他处理)

      // 处理函数
      function add() {
          return [...arguments].reduce((prev, curr) => prev + curr)
      }
      
    • (2)然后,我们要把这个add()函数作为参数,传给一个柯里化函数

      // 柯里化函数
      function curry(fn) {
          // 它返回一个函数
          return function(){
              // 在函数内部调用 add() 函数,并返回结果
              // 同时还要改变 this 指向调用它的那个对象
              // 这里的 this 指向调用它的那个对象
              // 这里的 arguments 是返回函数内部的参数
              return fn.apply(this, [...arguments])
          }
      }
      // 使用
      let myCurry = curry(fn)
      console.log(myCurry(1,2,3,4,5)) // 15
      
    • (3)然后,我们还要实现返回函数还可以继续传参,继续调用

      // 柯里化函数
      function curry(fn) {
          // 1. 首先,它要有个变量用于接收上次函数的结果
          // 这里直接用 arguments 来接收
          let outArgs = [].slice.call(arguments, 1)
          // 2. 然后,它要返回一个函数
          // 返回的函数要继续调用,所以这里要返回 curry() 函数
          return function() {
              // 2.1 接收本次函数传递的参数
              let innerArgs = [].slice.call(arguments)
              // 2.2 拼接上次结果参数和本次传递参数
              let allArgs = [...outArgs, ...innerArgs]
              // 2.3 返回 curry() 函数的调用
              return curry.call(null, fn, ...allArgs)
          }
      }
      
    • (4)不过这里没有add函数的调用了,而且打印的结果是一个函数

      • 所以要改造一下柯里化函数的toString()方法
      // 柯里化函数
      function curry(fn) {
          // 1. 首先,它要有个变量用于接收上次函数的结果
          // 这里直接用 arguments 来接收
          let outArgs = [].slice.call(arguments, 1)
          // 2. 然后,它要定义一个内部函数,用于返回、处理toString()方法
          function curried() {
              // 2.1 接收本次函数传递的参数
              let innerArgs = [...arguments]
              // 2.2 拼接上次结果参数和本次传递参数
              let allArgs = [...outArgs, ...innerArgs]
              // 2.3 返回 curry() 函数的调用
              return curry.call(null, fn, ...allArgs)
          }
          // 3. 处理 toString 方法,将 add 函数在这里调用
          curried.toString = function() {
              return fn.apply(null, [...outArgs])
          }
          // 4. 最后,返回这个内部函数
          return curried
      }
      // 使用
      let myCurry = curry(add)
      console.log(myCurry(1)(2)(3)) // f 6
      console.log(myCurry(1,2)(3,4,5)) // f 15
      
      
      

本人前端小菜鸡,如有不对请谅解