【JS】闭包及其应用

203 阅读4分钟

闭包

闭包形成的条件
  • 1.函数嵌套(JS可以在函数体中定义新的函数,这个新的函数称之为[嵌套]函数)
  • 2.将内部函数作为返回值返回
  • 3.内部函数必须使用到外部的变量

啥子是闭包

*默认情况下,函数产生的执行上下文在代码执行完后,都会被释放,来优化栈内存空间。

  1. MDN:闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
  2. 说法1:根据词法作用域规则,内部函数总是可以访问外部函数的变量。当外部函数执行完后,内部函数引用外部函数的变量依然保存在内存堆中,把这些变量的集合称为闭包。
  3. 说法2:闭包是一种机制,当函数执行上下文创建的东西(函数堆)被外部(全局)引用时,当前东西(函数堆)不能释放,函数执行上下文也不能被释放。

说法1

  • 作用域链查找 local->closure(fn)->global
  • 外部函数fn调用几次,就生成几个闭包。
let x = 5
const fn = function fn(x){
    return function (y){
        console.log(y + (++x))
    }
}
let f = fn(6)
f(7) //14
fn(8)(9) //18
f(10)//18
console.log(x) //5
1667630968666.png

image.png

说法2

闭包:函数执行产生一个私有上下文,私有变量被保护起来,不受外界的干扰。如果上下文不被释放,则私有变量值也会被保存下来,下级上下文就可以访问修改值。 保护+保存的机制->闭包。

let a = 0 ,
b = 0;
let A = function(a){
    A=function(b){
        alert(a+ ++b)
    }
    alert(a++)
}
A(1) // ‘1’
A(2) // ‘5’

1667615777438.png

闭包优缺点:

优点:

  • 延长局部变量的生命周期
  • 外部可对局部变量进行读写操作

缺点: 函数执行上下文不被释放造成大量的内存消耗,可能导致内存泄漏

闭包应用

  • 柯里化函数
  • compose函数
  • 函数的防抖/节流
  • bind函数...等

手撕Array.prototype.reduce()

reduce用法

reduce()  方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

// 应用
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (previousValue, currentValue) => previousValue + currentValue,
  initialValue
);
console.log(sumWithInitial);// 10

reduce参数

  • callBackFn 回调函数
  1. previousValue 上一次回调函数的返回值
  2. currentValue 当前值
  3. currentIndex 当前索引值
  4. array 遍历的数组
  • initialValue 初始值
    • 作为第一次调用 callback 函数时参数 previousValue 的值。若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。(number/[]/{})

异常

  • 数组为空且初始值 initialValue 未提供。
    • [].reduce(()=>{}) VM102:1 Uncaught TypeError: Reduce of empty array with no initial value
  • [].reduce(()=>{},10) // 10 空数组,有初始值直接返回初始值。
  • [20].reduce(()=>{}) // 20 若没有初始值且数组只有一项时,直接返回数组的第一项
  • 新增的元素不会被访问,未被访问元素删除了不会被访问,修改了访问时修改的元素
Array.prototype.myReduce = function myReduce(callBackFn,initialValue){
    if( typeof callBackFn !=='function'){
        throw new TypeError (callBackFn+ ' is not a function')
    }
    const array = this
    const  length =  array.length
    if(length === 0 && !initialValue){
        throw new TypeError ('Reduce of empty array with no initial value')
    }
    // 如果初始或者只有一项
    if(length === 1 && !initialValue){
        return array[0]
    }
    if(length === 0 && initialValue){
        return initialValue
    }
    let preValue = initialValue?initialValue:array[0]
    for(let i=0;i<length;i++){
        if(!initialValue && i === 0)continue
        preValue = callBackFn(preValue,array[i],i,array)
    }
   return preValue
}

compose函数

把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x)

例如
//mul3(add1(add(0)))
//const compose  = pip(mul3,add1,add)
//res = compose(0)

reduceRight() 从数组的末尾开始,再利用闭包保存function数组实现。


const double = x => x + x
const triple = x => 3 * x
const quadruple = x => 4 * x

// Function composition enabling pipe functionality
//const pipe = (...functions) => initialValue => functions.reduce(
//    (acc, fn) => fn(acc),
//    initialValue
//)
const pipe = (...functions) => {
    // ...functions拿到所有函数,
    let res = (initialValue)=>{
        return functions.reduce(
            (fnResult, fn) => fn(fnResult),
            initialValue
        )
    } 
    return res    
}
// Composed functions for multiplication of specific values
const multiply24 = pipe(double, triple, quadruple)
// Usage
multiply24(10) // 240

柯里化函数

柯里化 是一种转换,将 f(a,b,c) 转换为可以被以 f(a)(b)(c) 的形式进行调用。JavaScript 实现通常都保持该函数可以被正常调用,并且如果参数数量不足,则返回偏函数。它是一种思想,一种预先存储思想。

柯里化函数具有以下特点:

  • 函数可以作为参数传递
  • 函数能够作为函数的返回值
  • 闭包
const curring = function(){
    let params = []
    const add = (...args)=>{
        params = params.concat(args)
        return add

    }
    add.toString = ()=>{
       return params.reduce((res,item)=>res+item)
    }
    return add
}
let addFn = curring()
console.log(addFn(1)(2)(3).toString())//6