【JS基础记录】闭包与高阶函数

闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即时函数是在当前词法作用域之外执行。

function foo () {
    let a = 2;
    function bar(){
        console.log(a);
    }
    return bar;
}
let baz = foo();
baz();  // 2

由于 bar 声明在 foo 函数内部, bar 拥有涵盖 foo 内部作用域的闭包,使得 foo 的内部作用域一直存活不被回收。一般来说,函数在执行完后其整个内部作用域都会被销毁,因为JavaScript 的 GC(Garbage Collection)垃圾回收机制会自动回收不再使用的内存空间。但是闭包会阻止某些 GC,比如本例中 foo() 执行完,因为返回的 bar 函数依然持有其所在作用域的引用,所以其内部作用域不会被回收。

利用闭包实现结果缓存(备忘模式)

Array.prototype.slice(arguments): arguments是一个具有 length的对象,使用这个方法可以使这个对象使用数组的slice这个方法。

  • Array是构造函数;
  • arguments是类数组对象,缺少很多数组的方法;
  • call让一个对象去调用另一个对象的方法;
  • slice切割数组并返回新的数组。

里可以利用闭包的特点来实现一个简单的缓存,在函数内部用一个对象存储输入的参数,如果下次再输入相同的参数,那就比较一下对象的属性,如果有缓存,就直接把值从这个对象里面取出来

function memorize(fn) { 
    const cache = {}
    return function(...args) {
        // var args = Array.prototype.slice.call(arguments)
        const key = JSON.stringify(args);
        return cache[key] || (cache[key] = fn.apply(null, args))
 	}
}
/* 复杂计算函数 */
function add(a) {
 return a + 1 
}
var adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2) // 输出: 3 当前: cache: { '[1]': 2, '[2]': 3 }

高阶函数

高阶函数就是输入参数里有函数,或者输出是函数的函数。

函数作为参数

如果你用过 setTimeout 、 setInterval 、ajax 请求,那么你已经用过高阶函数了,这是我们最常看到的场景:回调函数,因为它将函数作为参数传递给另一个函数。

函数作为返回值

经常看到的高阶函数的场景是在一个函数内部输出另一个函数, 主要是利用闭包来保持着作用域。

function add () {
    let num = 0;
     return function(a){
         return num = num + a;
     }
}
let adder = add();
adder(1);  // 1
adder(2);  // 3

柯里化

柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的原函数变换成接受一个单一参数(原函数的第一个参数)的函数,并且返回一个新函数,新函数能够接受余下的参数,最后返回同原函数一样的结果。

function multiply(a, b, c){
    return a * b * c;
}
multiply(1,2,3);

// 使用柯里化
function multiply(a){
    return b => {
        return c => {
            return a * b *c
        }
    }
}
multiply(1)(2)(3);

柯里化有 3 个常见作用: 参数复用、提前返回、延迟计算/运行

// ES5
function currying(fn){
    let reset1 = Array.prototype.slice.call(arguments);
    reset.shift();
    return function (){
        let reset2 = Array.prototype.slice.call(arguments);
        return fn.apply(null, reset1.concat(reset2));
    }
}

// ES6
function currying(fn, ...reset1){
    return function(...reset2){
        return fn.apply(null, reset1.concat(reset2));
    }
}

function sayHello(name, age, fruit) {
 console.log(console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`))
}
var curryingShowMsg1 = currying(sayHello, '小明')
curryingShowMsg1(22, '苹果') // 输出: 我叫 小明,我 22 岁了, 我喜欢吃 苹果
var curryingShowMsg2 = currying(sayHello, '小衰', 20)
curryingShowMsg2('西瓜') // 输出: 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
编写可以复用和配置的小代码块:
function discount(price, discount){
    return price * discount;
}
let price1 = discount(500, 0,1);
let price2 = discount(200, 0.1);
let price3 = discount(10000, 0.1);

// 改编为柯里化
function discount(discount){
    return (price) => {
        return price * discount;
    }
}
// 计算1折之后的价格
const tenPercentDiscount = discount(0.1);
tenPercentDiscount(500);
tenPercentDiscount(200);

// 计算2折之后的价格
const twentyPercentDiscount = discount(0.2);
twentyPercentDiscount(500);
twentyPercentDiscount(1000);

即在只能确定一个参数而无法确定另一个参数时,代码设计变得更方便与高效,达到了提示性能的目的。

避免频繁调用具有相同参数的函数:
function volume(l, w, h){
    return l * w * h;
}
volume(200, 30, 100);
volume(32, 45, 100);
volume(25, 15, 100);

// 改编为柯里化, 100的值是固定的
function volume(h){
    return (l) => {
        return (w) => {
            return l * w * h;
        }
    }
}
const hCylinderHeight = volume(100);
hCylinderHeight(200)(30) // 600000
hCylinderHeight(2322)(232) // 53870400
通用的柯里函数
function curry(fn, ...reset1){
	return (...reset2) => {
        return fn.apply(null, reset1.concat(reset2));
    }
}

function volume(l, h, w){
    return l * h * w;
}
const hCy = curry(volume, 100);
hCy(200, 900);  // 18000000
hCy(70, 60);    // 420000
使用递归实现curry函数

只给curry函数传递一个fn就达到目的

function curry(fn){
    const c = (...reset1) => reset1.length === fn.length ?
              fn(...reset1) : (...reset2) => c(...reset1,  ...reset2);
	return c;
}

反柯里化

// ES5
function unCurrying(fn){
    return function (tar){
        let reset = Array.prototype.slice.call(arguments);
        reset.unshift();
        return fn.apply(tar, reset);
    }
}
// ES6
function unCurrying(fn){
    return function(tar, ...reset){
        return fn.apply(tar, reset);
    }
}

let push = unCurrying(Array.prototype.push);
function execuPush(){
    push(arguments, 4);
    console.log(arguments);
}

execuPush(1,2,3);

简单说,函数柯里化就是对高阶函数的降阶处理,缩小适用范围,创建一个针对性更强的函数。而反柯里化就是反过来,增加适用范围,让方法使用场景更大。使用反柯里化, 可以把原生方法借出来,让任何对象拥有原生对象的方法。

可以这样理解柯里化和反柯里化的区别:

  1. 柯里化是在运算前提前传参,可以传递多个参数;
  2. 反柯里化是延迟传参,在运算时把原来已经固定的参数或者 this 上下文等当作参数延迟到未来传递。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值