当前位置: 首页 > news >正文

揭秘面试官常见问题 —— JavaScript 闭包

 1. 什么是闭包?

闭包是 JavaScript 中的一种特性,它允许一个函数在定义的环境之外仍然能够访问和操作定义时的作用域中的变量。换句话说,闭包是指函数可以“记住”并访问它被创建时所处的词法作用域。

简单来说:

- 当一个函数被嵌套在另一个函数内部时,内部函数可以访问外部函数的变量。

- 即使外部函数已经执行完毕,内部函数仍然可以通过闭包访问这些变量。

1.1 基本概念

1、作用域链:函数内部可以访问外部作用域的变量,原因在于作用域链的存在。作用域链决定了函数在何处寻找变量,通常由当前函数作用域和外层作用域构成。

2、函数嵌套:当一个函数在另一个函数内部定义时,内部函数可以访问外部函数的变量。

举个 🌰

function outerFunction() {let outerVariable = 'I am from the outer scope!';function innerFunction() {console.log(outerVariable);}return innerFunction;
}const closureFunction = outerFunction();
closureFunction(); // 输出: "I am from the outer scope!"

例子中, innerFunction 是一个闭包,它获取 outerFunction 中的变量 outerVariable,即使 outerFunction 已经执行完毕,但 innerFunction 依然可以访问 outerVariable。

2. 常见使用场景

2.1 数据封装和私有化

闭包可以用来创建私有变量,这些变量只能通过特点的接口进行访问和修改,这在 JavaScript 中模拟了其他编程语言中的“私有”变量。

举个 🌰

function createCounter() {let count = 0;return {increment() {count++;return count;},decrement() {count--;return count;},getCount() {return count;}};
}const counter = createCounter();
console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
console.log(counter.getCount());  // 输出: 2
console.log(counter.decrement()); // 输出: 1
2.2 块级作用域

在 ES6 之前,JavaScript 没有块级作用域(let 和 const 引入之前),闭包可以用于模拟块级作用域。

举个 🌰

for (var i = 0; i < 3; i++) {(function(i) {setTimeout(() => {console.log(i);}, 1000);})(i);
}

说到这个例子,按道理而已,是每一秒输出一个数字,但仔细看看后发现,因为 setTimeout 的回调都是一秒后执行,所以三个 console.log 语句在同一时刻执行,导致它们几乎同时输出。

不符合我们的要求,可以更改一下代码:

for (var i = 0; i < 3; i++) {(function (i) {setTimeout(() => {console.log(i);}, i * 1000); // 每个迭代延迟时间增加 i * 1000 毫秒})(i);
}
2.3 函数柯里化

闭包是函数柯里化的基础,柯里化是一种将函数拆分为多个函数的技巧,每个函数只接受一部分参数。 

举个 🌰:非常典型

function multiply(a) {return function(b) {return a * b;};
}const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(5)); // 输出: 10const multiplyByThree = multiply(3);
console.log(multiplyByThree(5)); // 输出: 15
2.4 延迟执行和回调

闭包常用于回调函数中,尤其是在处理异步操作时。 

举个 🌰

function fetchData(url) {setTimeout(() => {console.log('Fetching data from ' + url);}, 1000);
}function loadUserData() {const url = 'https://api.example.com/user';fetchData(url);
}loadUserData(); // 输出: "Fetching data from https://api.example.com/user"

在 fetchData 函数中,setTimeout 的回调函数在 1 秒 后执行。当这个回调函数被定义时,它捕获了 fetchData 函数的参数 url,因此 url 变量形成了闭包。

3. 潜在问题和解决方法

3.1 内存泄漏

闭包可能导致内存泄漏,因为闭包会使外部函数的变量一直保存在内存中,导致无法被垃圾回收。

解决方法:确保不再需要使用闭包时,手动将闭包变量置为 null,或通过适当的函数生命周期来避免。

举个 🌰

function createClosure() {let largeData = new Array(10000).fill('*');return function() {console.log('Using closure', largeData);};
}
let closure = createClosure();
// 使用完成后手动解除引用
closure = null; 
3.2 意外的变量共享

在循环中使用闭包时,所有的闭包都可能引用同一个变量,导致输出不符合预期。

解决方法:使用 let 代替 var,或者使用立即调用的函数表达式(IIFE)来创建每次迭代时的独立作用域。

举个 🌰

for (let i = 0; i < 3; i++) {setTimeout(() => {console.log(i);}, 1000);
}
// 输出:0, 1, 2

4. 隐藏的知识点

4.1 闭包和性能

闭包会保留整个作用域链中的所有变量,而不仅仅是闭包中使用的变量。这可能导致占用更多的内存。

举个 🌰

function outerFunction() {let a = 1;let b = 2;let c = 3;return function innerFunction() {console.log(a); // 虽然只使用了 a,但 b 和 c 也会被保留};
}
4.2  闭包和垃圾回收

由于闭包引用的变量不会被立即回收,可能导致内存无法释放。

4.3 闭包和 this 关键字

闭包不会绑定 this,它只会捕获外部函数的作用域。如果需要访问当前对象的 this,可以使用 bind()、call()、apply() 或箭头函数。

举个 🌰

function Counter() {this.count = 0;setTimeout(() => {this.count++;console.log(this.count);}, 1000);
}const counter = new Counter(); // 输出:1

还有一点,后续补充一下 


http://www.mrgr.cn/news/18326.html

相关文章:

  • 力扣9.2
  • ClickHouse
  • Matlab三维图的坐标轴标签 自动平行坐标/自动旋转
  • Mybatis【分页插件,缓存,一级缓存,二级缓存,常见缓存面试题】
  • HCIE认证要学多久?3个方面决定HCIE学习时长
  • 24数学建模国赛及提供助力(12——存贮论)!!!!
  • 算法训练营|图论第11天 Floyd算法 A*算法
  • jetson orin nx安装todesk
  • MySQL中的分组统计
  • 特殊字符合集(包括各种emoji表情、windows ASCII字符、自定义字母图案等)
  • 项目管理干系人管理
  • 【Transformer】基本概述
  • 《父母爱情》:找结婚对象,别只看有房有车有颜,这4个特点更重要!
  • NVIDIA H200与AMD MI300X:前者高利润率是否合理?
  • .NET周刊【9月第1期 2024-09-01】
  • c++ 标准模板库 STL
  • 一文彻底搞懂Spring, Spring MVC, Spring Boot 和 Spring Cloud 区别
  • Python数据抓取与质量校验:以杭州市公交线路为例
  • 【全能型AI“草莓”来袭】探索未来AI市场的多元化与边界
  • 简单的棒棒图绘制教程