js 闭包与函数

闭包

  • 函数嵌套,内部函数使用到外部函数变量,形成闭包
  • 作用 ----------- 延长函数内部变量的生命周期
  • 本质 ----------- 函数执行完毕会从执行栈移除函数,但闭包中内部函数的堆内存存在变量的引用,不会立即销毁,可以访问到外部函数的变量
  //闭包
    function makeFun(){
          const msg = 'hello word'
          return function(){
               console.log(msg)
          }
    }
    makeFun()()

纯函数

  • 相同的输入得到相同的输出
 //纯函数
    let array = [1,2,3,4,5,6]
    console.log(array.slice(0,3))
    console.log(array.slice(0,3))
    console.log(array.slice(0,3))
    //非纯函数
    console.log(array.splice(0,3))
    console.log(array.splice(0,3))
    console.log(array.splice(0,3))
  • 纯函数好处 可对结果进行缓存
  • 可测试
  • 并行处理
//记忆函数
    function memoize(callback) {
        let cache = {
        }
        return function () {
            let key = JSON.stringify(arguments)
            cache[key] = cache[key] || callback.apply(callback, arguments)
            return cache[key]
        }
    }
//相同输入函数只执行一次
    function getArea(r) {
        console.log('执行')
        return r * r
    }
    const res = memoize(getArea)
    console.log(res(2))
    console.log(res(2))
    console.log(res(2))

副作用

  • 外部状态影响函数的输出
  //副作用
    let mini = 18
    function checkAge(age) {
          return age>=mini
    }

    //纯函数
    function checkAge(age) {
          let mini = 18
          return age>=mini
    }
  • 副作用来源
  1. 数据库
  2. 配置文件
  3. 用户输入

柯里化

  • 柯里化 函数有多个参数,先传递一部分调用,然后返回新的函数接受剩余参数。返回结果
 //普通函数
    function checkAge(min) {
        return function (age) {
            return age >= min
        }
    }
    //箭头函数写法
    let checkAge = min => (age => age >= min)
    let age18 = checkAge(18)
    let age20 = checkAge(20)
    console.log(age18(24))
    console.log(age20(19))

lodash中的柯里化函数curry

  • lodash柯里化 curry函数如果接受函数全部参数,则返回函数执行结果,如果接受部分参数,则返回一个函数去接受剩余参数,直到参数接受完毕返回执行结果
    function getSum(a, b, c) {
        return a + b + c
    }
    const curry = _.curry(getSum)
    console.log(curry(2,3,4))  //输出9
    console.log(curry(2)(3,4)) //输出9
    console.log(curry(2,3)(4)) //输出9
    console.log(curry(2)(3)(4)) //输出9

      //数组柯里化案例

    const match = _.curry(function (reg, str) {
        return str.match(reg)
    })
    const hasSpace = match(/\s+/g)
    const hasNumber = match(/\d+/g)
    const filter = _.curry(function (callback, arr) {
        return arr.filter(callback)
    })

    const findSpace = filter(hasSpace)
    console.log(filter(hasSpace, ['Tom Smith', 'Bob']))  //输出 [ 'Tom Smith' ]
    console.log(findSpace(['Tom Smith', 'Bob'])) //输出 [ 'Tom Smith' ]


loadsh carry函数原理

 //loadsh curry原理
    function curryied(func) {
        return function curriedFn(...args) {
            //判断实参与形参个数
            if (args.length < func.length) {
                return function () {
                        return curriedFn(...args.concat(Array.from(arguments)))
                }
            }else{
                 return func(...args)
            }
        }
    }
    function getSum(a, b, c) {
        return a + b + c
    }
    const curry = curryied(getSum)
    console.log(curry(2, 3, 4))  //输出9
    console.log(curry(2)(3, 4)) //输出9
    console.log(curry(2, 3)(4)) //输出9
    console.log(curry(2)(3)(4)) //输出9 

函数组合

  • 一个函数由多个函数处理得到结果,把多个函数合并成一个函数的过程
  • 默认从右往左执行
 //函数组合
    function compose(f, g) {
        return function (value) {
            return f(g(value))
        }
    }
    function reverse(arr) {
        return arr.reverse()
    }
    function first(arr) {
        return arr[0]
    }
    const last = compose(first,reverse)
    console.log(last([1,2,3,8]))
  • 组合函数compose的实现
    //函数组合
    function compose(...args) {
        return function (value) {
            return args.reverse().reduce(function (acc, f) {
                return f(acc)
            }, value)
        }
    }
    //箭头函数写法
    const compose = (...args) => value => args.reverse().reduce((acc, f) => f(acc),value)
    const reverse = arr => arr.reverse()
    const first = arr => arr[0]
    const toUpper = str => str.toUpperCase()
    const fn = compose(toUpper, first, reverse)  //满足结合律
    const fn1 = compose(compose(toUpper, first), reverse)  //满足结合律
    const fn2 = compose(toUpper, first, compose(reverse))  //满足结合律
    console.log(fn(['join', 'tom', 'hzy']))  //输出HZY
    console.log(fn1(['join', 'tom', 'hzy'])) //输出HZY
    console.log(fn2(['join', 'tom', 'hzy'])) //输出HZY
}

组合函数调试

  //组合函数调试 
    //  NENVER SAY NO =>  never-say-no
    const split = _.curry((sep, str) => _.split(str, sep))
    const join = _.curry((sep, arr) => _.join(arr, sep))
    const map = _.curry((fn, arr) => _.map(arr, fn))
    //    const log = str => {
    //        console.log(str)
    //         return str
    //    }
    const trace = _.curry((tag, v) => {
        console.log(tag, v)
        return v
    })
    //const f= _.flowRight(join('-'),log,_.toLower,split(' '))
    const f = _.flowRight(join('-'), trace('在map之后打印'), map(_.toLower), split(' '))

    console.log(f('NENVER SAY NO'))

  • lodash 里fp模块提供了柯里化函数 函数优先 数据滞后
  //loadsh fp模块   函数优先  数据滞后
    const fp = require('lodash/fp')
    const res = fp.map(fp.toLower, ['A', 'B', 'C']) //fp函数都是柯里化的函数
    const fn = fp.map(fp.toLower)
    const res2 = fn(['A', 'B', 'C'])
    console.log(res)
    console.log(res2)
    const f = fp.flowRight(fp.join('-'),fp.map(fp.toLower), fp.split(' ')) // never-say-no
    console.log(f('NENVER SAY NO'))
  • lodash map与fp模块map差异
//lodash map与fp模块区别
    console.log(_.map(['23', '8', '10'], parseInt))  //输出23  NAN 2
    //执行过程 parseInt('23',0,['1','2','3']) //十进制 23 转10进制 =>23
    //执行过程 parseInt('8',1,['1','2','3']) // 不支持进制
    //执行过程 parseInt('10',2,['1','2','3']) //10转二进制 => 2
    const fp = require('lodash/fp')
    console.log(fp.map(parseInt,['23', '8', '10']))  //fp模块函数参数只接受一个当前处理的元素
  • Point Free 本质是函数组合 不需要指明处理的数据 只需要合成运算过程 只需要定义辅助基本运算函数
 const fp = require('lodash/fp')
    const f = fp.flowRight(fp.join('.'), fp.map(item => {
        item = item.substring(0, 1).toUpperCase() + item.substring(1)
        return item
    }), fp.split(' '))
    console.log(f('hello world'))
    // hello world  => H.W
    const f2 = fp.flowRight(fp.join('.'), fp.map(fp.flowRight(fp.first,fp.toUpper)), fp.split(' '))
    console.log(f2('hello world'))

函子

  • 实现了map的对象,函数式编程不直接操作值,而是由函子完成
   //函子
    class container {
        static of(value){
             return new container(value)
        }
        constructor(value) {
            this._value = value
        }
        map(fn) {
            return container.of(fn(this._value))
        }
    }
    const r = container.of(100).map(value=>value+1).map(value=>value+2) 
    console.log(r)//输出103

MayBe函子 Either函子 IO函子

  • MayBe函子 对外部空值进行处理,避免错误
 
    class MayBe {
        static of(value) {
            return new MayBe(value)
        }
        constructor(value) {
            this._value = value
        }
        isNone(){
            return this._value===null||this._value===undefined
        }
        map(fn) {
            return this.isNone?MayBe.of(null):MayBe.of(fn(this._value))
        }
    }
    console.log(MayBe.of(null).map(item=>item.toUpperCase()))  //MayBe { _value: null }
  • Either函子 记录错误信息

    class Left {
        static of(value) {
            return new Left(value)
        }
        constructor(value) {
            this._value = value
        }
        map(fn) {
            return this
        }
    }
    class Right extends Left{
        static of(value) {
            return new Right(value)
        }
        constructor(value) {
            super(value)
        }
        map(fn) {
            return Right.of(fn(this._value))
        }
    }
    function parseJson(str){
         try{
              return Right.of(JSON.parse(str))
         }catch(e){
            return Left.of({error:e.message})
         }
    }
    console.log( Right.of(10).map(item=>item+1))
    console.log( Left.of(10).map(item=>item+1))
    console.log( parseJson('{a:"1"}')) 
    // Left {_value: { error: 'Unexpected token a in JSON at position 1' } }
  • IO函子 把不纯的函数存储到value中,延迟执行,执行交给调用者处理
 //IO函子
    class IO{
        static of(x) {
            return new IO(function(){
                return x
            })
        }
        constructor(fn) {
            this._value = fn
        }
        map(fn) {
            //当前_value和传入的fn组合成新的函数
            return new IO(fp.flowRight(fn,this._value))
        }
    }
    let fn = IO.of(10).map(item=>item+1)
    console.log( fn._value())

folktale 函数库

  • 提供函数式处理操作 函子
  //folktale 函数库  提供函数式处理操作 函子
    const {compose,curry} = require('folktale/core/lambda')
    const {toUpper,first} = require('lodash/fp')
    const f = curry(2,(x,y)=>{  //第一个参数是函数参数数量
         return x+y
    })
    console.log(f(1))   // [Function]
    console.log(f(1,2)) //3
    const f2 = compose(toUpper,first)
    console.log(f2(['hello','world']))    //HELLO
  • task异步任务
  // folktale Task异步任务 返回一个函子
    const { task } = require('folktale/concurrency/task')
    const { split, find } = require('lodash/fp')
    const fs = require('fs')
    function readFile(fileName) {
        return task(resolver => {
            fs.readFile(fileName, 'utf-8', (err, data) => {
                   if(err) resolver.reject()
                   resolver.resolve(data)
            })
        })
    }
     readFile('package.json')
    .map(split('\n'))
    .map(find(item=>item.includes('version')))
    .run()
    .listen({
        onRejected:err=>{
             console.log(err)
        },
        onResolved:data=>{
            console.log(data)
        }
    })
  • 函子嵌套问题
 const fp = require('lodash/fp')
    const fs = require('fs')
    class IO {
        static of(x) {
            return new IO(function () {
                return x
            })
        }
        constructor(fn) {
            this._value = fn
        }
        map(fn) {
            //当前_value和传入的fn组合成新的函数
            return new IO(fp.flowRight(fn, this._value))
        }
    }
    const readFile = filename => {
        return new IO(() => {
            return  fs.readFileSync(filename, 'utf-8')
        })
    }
    const print = x => {
        return new IO(() => {
            console.log('***')
            console.log(x)
            console.log('***')
            return x
        })
    }
    const cat = fp.flowRight(print,readFile)
    let r= cat('package.json')._value()._value()
    console.log(r)
  • 解决方案
 //Monad函子  包含of join两个方法
    const fp = require('lodash/fp')
    const fs = require('fs')
    class IO {
        static of(x) {
            return new IO(function () {
                return x
            })
        }
        constructor(fn) {
            this._value = fn
        }
        join(){
            return this._value()
        }
        flatMap(fn){
            return this.map(fn).join()
        }
        map(fn) {
            //当前_value和传入的fn组合成新的函数
            return new IO(fp.flowRight(fn, this._value))
        }
    }
    const readFile = filename => {
        return new IO(() => {
            return  fs.readFileSync(filename, 'utf-8')
        })
    }
    const print = x => {
        return new IO(() => {
            console.log('***')
            console.log(x)
            console.log('***')
            return x
        })
    }
    let r= readFile('package.json').flatMap(print).join()
    // readFile =>  { _value: () => {return  fs.readFileSync(filename, 'utf-8') }}
    console.log(r)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355