相信很多新人学习js的时候都绕不开一个问题,闭包。
一句话说清楚,闭包是由函数以及声明该函数的词法环境组合而成的,用不准确的大白话来说,就是函数(对象)中的函数(js中万物皆对象,函数是一种特殊的对象)。
那么问题来了,这玩意有什么用,函数中的函数不到处都是,为什么搞得这么神秘,面试中老喜欢问。下面就由我黒网吧天才少年带大家(自己)分析一段花裤衩老师在vue-element-admin中的一段防抖工具函数。
请看题:
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
一眼看下去,这是高手。
标准的模块模式设计模式。一种对闭包应用最为清晰的设计模式。(注:模块模式是为单例模式添加私有变量和私有方法,并减少全局变量的使用)
用闭包模拟私有方法
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:increment,``decrement 和 value。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个函数外部直接访问。必须通过函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。
请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。
接下来我们就可以很清晰地分析一下在调用这个工具函数的时候,它发生了什么:
import { debounce } from "@/utils/index";
const func = ()=>{ console.log(1) }
const btn1Obj = document.getElementById("btn2");
btn1Obj.addEventListener("click",debounce(func,3000),false);
1、import 并且在如果你调用过debounce的话,js会开辟了一块空间(调用几次开辟几个,互不干扰,能看出来使用闭包的缺点吗),创建了局部变量timeout, args, context, timestamp, result,还有一个later的函数,最后返回一个匿名函数,因为这个匿名函数内部有引用debounce中创建的变量,相互关连,支撑住了这块词法环境没有被垃圾回收,而是一直保留。
2、初始化完成之后,当你触发这个函数的时候,会直接进入retrun的匿名函数。接下来就是利用debounce中一直保留的词法环境写出一个严谨的防抖函数了。简单的说,就是利用这块词法环境记录上次点击的时间计算是否超出设置上限,再随便写几个if else完成。
最后感谢@花裤衩 ,让我能读到优秀的源码。
还有引用@明易 的《JS设计模式三:模块模式》链接:juejin.cn/post/684490…