闭包
定义
闭包(Closure)通常理解是 外部函数使用了内部函数的私有变量,导致私有变量无法释放
使用场景
1. 科里化函数
将多变量函数拆解为单变量(或部分变量)的多个函数并依次调用
例1 参数复用
function uri_curring(protocol) {
return function(hostname, pathname) {
return `${protocol}${hostname}${pathname}`;
}
} // 测试一下
const uri_https = uri_curring('https://');
const uri1 = uri_https('www.fedbook.cn', '/frontend-languages/javascript/function-currying/');
const uri2 = uri_https('www.fedbook.cn', '/handwritten/javascript/10-实现bind方法/');
const uri3 = uri_https('www.wenyuanblog.com', '/');
console.log(uri1);
console.log(uri2);
console.log(uri3);
例2 兼容性检测
因为浏览器的发展和各种原因,有些函数和方法是不被部分浏览器支持的,此时需要提前进行判断,从而确定用户的浏览器是否支持相应的方法。
以事件监听为例,IE(IE9 之前) 支持的是 attachEvent 方法,其它主流浏览器支持的是 addEventListener 方法,我们需要创建一个新的函数来进行两者的判断。
const addEvent = function(element, type, listener, useCapture) {
if(window.addEventListener) {
console.log('判断为其它浏览器')
// 和原生 addEventListener 一样的函数
// element: 需要添加事件监听的元素
// type: 为元素添加什么类型的事件
// listener: 执行的回调函数
// useCapture: 要进行事件冒泡或者事件捕获的选择
element.addEventListener(type, function(e) {
// 为了规避 this 指向问题,用 call 进行 this 的绑定
listener.call(element, e);
}, useCapture);
} else if(window.attachEvent) {
console.log('判断为 IE9 以下浏览器')
// 原生的 attachEvent 函数
// 不需要第四个参数,因为 IE 支持的是事件冒泡
// 多拼接一个 on,这样就可以使用统一书写形式的事件类型了
element.attachEvent('on' + type, function(e) {
listener.call(element, e);
});
}
}
// 测试一下
let div = document.querySelector('div');
let p = document.querySelector('p');
let span = document.querySelector('span');
addEvent(div, 'click', (e) => {console.log('点击了 div');}, true);
addEvent(p, 'click', (e) => {console.log('点击了 p');}, true);
addEvent(span, 'click', (e) => {console.log('点击了 span');}, true);
上面这种封装的弊端是:每次写监听事件的时候调用 addEvent 函数,都会进行 if...else... 的兼容性判断。事实上在代码中只需要执行一次兼容性判断就可以了,把根据一次判定之后的结果动态生成新的函数,以后就不必重新计算。
那么怎么用函数柯里化优化这个封装函数?
// 使用立即执行函数,当我们把这个函数放在文件的头部,就可以先进行执行判断
const addEvent = (function() {
if(window.addEventListener) {
console.log('判断为其它浏览器')
return function(element, type, listener, useCapture) {
element.addEventListener(type, function(e) {
listener.call(element, e);
}, useCapture);
}
} else if(window.attachEvent) {
console.log('判断为 IE9 以下浏览器')
return function(element, type, handler) {
element.attachEvent('on'+type, function(e) {
handler.call(element, e);
});
}
}
}) ();
// 测试一下
let div = document.querySelector('div');
let p = document.querySelector('p');
let span = document.querySelector('span');
addEvent(div, 'click', (e) => {console.log('点击了 div');}, true);
addEvent(p, 'click', (e) => {console.log('点击了 p');}, true);
addEvent(span, 'click', (e) => {console.log('点击了 span');}, true);
上述封装因为立即执行函数的原因,触发多次事件也依旧只会触发一次 if 条件判断。
这里使用了函数柯里化的两个特点:提前返回和延迟执行。
例3:实现一个 curry 函数
const add = (a,b,c)=>a+b+c;
let a1 = curry(add,1)
let a2 = a1(2)
let a3 = a2(3)
a3() // 6 也就是一个累加的过程,在面试中比较常见
functon curry(fn,...arg){
let totalArg = arg;
let fnLength = fn.length;
// 这个返回的函数就是 a1 ,a2
return fnRes = (...newArg)=>{
// 收集 新的 arguments
totalArg = [...newArg,...totalArg]
if(totalArg.length == fnLength){
// 因为 totalArg 是个数组,需要展开
return fn(...totalArg)
}else {
return fnRes
}
}
}
实现私有化变量
由于刚开始没有模块的概念,又担心命名冲突,所以使用闭包来隔绝作用域,现代
webpack打包结果是 匿名自执行性函数的
function One(i){
return function Two(){
console.log(i)
}
}
let a = One(1)
let b = One(2)
//在 Two 中使用了 One 中传递的变量 i;每次返回的都是一个新函数
匿名自执行函数
实现保护变量的作用,外部无法获取
num值,保护了num不会发生命名冲突,和上述的 私有变量 差不多
var funOne = (function One(){
var num = 0;
return Two(){
num++;
return num
}
})()
funOne() //1
funOne() //2
funOne() //3
缓存结果
let memoize = (fn) => {
let cache = {};
return function() {
let key = JSON.stringify(arguments)
cache[key] = cache[key] || fn.apply(this, arguments)
return cache[key]
}
}
let add = memoize((a,b)=>a+b)
add(1,3)
add(1,3) // 这个就会执行的是 cache
note:记录关于闭包的一些知识,便于以后自己复习,2022.5.15 18:18