成功的秘诀在于每天都有所进步,即使只是一点点也可以。- 约翰·C·迪维尔比
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
大家好,我是siuuuuuuuuu。
在如今的互联网大环境下,每个人都岌岌可危。本着互联网行业天然的高波动性, 有可能你上午还在工位摸鱼,下午HR已经给你单独开小灶,通知你提前毕业了。所以在当前的互联网环境,每天刷八股文变得很有必要, 因为你永远都会随时被毕业.
该圣经系列就是为各位广大的前端从业者准备,该圣经如同武林中绝世武学. 读完此文,融会贯通后, 拳打面试官,脚踢hr, offer拿到手软,不在话下。
废话不多说,接下来进入正题。
闭包
在 JavaScript 中,闭包是指函数可以访问并操作其外部函数作用域内的变量,即使外部函数已经执行完毕并返回了,闭包仍然能够保留对那些变量的引用。具体来说,闭包会将外部函数作用域内的变量通过引用进行捕获,形成一个私有化的上下文环境。
这种特性可以使 JavaScript 函数拥有类似于面向对象的编程方式中“对象”的概念,从而使代码更加灵活和可重用。
下面是一个例子,展示了如何创建并使用一个闭包:
function outerFunc() {
var num = 0; // 外部函数作用域内的变量
return function innerFunc() {
num++; // 访问外部函数作用域内的变量并进行操作
console.log(num);
}
}
var inner = outerFunc(); // 创建 inner 函数,它包含了对 num 的引用
inner(); // 输出 1
inner(); // 输出 2
在上述例子中,outerFunc 返回了一个内部函数 innerFunc。当我们调用 outerFunc 时,它会返回一个函数 innerFunc,并将其赋值给变量 inner。此时,inner 函数被创建出来,并捕获了 outerFunc 函数作用域内的变量 num 的引用,这个变量 num 在 innerFunc 函数中依然是可用的。
每次调用 inner 函数时,都会访问并操作 num 变量的值,并输出到控制台。由于 inner 函数仍然保持着对 num 变量的引用,因此在第二次调用 inner 函数时,num 的值依然是上一次的结果加 1。
因此,在这个例子中,inner 函数就是一个闭包,它可以访问并操作 outerFunc 函数作用域内的变量,即 num 变量。
闭包的好处
- 可以访问外部函数作用域内的变量,因此可以避免使用全局变量,并且保护了变量的作用域;
- 可以使代码更加灵活和可重用,可以将一些逻辑封装在函数内部,形成一个“私有”的作用域,避免变量名冲突;
- 可以创建一些动态的函数,即根据不同的输入参数生成不同的函数。
闭包的坏处
- 如果不恰当地使用闭包,会导致内存泄漏。当函数执行完毕后,其内部变量仍然被引用着,因此需要注意及时释放内存;
- 使用闭包可能会降低代码的可读性,因为闭包中的变量不仅可以是函数内部定义的,还可能是来自外部环境的变量,这会带来一定的困扰
闭包具体使用场景
模块化
实现模块化:利用闭包封装私有变量和方法,实现私有作用域,避免变量名重复、函数冲突等问题。这是 JavaScript 中最常见的应用场景之一;
var module = (function() {
var privateVar = "I am private.";
function privateFunc() {
console.log("This is a private function.");
}
return {
publicVar: "I am public.",
publicFunc: function() {
console.log("This is a public function.");
}
};
})();
console.log(module.publicVar); // 输出 "I am public."
module.publicFunc(); // 输出 "This is a public function."
console.log(module.privateVar); // undefined
module.privateFunc(); // 抛出 TypeError 异常
在上述代码中,module 变量被赋值为一个立即执行函数的返回值,这个函数返回一个包含公有变量和方法的对象。私有变量和函数都定义在立即执行函数内部,并且不可外部访问,从而实现了模块化。
柯里化
实现柯里化:柯里化是指将一个接受多个参数的函数转换为一系列只接受一个参数的函数的过程。利用闭包,可以很容易地实现柯里化;
function add(x, y) {
return x + y;
}
function currying(func) {
var args = [];
return function result() {
if (arguments.length === 0) {
return func.apply(this, args);
} else {
Array.prototype.push.apply(args, arguments);
return result;
}
};
}
var addCurry = currying(add);
console.log(addCurry(1)(2)()); // 输出 3
console.log(addCurry(1, 2)()); // 输出 3
在上述代码中,currying 函数接收一个函数作为参数,并返回一个柯里化后的函数。result 函数通过自身递归调用来收集所有参数,当参数长度为 0 时,返回原始函数的结果。
实现缓存
实现缓存:在函数中保存一些计算结果,当下一次输入相同参数时,直接返回之前已经保存的结果,从而提高程序的执行效率;
function cache(func) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (!(key in cache)) {
cache[key] = func.apply(this, arguments);
}
return cache[key];
};
}
var factorial = cache(function(n) {
if (n === 1) {
return 1;
} else {
return n * factorial(n - 1);
}
});
console.log(factorial(5)); // 输出 120
console.log(factorial(5)); // 输出 120,此时直接从缓存中获取结果
在上述代码中,cache 函数接收一个函数作为参数,并返回一个使用闭包缓存结果的函数。当参数列表没有在缓存中出现过时,执行原始函数并将结果存入缓存中,否则直接从缓存中获取结果。这个例子中将阶乘函数进行了缓存,可以避免重复计算。
实现事件监听器
实现事件监听器:利用闭包来创建事件监听器,避免全局变量的污染,同时也可以随时删除已经添加的事件监听器。
function EventListener() {
var eventList = {};
return {
addListener: function(eventName, func) {
if (!eventList[eventName]) {
eventList[eventName] = [];
}
eventList[eventName].push(func);
},
removeListener: function(eventName, func) {
if (eventList[eventName]) {
for (var i = 0, len = eventList[eventName].length; i < len; i++) {
if (eventList[eventName][i] === func) {
eventList[eventName].splice(i, 1);
return true;
}
}
}
return false;
},
trigger: function(eventName) {
if (eventList[eventName]) {
for (var i = 0, len = eventList[eventName].length; i < len; i++) {
eventList[eventName][i].call(this);
}
}
}
};
}
var eventListener = new EventListener();
function foo() {
console.log("foo is called.");
}
function bar() {
console.log("bar is called.");
}
eventListener.addListener("click", foo);
eventListener.addListener("click", bar);
eventListener.trigger("click"); // 输出 "foo is called." 和 "bar is called."
eventListener.removeListener("click", foo);
eventListener.trigger("click"); // 只输出 "bar is called."
在上述代码中,EventListener 函数返回一个包含 addListener、removeListener 和 trigger 方法的对象。addListener 方法用于添加事件监听器,removeListener 方法用于删除事件监听器,trigger 方法用于触发事件。这个例子中使用了闭包来保存事件列表。