前言
曾有一段时间,我也和许多人一样,每当时间推移,或是面试在即,便会上网搜索几篇关于闭包的面试题来背诵,试图以此来应对即将到来的挑战。
然而,随着时间的流逝,我渐渐意识到这种方法其实只是在浪费我的宝贵时间,而没有真正让我掌握闭包的精髓。于是,我决心深入探索闭包的奥秘,打破砂锅问到底,直到我能彻底搞懂它为止。
经过大量的资料搜索和深入研究,我终于可以说我已经彻底搞明白了闭包。现在,我想通过这篇文章,将我所学到的心得体会分享给大家,希望能帮助更多的人走出闭包的困惑,真正掌握这个重要的编程概念。
怎么学闭包?
在开始之前,允许小编再啰嗦一章。
小编以前学习也是每次都去网络搜索不一样的博主前辈们总结的闭包知识,但是由于每个博主擅长的技能都不一样,很难分辨哪篇文章讲的好,所以导致小编对于知识的理解参次不齐,全是碎片化的。
所以小编想在这边文章中,带大家用不同的视角去理解闭包,把这个闭包彻底消化成自己的知识,可以自己总结出一套概念,而不是再去搜索背诵别人总结后的概念。
引用《道德经》中的一句话:“无名万物之始,有名万物之母。”它告诉我们,无形的东西是一切有形之物的母亲,是宇宙的根本。借用这个理念,我们可以将其映射到编程的世界——闭包,它是JavaScript中的一个无形概念,却孕育了无数的功能和可能性。
废话完毕!GO!
什么是闭包?
想象一下:
你在一家餐厅吃饭,服务员给你送来了一本菜单。尽管你离开了餐厅,那本菜单仍然记录着餐厅里的所有食物选项,让你能够回忆起那些美味的选择。
同样地,闭包就像那本菜单,它记住了函数被创建时周围的作用域,即使那个作用域已经不存在了。
- 其实:闭包就是一个函数。
- 而且:闭包是一个“记忆功能”很强的函数。
- 有多强:它不仅能够记住自己的代码,还能够记住在哪里被创建,以及当时创建时周围环境的情况,包括那个时候的变量等。
- 还不止:即使这个函数后来跑到别的地方去执行,它依然能够像在原来的地方一样“访问”那些变量。
闭包的优缺点
总结:
任何东西都有优缺点,闭包也不例外。
闭包在JavaScript中是一把双刃剑,它既提供了强大的功能,也带来了一定的挑战。
正确使用闭包可以让我们的代码更加优雅和高效,但如果不注意,可能会让我们的代码变得难以维护。
因此,我们需要像魔术师一样,既要善于利用工具,也要时刻警惕其潜在的风险。
列举:
闭包的优点:
- 数据隐藏:闭包可以帮助我们隐藏数据(这就像是魔术师的助手在幕后协助完成表演,观众虽然看不见,但知道她的存在对整个表演至关重要)
- 持久化变量:闭包允许我们在函数外部访问和操作函数内部的变量,即使这个函数已经执行完毕(这就像是魔术师将一些道具暂时“隐藏”起来,等需要的时候再拿出,给观众带来惊喜)
- 减少全局变量污染:由于闭包可以封装变量,所以可以有效地减少全局变量的使用,避免了全局命名空间的污染(这就像是魔术师在自己的舞台上表演,不会影响到其他魔术师的表演空间)
闭包的缺点:
- 性能问题:因为闭包会保存函数内部的作用域链,所以可能会导致内存占用较多,尤其是在使用大量闭包的情况下,可能会对性能产生负面影响(这就像是魔术师使用了太多的道具,虽然增加了表演的神秘感,但也可能让一些观众感到混乱)
- 代码可读性下降:闭包的使用有时会使得代码结构变得复杂,特别是对于初学者来说,可能不容易理解闭包的工作原理(这就像是魔术师的表演过于复杂,普通观众可能无法完全领会其中的奥秘)
- 潜在的内存泄漏:如果不当使用闭包,可能会导致一些变量无法被垃圾回收机制回收,从而造成内存泄漏(这就像是魔术师在表演结束后忘记清理道具,长此以往可能会影响到下一次表演)
闭包的使用场景
闭包的使用场景主要包括但不限于:
- 延迟计算
- 私有变量
- 柯里化
延迟计算
设想你在图书馆借了一本书,但你决定等到回家后再阅读。你把书带回家,这就是一种延迟——你将阅读的行为推迟到了更合适的时候。
在JavaScript中,闭包可以用来创建一个函数,这个函数会在未来的某个时刻执行,而不是在定义它的时候就执行。这就像是你决定把书留到家再读一样。
function createReaderAction(bookTitle) {
return function() {
console.log(`Reading "${bookTitle}" now.`);
};
}
const readBook = createReaderAction("JavaScript Cookbook");
// 相当于你把书带回家,准备稍后阅读
readBook(); // 输出: Reading "JavaScript Cookbook" now.
在这个例子中,createReaderAction
是一个工厂函数,它返回了一个闭包readBook
,这个闭包保存了传入的书籍标题。只有当你调用readBook()
的时候,它才会打印出消息,就像是你回到家开始阅读一样。
延迟计算的具体应用场景包括但不限于:防抖和节流(可以参考我以前的笔记)
私有变量
在你的日记里,你记录了许多私人想法,这是私有的,别人无法直接查看。
在JavaScript中,闭包可以用来创建私有变量,这些变量只能通过特定的方法来访问,就像你的日记只对你开放一样。
function createAccount(balance) {
return {
deposit: function(amount) {
balance += amount;
},
withdraw: function(amount) {
if (balance >= amount) {
balance -= amount;
return true;
} else {
return false;
}
};
}
const myAccount = createAccount(100);
myAccount.deposit(50); // 存钱
console.log(myAccount.balance); // 报错,因为balance不是公开的
在这里,balance
是私有的,只能通过deposit
和withdraw
方法来访问和修改。这保证了账户的安全性,正如你的日记内容不会轻易被人窥探。
柯里化
假设你有一个食谱,你想制作多种口味的蛋糕,但是基础配方是一样的。你会一次又一次地使用同一个配方,只是改变其中的一些配料。
在JavaScript中,柯里化是一种技术,它允许你将一个函数转化为接受几个参数的函数,然后返回一个新的函数,这个新函数等待接收剩余的参数。
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...newArgs) {
return curried.apply(this, args.concat(newArgs));
};
}
};
}
function add(a, b) {
return a + b;
}
const addFive = curry(add, 5);
const addTen = curry(add, 10);
console.log(addFive(3)); // 使用curry预设第一个参数为5,输出8
console.log(addTen(3)); // 使用curry预设第一个参数为10,输出13
在这个例子中,curry
函数返回一个新的函数,这个新函数记住前面传递的参数,直到它接收到足够的参数来执行原始的add
函数。
结语
通过理解闭包的工作原理,你可以创造出更加模块化和功能强大的代码,就像是在你的写作中加入了一种新的笔触,使得你的作品层次丰富,意味深长。不要仅仅依赖记忆中的概念,而是要去实践,去体验闭包的力量,并且形成你自己对它的理解。这样,无论何时何地,你都能够运用自如,写出既优雅又高效的代码。