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

news/2024/9/19 0:38:49/ 标签: javascript, 开发语言, this

 1. 什么是闭包?

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

简单来说:

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

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

1.1 基本概念

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

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

举个 🌰

javascript">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 中模拟了其他编程语言中的“私有”变量。

举个 🌰

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 引入之前),闭包可以用于模拟块级作用域。

举个 🌰

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

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

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

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

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

举个 🌰:非常典型

javascript">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 延迟执行和回调

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

举个 🌰

javascript">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,或通过适当的函数生命周期来避免。

举个 🌰

javascript">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)来创建每次迭代时的独立作用域。

举个 🌰

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

4. 隐藏的知识点

4.1 闭包和性能

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

举个 🌰

javascript">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() 或箭头函数。

举个 🌰

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

还有一点,后续补充一下 


http://www.ppmy.cn/news/1519886.html

相关文章

[Meachines] [Medium] SecNotes XSRF跨站请求伪造+SMB-Webshell上传+Linux子系统命令历史记录泄露权限提升

信息收集 IP AddressOpening Ports10.10.10.97TCP:80&#xff0c;445&#xff0c;8808 $ nmap -p- 10.10.10.97 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 80/tcp open http Microsoft IIS httpd 10.0 | http-title: Secure Notes - Login |…

SpringBoot中基于MongoDB的findAndModify原子操作实现分布式锁原理详解

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

网络是怎样连接的

网络是怎样连接的 HTTPDNS数据通信协议栈——TCP/IPRJ-45接口信号衰减噪声干扰双绞线MDI接口集线器交换机路由器接入网ADSL接入网FTTH接入网用户认证和配置下发DHCPPOPWeb服务器的部署地点防火墙 HTTP HTTP发展史&#xff1a; 1991 – HTTP/0.91996 – HTTP/1.01997 – HTTP/…

人工智能领域面试基础问题整理(二):什么是人工智能?

当你说你的专业是“人工智能”时&#xff0c;面试官问你&#xff1a;“假如我是一个不懂AI的人&#xff0c;你能和我说说&#xff0c;什么是AI吗&#xff1f;”你会怎么回答。 我们可以从以下几个方面入手&#xff1a; 1、人工智能的定义 人工智能&#xff08;Artificial Inte…

Android Studio gradle下载太慢了!怎么办?(已解决)

Android Studio&#xff01;你到底干了什么&#xff1f;&#xff01; 不能高速下载gradle&#xff0c;我等如何进行app编程&#xff1f;&#xff01; 很简单&#xff0c;我修改gradle地址不就是了。 找到gradle-wrapper.properties文件 修改其中distributionUrl的地址。 将 ht…

[Leetcode 51][Hard]-n皇后问题-回溯

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 这种可以算是组合问题的变种&#xff0c;在回溯函数中我们要保存当前已放置皇后的所有位置&#xff0c;同时递归调用时要进行寻找下一个皇后的放置位置。那么我们可以逐行遍历棋盘并作为递归调…

如何完美实现 Go 服务的平滑升级

Go 服务作为常驻进程,如何进行服务升级呢?你可能会觉得这还不简单,先将现有服务停止,再启动新的服务不就可以了。可是将现有服务停止时,如果它还在处理请求,那么这些请求该如何处理?另外,在现有服务已经退出但是新服务还没有启动期间,新的请求到达了又该如何处理? Go…

Logistic分类算法原理及Python实践

一、Logistic分类算法原理 Logistic分类算法&#xff0c;也称为逻辑回归&#xff08;Logistic Regression&#xff09;&#xff0c;是机器学习中的一种经典分类算法&#xff0c;主要用于解决二分类问题。其原理基于线性回归和逻辑函数&#xff08;Sigmoid函数&#xff09;的组…

3.4 数据传送指令

&#x1f393; 微机原理考点专栏&#xff08;通篇免费&#xff09; 欢迎来到我的微机原理专栏&#xff01;我将帮助你在最短时间内掌握微机原理的核心内容&#xff0c;为你的考研或期末考试保驾护航。 为什么选择我的视频&#xff1f; 全程考点讲解&#xff1a;每一节视频都…

使用JavaScript读取手机联系人列表:从理论到实践

更多内容前往个人网站&#xff1a;孔乙己大叔 在现代Web开发中&#xff0c;随着技术的不断进步&#xff0c;以前看似不可能的任务现在变得可行。例如&#xff0c;使用JavaScript读取手机联系人列表这一功能&#xff0c;在几年前几乎是不可想象的&#xff0c;但现在随着Web API的…

MyBatis之XML配置文件(一)

Mbatis是一个ORM框架&#xff0c;可以用XML配置文件或注解映射SQL语句&#xff0c;映射文件是MyBatis框架的核心&#xff0c;本文主要讲述XML 映射文件的结构和使用方法。 一、SQL映射文件 SQL映射文件就是mapperxml配置文件&#xff0c;主要实现SQL语句的配置和映射&#xf…

pdf.js如何支持base64的查看

1.pdf.js 作为一个查看在线阅读pdf的软件&#xff0c;常常被运用到前端开发中&#xff0c;但是如何让pdf支持base64的查看&#xff0c;这边就需要去进行修改一些代码了 这边我们就进行开发修改 首先去下载 https://mozilla.github.io/pdf.js/ 当然了&#xff0c;低版本的可以…

Kubernetes 上安装 Jenkins

安装 Helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash添加 Jenkins Helm 仓库 首先添加 Jenkins Helm 仓库 helm repo add jenkins https://charts.jenkins.io helm repo update安装 Jenkins 使用 Helm 安装 Jenkins 的最新版本&…

基于分布式计算的电商系统设计与实现【系统设计、模型预测、大屏设计、海量数据、Hadoop集群】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目展示项目介绍 目录摘要Abstract1 引言1.1 研究背景1.2 国内外研究现状1.3 研究目的1.4 研究意义 2 关键技术理论介绍2.1 Hadoop相关组件介绍2.2 分布式集群介绍2.3 Pyecharts介绍2.4 Fl…

Android音视频开发,需要学些什么?

如果你想学习 Android 音视频开发&#xff0c;以下是一些需要学习的内容&#xff1a; 一、基础知识 Java 或 Kotlin 编程语言&#xff1a;Android 开发主要使用这两种语言&#xff0c;确保你对其中一种有扎实的掌握&#xff0c;包括语法、面向对象编程概念、数据结构和算法等…

docker-compose 启动的harbor页面能登录,但是不能推送镜像

问题现象&#xff1a; docker-compose 安装的harbor&#xff0c;页面可以正常打开&#xff0c;但是不能推送镜像。 报错信息提示&#xff1a;connect: connection refused 故障原因&#xff1a; harbor.yml 中的external_url参数写错。这个是提供外部访问。页面请求地址和…

Java 面向对象编程的四个基本原则(封装、继承、多态和抽象),并给出一个简单的例子说明如何在 Java 中应用这些原则?

面向对象编程&#xff08;OOP&#xff09;是一种编程范式&#xff0c;它使用“对象”来设计软件。在 Java 中&#xff0c;面向对象编程的四个基本原则是封装、继承、多态和抽象。每个原则都有其特定的目标&#xff0c;帮助开发者构建更加模块化、可维护和可扩展的代码。 封装 …

ImmersiveTranslate:一键中英对照,Google Chrome上不可或缺的翻译利器

ImmersiveTranslate&#xff1a;一键中英对照&#xff0c;Google Chrome上不可或缺的翻译利器 基本介绍 ImmersiveTranslate 是一款为Google Chrome用户设计的翻译插件&#xff0c;旨在帮助用户轻松实现中英对照翻译。这款插件不仅适合普通用户&#xff0c;同时也为开发者提供…

CSS3动画——飞行的小精灵

CSS3动画——飞行的小精灵 今天的这段代码通过多层结构、渐变色、圆角、多种动画效果以及细节处理&#xff0c;成功地创造了一个充满活力和趣味性的飞行小精灵动画效果。 效果如下&#xff1a; 飞行的小精灵 源代码如下&#xff1a; <!DOCTYPE html> <html lang&quo…

呵,老板不过如此,SQL还是得看我

2018年7月&#xff0c;大三暑假进行时&#xff0c;时间过得飞快&#xff0c;我到这边实习都已经一个月了。 我在没工作之前&#xff0c;我老是觉得生产项目的代码跟我平时自学练的会有很大的区别。 以为生产项目代码啥的都会规范很多&#xff0c;比如在接口上会做很多安全性的…