闭包小知识:闭包实现数据缓存、变量隔离,以及柯里化的使用场景

缓存

什么是缓存函数?

接收一个函数,用闭包将每次函数执行的结果缓存起来

缓存例子1:

	/*
    * 闭包实现缓存
    * 属性:有个键--值   --->所以可以将缓存数据存放在一个对象中
    * 方法:缓存存储   setCache
    *      缓存的获取  getCache
    * */
    function  configCache(){
        var  obj={};//设置一个内部的对象 用来存储缓存数据;这个属性是私有的
        //对外暴露两个公共的方法
        return {
            setCache:function(k,v){//设置缓存
                obj[k]=v;
            },
            getCache:function(k){//获取缓存
                return obj[k];
            }
        };
    }
    var conf = configCache();
    console.log(conf);
    conf.setCache(1,'sdwqecwqv');
    console.log(conf.getCache(1));//sdwqecwqv
    /*
    * 注意下面这种情况,两次configCache()会产生不同的执行环境
    * */
    configCache().setCache(1,'sdwqecwqv');
    console.log(configCache().getCache(1));//undefined
    /*
    * 使用立即执行函数
    * */
    var cacheConfig = (function(){
        var obj={};
        return {
            setCache:function(k,v){
                obj[k]=v;
            },
            getCache:function(k){
                return obj[k];
            }
        }
    })();

上面代码能看到外部cache可以读取也可以设置useCache内部的data,由此实现了缓存。

缓存函数的应用场景

缓存函数-柯里化

const memoize = (fn) => {
    const cache = {};
    const func = (...args) => {
      const key = JSON.stringify(args); // 函数入参序列化成string做为本次递归结果的唯一标识
      return cache[key] || (cache[key] = fn.apply(null, args)); // 如果缓存中有则直接返回 否则执行本次递归并将结果和key存入缓存
    };
    return func;
};

例子1:求1到n的和优化

//使用缓存函数
const add = (n) => {
  console.log('执行了一次');
  if (n === 1) {
    return 1;
  }
  return add(n - 1) + n;
};


const addFn = memoize(add);
console.log(addFn(3)); // 执行3次
console.log(addFn(3)); // 不会执行 缓存里有结果了 直接取

------------------------------------------------------------------------------------
//不使用
const useAdd = () => {
  const cache = {};
  const add = (n) => {
    console.log('执行了', cache);
    if (n === 1) {
      return 1;
    }
    return cache[n] || (cache[n] = add(n - 1) + n); // 如果缓存有值取缓存,否则继续递归存入缓存
  };
  return add;
};
const add = useAdd();
console.log(add(3)); // 会调用3次
console.log(add(2)); // 2已经缓存过了 所以只调用一次 从缓存中取

例子2:求斐波那契数列优化

const useGetRabbit = () => {
    const cache = {};
    return function getRabbit(n) {
      if (n === 1 || n === 2) {
        return 1; // 出生后第三个月开始每个月才会生一对兔子,第三个月之前还是1对
      }
      return cache[n] || (cache[n] = getRabbit(n - 1) + getRabbit(n - 2)); // 如果缓存有就取缓存 否则当前月兔子对数取前两个月之和
    };
};
const getRabbit = useGetRabbit();
console.log(getRabbit(48)); // 如果不用闭包缓存48个月就算不出来报栈溢出错误了

利用闭包及纯函数的特性来缓存函数每次调用的结果提高性能,避免重复的计算,使用时要注意一定要是个纯函数!

隔离变量

function useCache () {
      const data = {};
      const setItem = (key, value) => {
          data[key] = value
      }
      const getItem = key => {
          return data[key]
      }
      return {
          state: {
              data
          },
          getItem,
          setItem
      }
  }
  const cache = useCache()
  console.log(cache.state.data);
  cache.setItem('a', '1')
  cache.getItem('a')

可以这样看,data、setItem、getItem是被隔离在useCache内的数据或方法,在外部可以读取到或调用,但也只有cache有这个权限。

函数柯里化

通俗的说,柯里化是指一个函数,他接收函数作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数A的剩余参数,完整柯里化通常依赖 JS 的闭包。

function curry(fn) {
  let arr = [];
  return function curried(...args) {
    arr = [...arr, ...args]
    if (arr.length > fn.length) {
      return fn(...arr);
    }
    return curried;
  }
}

function sum(a, b, c) {
  return a * b * c;
}

let sum2 = curry(sum)
let sum3 = curry(sum)
console.log(sum2(1)(2)(2, 3)) // 4
console.log(sum3(1)(2)(3, 2)) // 6

上面的例子中,被传入的函数 fn 的调用时机,依赖于它自身的参数个数,当闭包属性 arr 长度达到要求后,才会被调用。这样的需求我个人感觉不多,那么函数柯里化的好处是什么?下面是柯里化更常用的一种场景:

// 更自由的组合参数
function createRequest(option, request) {
  return function (args) {
    return request({...option, ...args })
  }
}

function request(option) {
  return option;
}

let longRequest = createRequest({
  timeout: 24 * 3600 * 1000
}, request)

let longAndUserRequest = createRequest({
  api: '/user'
}, longRequest)

var res = longAndUserRequest({ name : '111' })
// api: "/user"
// name: "111"
// timeout: 86400000

它可以自由组合参数,让代码更简洁,比如 Function.prototype.bind 以及 axios.create 都使用了 函数柯里化的特性,我认为函数柯里化是一种设计模式,有点类似组合模式。这种组合方式缺点是:导致调用栈增加。

posted @ 2022-11-07 21:50  青川薄  阅读(329)  评论(0编辑  收藏  举报