美文网首页Javascript
【JavaScript ES6 函数式编程入门经典】读书笔记(第

【JavaScript ES6 函数式编程入门经典】读书笔记(第

作者: 八宝君 | 来源:发表于2019-12-20 11:12 被阅读0次

    第四章 闭包与高阶函数

    4.1 理解闭包

    简而言之,闭包就是一个内部函数,是在另一个函数内部的函数,比如

    function outer() {
      function inner() {
      }
    }
    

    这就是闭包,函数inner称为闭包函数,闭包如此强大的原因在于它对作用域链(或作用域层级)的访问。
    闭包有3个可访问的作用域:

    • 1.在它自身声明之内声明的变量
    function outer() {
      function inner() {
        let a = 5;
        console.log(a)
      }
      inner() // 调用inner函数
    }
    
    // tips: inner函数在outer函数的外部是不可见的
    

    当inner函数被调用时,控制台返回5,因为闭包函数可以访问所有在其声明内部声明的变量。

    • 2.对全局变量的访问
      现将上面的代码片段修改为:
    let global = "global"
    function outer() {
      function inner() {
        let a = 5
        console.log(global)
      }
      inner() // 调用inner函数
    }
    

    现在当inner函数执行后,打印出变量global,如此,闭包就能访问全局变量了

    • 3.对外部函数变量的访问
      再修改一下函数
    let global = "global"
    function outer() {
      let outer = "outer"
      function inner() {
        let a = 5
        console.log(outer)
      }
      inner() // 调用inner函数
    }
    

    当inner函数执行后,打印出变量outer,看起来是合理的,但却是一个非常重要的闭包属性。
    闭包能够访问外部函数的变量,此处外部函数的含义是包裹闭包函数的函数。

    闭包可以访问外部函数的参数,比如将outer函数添加一个参数,在inner函数中尝试访问它,是可以拿到该参数的。

    闭包还有一个重要概念: 闭包可以记住它的上下文

    var fn = (arg) => {
      let outer = "Visible"
      let innerFn = () => {
        console.log(outer)
        console.log(arg)
      }
      return innerFn
    }
    
    var closureFn = fn(5)
    closureFn()
    =>Visible
    =>5
    

    解析
    1.var closureFn = fn(5),这里fn被参数5调用,它返回了innerFn,
    2.当innerFn被返回时,javascript执行引擎视innerFn为一个闭包,并且相应地设置了它的作用域
    闭包有3个作用于层级,这3个层级(arg、outer值将被设置到inner的作用域层级中)在innerFn返回时都被设置了,如此closureFn通过作用域链被调用时就记住了arg、outer值。
    3.最后调用closureFn时,因为它已经记住了它的上下文(作用域,也就是outer和arg),因此对console.log的调用才能正确打印出结果。

    回顾上章的sortBy函数

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

    当我们以如下方式调用sortBy时,sortBy("firstname")
    发生了以下事情:
    sortBy函数返回了一个接受两个参数的新函数
    (a, b) => { /* 实现 */ }
    闭包的返回函数能够范围sortBy函数的参数property,该函数只有在sortBy被调用时才会返回。而这时property参数会被替换为一个值。因此返回函数将在其生命周期中持有该上下文:
    // 通过闭包持有的作用域
    property = "passedValue"
    (a, b) => { /* 实现 */ }
    由于返回函数在它的上下文中持有property的值,所以它将在合适并且需要的时候使用返回值。

    4.2 真实的高阶函数

    • tap函数
    const tap = (value) =>
      (fn) => (
        typeof(fn) === 'function' && fn(value),
        console.log(value)
      )
    // tap函数接受一个value,并返回一个包含value的闭包函数,该函数将被执行。
    

    javascript中,(exp1, exp2)的含义是它将执行两个参数并返回第二个表达式的结果,即exp2。

    运行tap函数
    tap("fun")((it) => console.log("value is ", it))
    => value is fun
    => fun
    那么这个函数有什么用处,假设遍历一个来自服务器的数组,并发现数据错了,想调试一下,看看数组内究竟包含了什么,会如何做?抛弃命令式的方法,用函数是的方法来看,这正是使用tap函数,对于此场景可以这样做

    forEach([1, 2, 3], (a) =>
      tap(a)(()=>{
        console.log(a)
      })
    )
    
    • unary函数
      array原型有一个默认的方法称为map,map是一个与之前定义的forEach函数非常相似的函数(遍历),唯一的区别是map返回了回调函数的结果。
      假设要使一个数组加倍并得到结果,可以使用map函数以如下方式实现:
      [1, 2, 3].map((a) => { return a * a })
      =>[1, 4, 9]
      这里map用3个参数调用了函数,分别是element、index和arr,假设要把字符串数组解析为整数数组,有一个内置的函数叫做parseInt,它接受两个参数parse和radix,如果可能,该函数会把传入的parse转换成数字。如果把parseInt传给map函数,map会把index的值传给parseInt的radix参数,这将产生意想不到的行为。
    ['1', '2', '3'].map(parseInt)
    => [1, NaN, NaN]
    
    这个结果并不是我们期望的,我们需要把parseInt函数转换成为另一个只接受一个参数的函数。
    用unary函数可以做到,它的任务是接受一个给定的多参数函数,并把它转换成一个只接受一个参数的函数,如下:
    const unary = (fn) =>
      fn.length === 1
        ? fn
        : (arg) => fn(arg)
    

    检查传入的fn是否有一个长度为1的参数列表(可通过length属性查看),有就什么也不做;没有就返回一个新函数,只接收一个参数arg,并且该参数调用fn,重新运行上面的问题。
    ['1', '2', '3'].map(unary(parseInt))
    => [1, 2, 3]
    此处unary函数返回了一个新函数(parseInt的克隆体),它只接收一个参数,如此map函数传入的index、arr参数就不会对程序产生影响。

    也有像binary一样的函数,它们转换函数,使其接受相应的参数。

    • once函数
      只运行一次给定的函数,比如只想设置一次第三方库,或者初始化一次支付设置。
    const once = (fn) => {
      let done = false
      return function () {
        return done ? undefined : (done =true), fn.apply(this, arguments)
      }
    }
    

    接受一个参数fn,并且通过调用它的apply方法返回结果。声明了一个done变量,初始值为false, 返回的函数会形成一个覆盖它的闭包作用域,因此,返回的函数会访问并检查done是否为true,是则返回undefined,否则将done设置为true(阻止了下一次执行),并用必要的参数调用fn

    apply函数允许设置函数的上下文,并为给定的函数传递参数。

    var doPayment = once(() => {
      console.log("Payment is done")
    })
    doPayment()
    => Payment is done
    doPayment()
    => undefined
    
    • memoized函数
      假设有一个纯函数名为factorial,计算给定数字的阶乘
    var factorial = (n) => {
      if (n === 0) {
        return 1
      }
      // 递归
      return n*facotrial(n-1)
    }
    

    该函数只依赖它的参数执行,其它不需要。有一个局限性,无法重用之前的计算结果,memoized函数能够记住其计算结果

    const memoized = (fn) => {
      const lookupTable = {}
      return (arg) => lookupTable[arg] || (lookupTable[arg] = fn(arg))
    }
    

    声明lookupTable的局部变量,它在返回函数的闭包上下文中,返回函数将接受一个参数并检查它是否在lookupTable中,如果在则返回对应的值(lookupTable[arg]);否则,使用新的输入作为key,fn的结果作为value,更新lookupTable对象(lookupTable[arg] = fn(arg))
    现在将上面的factorial用memoized函数改写

    let fastFactorial = memoized((n) => {
      if (n === 0) return 1
      // 递归
      return n *fastFactorial(n-1)
    })
    
    调用fastFactorial
    fastFactorial(5)
    => 120
    

    它以同样的方式运行,但是比之前快得多,运行fastFactorial时,会检查lookupTable对象。

    附上
    第三章地址:高阶函数
    第二章地址:JavaScript函数基础
    第一章地址:函数编程简介

    相关文章

      网友评论

        本文标题:【JavaScript ES6 函数式编程入门经典】读书笔记(第

        本文链接:https://www.haomeiwen.com/subject/xxhmnctx.html