今天在JavaScript世界里,我见证了代码如何像人类一样学会"社交恐惧"和"自律"的神奇技能。私有变量像社恐患者一样躲着外部世界,节流函数像自律达人一样控制行为频率,而闭包则是它们的秘密基地。让我们一起来看看这些有趣的编程技巧吧!
一、私有变量:代码中的"社恐患者"
在下述代码中,我们看到了一位资深社恐患者——私有变量的精彩表演:
function Book(title, author, year) {
// 对内 私有
// 对外
let _title = title; // _开始的变量 叫做私有变量 内部 有利于可读性的编程风格
let _author = author;
let _year = year;
this.getTitle = function () { // 对外的方法
return _title; // 可以访问私有变量
}
// 私有方法
function getFullTitle() {
return `${_title} by ${_author} `;
}
this.getFullInfo = function () {
return `${getFullTitle()},published in ${_year}`
}
this.getAuthor = function () {
return _author;
}
this.getYear = function () {
return _year;
}
// 类的声明者,和类的使用者,可能是两拨人
}
let book = new Book('JavaScript高级程序设计', 'Nicholas C.Zakas', '2011')
这个Book类就像个社交恐惧症患者:
- 私有变量(
_title,_author,_year)是它的内心世界,外面无法直接访问 - 公共方法(
getTitle,getFullInfo)是它对外沟通的有限渠道 - 尝试直接访问私有属性?
book._title只会得到undefined,就像对社恐患者问隐私问题只会得到沉默
更有趣的是,我们还能给私有属性设置"门禁系统":
this.updateYear = function(newYear) {
if (typeof newYear === 'number' && newYear > 0) {
_year = newYear; // 只有符合条件的访客才能进入
} else {
console.error('Invalid year'); // 其他人吃闭门羹
}
}
console.log(book._title); // 私有属性不能通过实例访问 undefined
console.log(book.getTitle()); // 可以通过实例访问 私有属性
console.log(book.getFullInfo()); // 可以通过实例访问 私有方法
book._year = '2025' // 私有属性不能通过实例修改
book.updateYear('1234')
book.updateYear(2015)
console.log(book.getYear());
这种封装的好处显而易见:
- 保护内部状态:外部无法直接修改关键数据
- 隐藏实现细节:用户只需关心接口,不必了解内部复杂逻辑
- 数据验证:确保输入符合预期,避免脏数据
当我们试着访问的时候,我们是访问不到我们的私有变量的,当然也不能修改,只能通过对象返回的对象从而进行对私有变量进行操作
二、节流函数:代码中的"自律达人"
继昨天学习了防抖函数分享了一下自己学习看法,今天再来分享一下今天所学的节流函数,这是昨天关于防抖的文章分享大家可以看看闭包与防抖:代码里的“拖延症患者”和“隐私守护者
那么什么是节流呢?简单来说:就是每隔单位时间内一定执行一次
在下述代码中,我们遇到了一位极度自律的节流函数,它严格控制着自己的行为频率:
function throttle(fn, delay) {
let last, deferTimer;
return function(...args) {
let that = this;
let now = +new Date();
if (last && now < last + delay) {
clearTimeout(deferTimer);
deferTimer = setTimeout(() => {
last = now;
fn.apply(that, args);
}, delay);
} else {
last = now;
fn.apply(that, args);
}
};
}
这位自律达人的行为准则:
- 首次见面热情响应(立即执行)
- 频繁接触保持距离(延迟执行)
- 最后请求必定回应(确保最终执行)
在输入框搜索建议中的应用:
let throttleAjax = throttle(ajax, 2000);
inputC.addEventListener('keyup', (e) => {
throttleAjax(e.target.value);
});
在这里我们假设了一个AJAX请求,模拟一下向后端请求数据,可以知道的,如果我们不设置节流,就会频繁触发请求,会对我们的后端服务器造成极大的性能消耗,如果用户一多,就有种像隐形的DDOM攻击
效果就像个贴心的助手:
- 你疯狂输入时,它不会频繁打扰服务器
- 你停止输入后,它才优雅地发送最后一次请求
- 避免像过度热情的推销员那样狂轰滥炸
我们来看看防抖和节流的对比
| 项目 | 节流(Throttling) | 防抖(Debouncing) |
|---|---|---|
| 中文名 | 节流 | 防抖 |
| 英文名 | Throttle | Debounce |
| 核心思想 | 在一段时间内只执行一次函数 | 在一段时间内没有再次触发才执行函数 |
| 举例 | 滚动事件中每隔一定时间检查位置 | 输入框输入时,停止输入后才触发搜索 |
| 执行时机 | 触发后,每隔固定时间执行一次 | 触发后,等待指定时间不再触发才执行 |
三、闭包:代码中的"记忆大师"
闭包就像拥有超强记忆力的朋友,它能记住创建时的环境。在下述代码中,我们看到三种解决this丢失问题的巧妙方法:
我们先来看看,下述代码
const obj = {
message: "hello from the object!",
init: function () {
const button = document.getElementById('myButton');
// const that = this
button.addEventListener('click', function () {
console.log(this.message);
})
}
}
obj.init();
执行结果为什么是undefined呢?
原因就在于我们在点击事件中,this是指向我们事件本身的,所以得到了undefined
1. "老派绅士" - that = this
init: function() {
const button = document.getElementById('myButton');
const that = this; // 记住当下的自己
button.addEventListener('click', function() {
console.log(that.message); // 还是原来的我
})
}
2. "时尚达人" - 箭头函数
sayHello: function() {
setTimeout(() => {
console.log(`${this.name} says hello!`); // 自带记忆功能
}, 1000)
}
3. "专业绑定师" - bind
sayHello: function() {
setTimeout(function() {
console.log(`${this.name} says hello!`);
}.bind(this), 1000) // 绑定专属身份
}
这三种方法本质上都是闭包的应用,它们记住了创建时的上下文,避免在回调中迷失自我。
这里还是我们老生常谈的this的问题,在事件和延迟函数,我们的this都会丢失,这时候我们上诉三种方法就防止了我们的this丢失问题
四、闭包应用全景图
闭包在JavaScript中扮演着多重角色:
| 应用场景 | 作用 | 类比 |
|---|---|---|
| 防抖(debounce) | 确保只执行最后一次操作 | 耐心等待说完最后一句话 |
| 节流(throttle) | 单位时间只执行一次 | 自律的会议发言者 |
| 绑定上下文 | 解决this丢失问题 | 身份认证系统 |
| 事件监听 | 维持回调函数上下文 | 记忆增强芯片 |
| 记忆函数 | 缓存计算结果提升性能 | 过目不忘的天才 |
| 柯里化 | 分步传递参数 | 分步烹饪指南 |
| 模块模式 | 创建私有作用域 | 秘密基地建设者 |
五、闭包的双面人生
闭包虽好,但也像爱吃内存的小怪兽:
function createHeavyObject() {
const hugeData = new Array(1000000).fill("🍔"); // 巨型汉堡数组
return {
eatBurger: function() {
return hugeData.pop(); // 每次只吃一个汉堡
}
};
}
const burgerMachine = createHeavyObject();
这里即使我们只需要eatBurger方法,整个hugeData数组依然会留在内存中,因为闭包记住了它的存在!
解决方案:
- 避免不必要的闭包
- 及时解除引用:
burgerMachine = null - 模块化设计,限制作用域
结语:闭包与封装的艺术
今天的学习旅程让我们看到:
- 私有变量如社恐患者,精心保护自己的内心世界
- 节流函数如自律达人,优雅控制行为频率
- 闭包如记忆大师,忠实地保存执行环境
- IIFE如秘密基地,创造安全的私有空间