闭包知识与应用
什么是闭包?如何制造闭包?
基本概念
> 【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>