闭包的应用场景

177 阅读3分钟

这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

闭包可以读取函数内部的变量,并让这些变量始终保存在内存中;

立即调用的函数表达式 (IIFE)

立即调用的函数表达式 (IIFE,Immediately Invoked Function Expression): 顾名思义,就是在创建函数后立即调用的函数。

1. 写法

函数被包裹在括号里,被当做函数表达式,后面再紧跟一组括号会立即调用它前面的函数表达式。

// 第一种写法
(function(){
    console.log('巴拉巴拉')
})(); // 巴拉巴拉

// 第二种写法
(function(){
    console.log('巴拉巴拉')
}()); // 巴拉巴拉

2. 应用

  1. IIFE模拟块级作用域, es6之前很常用的一种方式
// es6 let
for(let i =0; i < 5; i++){
    console.log(i)
}

防止变量定义外泄, 也不会导致闭包相关的内存问题,因为不存在对这个匿名函数的引用。只要函数执行完毕, 其作用域链就可以被销毁。

惰性函数

函数内部的判断只会执行一次,后序再执行都不会再做判断逻辑。他的原理是使用了闭包,在内部做完条件判断后,返回了一个新的函数出来,等于是当前函数已经被改写了。

const addEvent = (function () {
    if (window.addEventListener) {
        return (elem, type, fn, capture) => {
            elem.addEventListener(type, (e) => fn.call(elem, e), capture);
        };
    } else {
        return (elem, type, fn, capture) => {
            elem.attachEvent('on' + type, (e) => fn.call(elem, e);
        };
    }
})();

缓存数据

空间换时间。就是在多次调用计算方法的时候,其实有的值之前可能已经计算过了,我们可以利用闭包将计算结果存在函数内,计算之前先判断缓存内是否有,没有的话就再计算,更新缓存。

function fnL() {
    var only = 'onlyfnL';
    return function fn2(ang=only){
        var name = 'namefn2';
        only = ang;
        console.log(only,name)
    }
}
let act1 = fnL();
let act2 = fnL();

单例模式

单例模式是设计模式的一种,一个类只有一个实例,在创建实例的时候会先判断是否已经有实例,如果有则直接返回,没有的话才创建,优点就是避免重复创建实例,减少内存开销。

// 单例模式
function Singleton(){
  this.data = 'singleton';
}

Singleton.getInstance = (function () {
  var instance;
    
  return function(){
    if (instance) {
      return instance;
    } else {
      instance = new Singleton();
      return instance;
    }
  }
})();

var sa = Singleton.getInstance();
var sb = Singleton.getInstance();
console.log(sa === sb); // true
console.log(sa.data); // 'singleton'

私有变量

保存自己私有变量,通过提供接口给外部使用,但外部不能直接访问该变量。

// 模拟私有属性
function getGeneratorFunc () {
  var _name = 'John';
  var _age = 22;
    
  return function () {
    return {
      getName: function () {return _name;},
      getAge: function() {return _age;}
    };
  };
}

var obj = getGeneratorFunc()();
obj.getName(); // John
obj.getAge(); // 22
obj._age; // undefined

compose函数

容易和柯里化函数搞混,compose是将多个回调调用的函数整合成一个接受多个函数为参数的函数,即F(A(B(x)))=>P(F,A,B)(x)
compose函数可以让整个js代码可读性更强。他的原理其实也是用到了闭包,实现如下

var compose = function(...args) {
        var len = args.length
        var count = len - 1
        var result
        return function f1(...args1) {
            result = args[count].apply(this, args1)
            if (count <= 0) {
                count = len - 1
                return result
            } else {
                count--
                return f1.call(null, result)
            }
        }
    }

科里化

维基百科中定义:科里化(英文:Currying): 又译为卡瑞化或加里化;
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

柯里化的优点有:

  1. 参数的复用,如果多次调用一个函数,传入的参数大部分都是相同的,可以考虑用柯里化函数,将前面相同参数的部分封装起来,再去接受后面不同的参数,减少函数的调用。
  2. 延迟计算,分段传入参数调用函数,等真正需要返回结果的时候,不传参数直接调用。
function currying(fn, args) {
  var _this = this
  var len = fn.length
  var args = args || []

  return function() {
    var _args = Array.prototype.slice.call(arguments)
    Array.prototype.push.apply(args, _args)
    
    if(_args.length < len) {
      return currying.call(this, fn, _args)
    }

    return fn.apply(this, _args)
  }
}

函数柯里化的作用是能够使得参数复用和函数的延迟执行

偏应用函数

偏函数是指使用一个函数并将其应用一个或多个参数,但不是全部参数;他会创建一个接收剩余参数的函数

function info(country) {
  return function (province, city) {
    console.log(country + "-" + province + "-" + city);
  };
}
let place = info("中国")
place(); //中国-undefined-undefined
place("山东省", "济南市"); // 中国-浙江省-杭州市