闭包知识与应用


什么是闭包?如何制造闭包?

基本概念

> 【JS忍者秘籍】闭包允许函数访问并操作函数外部的变量。   > 【红宝书】闭包是指有权访问另外一个函数作用域中的变量的函数。   > 【MDN 】 闭包是指那些能够访问自由变量的函数。

如何形成闭包:内部的函数存在外部作用域的引用就会导致闭包

词法作用域对执行环境的保护

// 矿区工厂
function createMine() {
    const s = '??'
}

// 挖出??
function mine() {
  console.log(s)
}
//方法一:函数做为返回值

function createMine() {
  const s = '??'
  return function() {
    console.log(s)
  }
}

const m = createMine()
m()
//??
//方法二:函数作为参数

const b = []
function createMine(b) {
  const s = '??'
  b.push(function() {
    console.log(s)
  })
}
createMine(b)
b[0]()
//??
//方法三:作用域 利用形参

const mine
function createMine(b) {
  const s = '??'
  mine = function() {
    console.log(s)
  })
}
createMine()
mine()
//??

利用闭包制造惰性函数

惰性函数:函数式编程的一个重要概念。可以有效提高程序的运行效率。

表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。

1.实现计算缓存,平方数

var cache = {}
function square(n) {
    if (!cache[n]) {
        cache[n] = n * n;
     }
     return cache[n];
}
隐藏全局变量: 以上代码虽然可以实现功能,但是最大的问题就是缓存需要定义一个全局变量,这个变量可以隐藏
var square = (function () {
    var cache = {};
    return function(n) {
        if (!cache[n]) {
            cache[n] = n * n;
        }
        return cache[n];
    }
})();

2.单例模式:

设计模式之一,它保证了一个类只有一个实例。实现方法一般是先判断实例是否存在,如果存在就直接返回,否则就创建了再返回。 优点:避免了重复实例化带来的内存开销 单例模式前端最典型的应用场景,全局唯一消息框  

    
    
    Document
    



    
click!

3.提高浏览器兼容问题的执行效率

了解决浏览器之间的行为差异,经常会在代码中包含了大量的 if 语句,以检查浏览器特性,解决不同浏览器的兼容问题。
显然这些if语句我们只希望在一个浏览器中只执行一遍, 最佳的办法就是使用惰性函数将结果缓存起来
function addEvent(type, element, fun) {
  if (element.addEventListener) {
    addEvent = function (type, element, fun) {
      element.addEventListener(type, fun, false);
    }
  }
  else if (element.attachEvent) {
    addEvent = function (type, element, fun) {
      element.attachEvent('on' + type, fun);
    }
  }
  else {
    addEvent = function (type, element, fun) {
      element['on' + type] = fun;
    }
  }
  return addEvent(type, element, fun);
}

//第一次执行: 选择合适的api并且执行

//第二次执行: 由于addEvent已经被指定为其中的一种
function createXHR() {
  var xhr = null;
  if (typeof XMLHttpRequest != "undefined") {
    xhr = new XMLHttpRequest();
    createXHR = function () {
      return new XMLHttpRequest();
    };
  } else {
    try {
      xhr = new ActiveXObject("Msxml2.XMLHTTP");
      createXHR = function () {
        return new ActiveXObject("Msxml2.XMLHTTP");
      };
    } catch (e) {
      try {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
        createXHR = function () {
          return new ActiveXObject("Microsoft.XMLHTTP");
        };
      } catch (e) {
        createXHR = function () {
          return null;
        };
      }
    }
  }
  return xhr;
}
ajax兼容性code

偏应用函数与柯里化

偏应用函数 Partial application
// Helper to create partially applied functions
// Takes a function and some arguments
const partial = (f, ...args) =>
  // returns a function that takes the rest of the arguments
  (...moreArgs) =>
    // and calls the original function with all of them
    f(...args, ...moreArgs)

// Something to apply
const add3 = (a, b, c) => a + b + c

// Partially applying `2` and `3` to `add3` gives you a one-argument function
const fivePlus = partial(add3, 2, 3) // (c) => 2 + 3 + c

fivePlus(4) // 9
  柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
const curry = fn => x => y => fn(x , y)
curriedSum(40)(2) // 42.
const add2 = curriedSum(2) // (b) => 2 + b
add2(10) // 12
实例1: 分割函数转CSV
const split = (regex, str) => ("" + str).split(regex);
const elements = split("v1,v2,v3", /,\s*/);
const partial =
  (f, ...args) =>
  (...moreArgs) =>
    f(...args, ...moreArgs);
const csv = partial(split, /,\s*/);
const s = csv("v1,v2,v3");
console.log(s);

实例2: Vue3 CompositionAPI From Antfu https://github.com/vueuse/vueuse
持续性增加 反复打开页面
const useLocalStorage = (key, defaultValue) => {
    const data = reactive({});
    Object.assign(
      data,
      (localStorage[key] && JSON.parse(localStorage[key])) || defaultValue
    );
    watchEffect(() => (localStorage[key] = JSON.stringify(data)));
    return data;
  };

  //用于指定持久化方案
  const useStorage = (defaultValue) => {
     return useLocalStorage("store", defaultValue);
   };
   const store = useStorage({ count: 0 });

立即执行函数

IIFE Immediately-Invoked Function Expressions
- 声明一个匿名函数
- 马上调用这个匿名函数
- 销毁该函数(因为语句结束,没有任何引用了)   可以与闭包结合使用

1.创建临时独立作用域

假设想创建一个累加器这样的功能,需要一个临时变量用于保存累加状态。放在全局作用域显然不太优雅,最简单的方法就是创建一个IIFE然后创建变量,返回一个函数,然后再函数中完成累加功能。
//一般写法
var n= 0
setInterval(() => console.log(++n), 1000)
setInterval((function() {
  var n = 0
  return function() {
    console.log(++n)
  }
})(), 1000)
// 1,2,3 
//实现n不可见

2.解决变量名冲突


3.循环陷阱

原因是由创建的函数引用外部变量形成闭包。所有的新创建的函数引用的都是同一个变量。而在调用函数时变量又都


循环内加入即时函数
由于即时函数的参数为实参复制关系,相当于复制的现场快照  

4.使用简洁变量名

var data {
  abc : {
    efg : 0
  } 
}

(function() {
  console.log(v)
})(data.abc.efg)

类库封装

闭包和即时函数的另外一个重要用途就是类库封装
类库封装要求:类库封装最重要的要求就是不能让类库中的变量污染全局。
比如jQuery只暴露 $ 就好了 实战Rollup   npx rollup -f iife -n $ -o ./dist/jquery.js  ./src/jquery.js - -f 指定iife方式输出 - -o 指定输出文件 - -n 指定输出变量名
var $ = (function (exports) {
    'use strict';

    function jQuery() {
        console.log('I am jQuery');
    }

    exports.jQuery = jQuery;

    Object.defineProperty(exports, '__esModule', { value: true });

    return exports;

})({});

//调用

<script src="./dist/jquery.js">script>
<script>
    // test
    $()
script>