【前端面试系列】JavaScript闭包

1. 闭包的本质

闭包是JavaScript中的一个核心概念,它是一个函数和其词法环境的组合体。这个词法环境由函数声明时所在的作用域中的所有局部变量组成。简单来说,闭包允许一个内部函数访问其外部函数的作用域。

1.1 闭包vs普通函数

特性闭包普通函数
访问外部变量可以访问外部函数作用域只能访问全局变量和自身局部变量
生命周期可以延长局部变量的生命周期函数执行完毕后,局部变量被销毁
内存占用较高,因为保留了外部作用域较低
封装性可以创建私有变量和方法无法直接创建私有变量和方法

2. 高级应用场景

2.1 函数式编程

闭包在函数式编程中扮演着重要角色,特别是在实现高阶函数、柯里化和组合等技术时。

function multiplyBy(factor) {return function(number) {return number * factor;};
}const double = multiplyBy(2);
const triple = multiplyBy(3);console.log(double(5));  // 输出: 10
console.log(triple(5));  // 输出: 15

2.2 异步编程模式

闭包在处理异步操作时非常有用,特别是在回调函数和Promise中。

function fetchUserData(userId) {return fetch(`https://api.example.com/users/${userId}`).then(response => response.json()).then(user => {return fetch(`https://api.example.com/posts?userId=${user.id}`).then(response => response.json()).then(posts => {return { user, posts };  // 闭包捕获了外部的user变量});});
}fetchUserData(1).then(({ user, posts }) => {console.log(user, posts);
});

2.3 模块模式与私有状态

闭包可以用来创建私有变量和方法,实现信息隐藏和封装。

const Counter = (function() {let count = 0;  // 私有变量function changeBy(val) {  // 私有方法count += val;}return {increment: function() {changeBy(1);},decrement: function() {changeBy(-1);},value: function() {return count;}};
})();console.log(Counter.value());  // 0
Counter.increment();
Counter.increment();
console.log(Counter.value());  // 2
console.log(Counter.count);  // undefined,无法直接访问私有变量

3. 性能考虑与优化

3.1 内存泄漏风险

闭包可能导致意外的内存泄漏,特别是在处理DOM元素时:

function attachHandler(element) {let clickCount = 0;element.addEventListener('click', function() {console.log(`Clicked ${++clickCount} times`);});
}// 使用
const button = document.createElement('button');
attachHandler(button);
// 之后即使button元素被移除,闭包仍然引用着clickCount,可能导致内存泄漏

3.2 优化策略

  1. 及时清理:在不需要时,手动解除对闭包的引用。
  2. 避免过度使用:不是所有场景都需要闭包,评估是否有更简单的替代方案。
  3. 使用WeakMap:对于需要关联数据到对象但又不想阻止垃圾回收的场景,考虑使用WeakMap。
const cache = new WeakMap();function computeExpensiveResult(obj) {if (cache.has(obj)) {return cache.get(obj);}const result = /* 复杂计算 */;cache.set(obj, result);return result;
}

4. ES6中的闭包

4.1 块级作用域与let/const

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

4.2 箭头函数与词法this

function DelayedGreeter(name) {this.name = name;
}DelayedGreeter.prototype.greet = function() {setTimeout(() => {console.log(`Hello, ${this.name}`);}, 1000);
};const greeter = new DelayedGreeter("World");
greeter.greet();  // 输出: Hello, World

5. 闭包的优缺点

优点

  1. 数据隐藏和封装:可以创建私有变量和方法,实现信息隐藏。
  2. 状态保持:能够在函数之间保持状态,实现数据持久化。
  3. 回调和高阶函数:在异步编程和函数式编程中非常有用。
  4. 模块化开发:可以用来创建模块和命名空间,避免全局变量污染。

缺点

  1. 内存占用:闭包会保留其外部作用域的引用,可能导致更高的内存使用。
  2. 性能影响:由于额外的作用域链查找,可能会对性能造成轻微影响。
  3. 内存泄漏风险:如果不正确管理,可能导致意外的内存泄漏。
  4. 复杂性:过度使用闭包可能使代码难以理解和维护。

6. 面试题解析

问题1:什么是闭包?如何在JavaScript中创建闭包?

答:闭包是指一个函数及其词法环境的组合。它允许内部函数访问其外部函数的作用域。在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

示例:

function outerFunction(x) {let y = 10;function innerFunction() {console.log(x + y);}return innerFunction;
}const closure = outerFunction(5);
closure(); // 输出15

问题2:请举例说明闭包在实际开发中的应用场景

答:闭包在实际开发中有多种应用场景,以下是几个常见的例子:

模块模式
const counter = (function() {let count = 0;return {increment: function() { count++; },decrement: function() { count--; },getCount: function() { return count; }};
})();counter.increment();
console.log(counter.getCount()); // 1
函数工厂
function multiplyBy(factor) {return function(number) {return number * factor;};
}const double = multiplyBy(2);
const triple = multiplyBy(3);console.log(double(5)); // 10
console.log(triple(5)); // 15
异步操作中的数据保持
function fetchData(url) {return function(callback) {fetch(url).then(response => response.json()).then(data => callback(data));};
}const getUserData = fetchData('https://api.example.com/user');
getUserData(data => console.log(data));

问题3:在使用闭包时,有哪些常见的陷阱?如何避免这些问题?

答:使用闭包时的常见陷阱及其解决方案包括:

循环中的闭包问题

问题:

for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, 1000);
}
// 输出五次5,而不是0,1,2,3,4

解决方案:

for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, 1000);
}
// 正确输出0,1,2,3,4
this指向问题

问题:

const obj = {name: 'MyObject',greet: function() {setTimeout(function() {console.log('Hello, ' + this.name);}, 1000);}
};
obj.greet(); // 输出 "Hello, undefined"

解决方案:

const obj = {name: 'MyObject',greet: function() {setTimeout(() => {console.log('Hello, ' + this.name);}, 1000);}
};
obj.greet(); // 输出 "Hello, MyObject"
内存泄漏

不正确的DOM引用可能导致内存泄漏。解决方案是确保在不需要时解除对DOM元素的引用,或使用弱引用(WeakMap/WeakSet)。

问题4:如何优化使用闭包的代码以提高性能?

答:优化使用闭包的代码可以从以下几个方面入手:

  1. 最小化闭包范围:只捕获必要的变量,减少内存占用。
  2. 及时清理:当闭包不再需要时,将其设置为null以便垃圾回收。
  3. 避免在循环中创建函数:如果可能,将函数创建移到循环外部。
  4. 使用立即执行函数表达式(IIFE):来限制闭包的作用范围。
  5. 权衡使用:评估是否真的需要闭包,有时普通函数或其他模式可能更合适。

示例优化:

// 优化前
function createFunctions() {var result = [];for (var i = 0; i < 1000; i++) {result.push(function() { return i; });}return result;
}// 优化后
function createFunctions() {var result = [];for (var i = 0; i < 1000; i++) {result.push((function(num) {return function() { return num; };})(i));}return result;
}

通过这些问题和答案,我们可以更好地理解闭包的概念、应用场景、潜在问题及其解决方案。在实际开发和面试中,掌握这些知识点将有助于更好地运用闭包,同时避免常见的陷阱。

一、比较操作符概述

JavaScript中的比较操作符主要分为两类:相等操作符(==)和全等操作符(===)。理解这两者的区别和使用场景对于编写健壮的代码至关重要。

二、相等操作符(==)深入解析

相等操作符(==)用于比较两个操作数是否相等,且在比较前会进行类型转换。

2.1 类型转换规则

  1. 布尔值转换为数值

    console.log(true == 1); // true
    console.log(false == 0); // true
    
  2. 字符串转换为数值

    console.log("55" == 55); // true
    console.log("" == 0); // true
    
  3. 对象转换为原始值

    let obj = { valueOf: function() { return 1; } };
    console.log(obj == 1); // true
    
  4. nullundefined

    console.log(null == undefined); // true
    
  5. NaN 比较

    console.log(NaN == NaN); // false
    
  6. 对象引用比较

    let obj1 = { name: "John" };
    let obj2 = { name: "John" };
    console.log(obj1 == obj2); // false
    

2.2 相等操作符的特殊情况

console.log('' == '0');         // false
console.log(0 == '');           // true
console.log(0 == '0');          // true
console.log(false == 'false');  // false
console.log(false == '0');      // true
console.log(false == undefined);// false
console.log(false == null);     // false
console.log(null == undefined); // true
console.log(' \t\r\n' == 0);    // true

三、全等操作符(===)详解

全等操作符(===)用于严格比较两个操作数,仅当类型和值都相同时才返回 true

3.1 全等比较示例

console.log("55" === 55);   // false
console.log(55 === 55);     // true
console.log(null === null); // true
console.log(undefined === undefined); // true

四、相等与全等操作符的对比

4.1 主要区别

  1. 类型转换== 在比较前进行类型转换,=== 不会。

  2. nullundefined 的处理

    console.log(null == undefined);  // true
    console.log(null === undefined); // false
    

4.2 使用建议

除非在比较对象属性为nullundefined时,一般建议使用全等操作符(===),以避免意外的类型转换导致的错误。

const obj = {};
if (obj.x == null) {console.log("属性 x 不存在或为 null");
}
// 等同于但更冗长的写法
if (obj.x === null || obj.x === undefined) {console.log("属性 x 不存在或为 null");
}

五、面试题精选

  1. Q: 请解释 ===== 的区别,并给出一个例子说明何时使用 == 可能更合适。

    A: == 在比较前进行类型转换,而 === 不会。使用 == 比较 nullundefined 时可能更合适,例如:

    function isNullOrUndefined(value) {return value == null;
    }
    

    这个函数可以同时检查 nullundefined,而使用 === 则需要两次比较。

  2. Q: 下面的比较结果是什么,为什么?

    console.log([] == ![]);
    

    A: 结果是 true。解释如下:

    1. ![] 首先被计算,结果为 false
    2. 比较变成了 [] == false
    3. false 被转换为数字 0
    4. [] 被转换为原始值,即空字符串 ''
    5. '' 被转换为数字 0
    6. 最终比较变成 0 == 0,结果为 true
  3. Q: 如何安全地比较两个可能为 NaN 的值?

    A: 可以使用 Object.is() 方法:

    function safeCompare(a, b) {return Object.is(a, b);
    }
    console.log(safeCompare(NaN, NaN)); // true
    
  4. Q: 在什么情况下 a !== a 会返回 true

    A: 当 aNaN 时。NaN 是JavaScript中唯一不等于自身的值。

  5. Q: 如何在不使用 === 操作符的情况下实现严格相等比较?

    A: 可以使用 Object.is() 方法:

    function strictEqual(a, b) {return Object.is(a, b);
    }
    

    注意,Object.is()=== 略有不同,它认为 NaN 等于 NaN,且 -0 不等于 +0

六、实践建议

  1. 默认使用 === 进行比较,避免意外的类型转换。
  2. 在检查值是否为 nullundefined 时,可以考虑使用 ==
  3. 对于可能涉及 NaN-0+0 比较的场景,考虑使用 Object.is()
  4. 在进行复杂比较时,优先考虑将值转换为相同类型后再使用 === 比较。
  5. 在代码审查中,特别关注 == 的使用,确保其使用是有意为之且合理的。

结语

感谢阅读本文!如果您觉得这篇文章对您有帮助,欢迎:

关注我的技术博客:徐白知识星球

本文是前端系列文章的一部分,更多精彩内容:

  • JavaScript闭包
  • 大文件上传以及分片上传与断点续传
  • 掌握可视区域判断

让我们一起在技术的道路上不断进步!

专注前端技术,定期分享高质量的技术文章和实战经验。欢迎交流与讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/14243.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Collections 工具类

在 Java 编程中&#xff0c;集合&#xff08;Collections&#xff09;是处理数据的核心工具之一。为了简化集合操作并提高代码的可读性和可维护性&#xff0c;JDK 提供了一个强大的工具类&#xff1a;java.util.Collections。这个类包含了一系列静态方法&#xff0c;用于对集合…

机器学习引领流体动力学新纪元:CFD、Fluent与OpenFOAM的深度融合

在科技日新月异的今天&#xff0c;机器学习正以前所未有的力量重塑着众多学科领域&#xff0c;其中&#xff0c;流体动力学便是受益匪浅的典范。作为计算流体力学&#xff08;CFD&#xff09;领域的两大巨头&#xff0c;Fluent与OpenFOAM正携手机器学习技术&#xff0c;共同开启…

django入门【05】模型介绍(二)——字段选项

文章目录 1、null 和 blank示例说明⭐ null 和 blank 结合使用的几种情况总结&#xff1a; 2、choices**choices 在 Django 中有以下几种形式&#xff1a;**&#xff08;1&#xff09; **简单的列表或元组形式**&#xff08;2&#xff09; **字典映射形式**&#xff08;3&#…

PL/SQL执行.sql文件

1.编写.sql文件&#xff0c;创建update.sql文件&#xff0c;文件如下&#xff1a; set feedback offset define off--更新表中所有人的年龄update a set age18;prompt Done. 2.打开plsql选择命令窗口&#xff0c;即选择File->New->Command Window&#xff1b; 打开后的…

论文5—《基于改进YOLOv5s的轻量化金银花识别方法》文献阅读分析报告

论文报告&#xff1a;基于改进YOLOv5s的轻量化金银花识别方法 论文报告文档 基于改进YOLOv5s的轻量化金银花识别方法 论文报告文档摘要国内外研究现状国内研究现状国外研究现状 研究目的研究问题使用的研究方法试验研究结果文献结论创新点和对现有研究的贡献1. 目标检测技术2. …

【数据结构】ArrayList与LinkedList详解!!!——Java

目录 一&#x1f31e;、List 1&#x1f345;.什么是List&#xff1f; 2&#x1f345;.List中的常用方法 二&#x1f31e;、ArrayList 1&#x1f34d;.什么是ArrayList? 2&#x1f34d;.ArrayList的实例化 3&#x1f34d;.ArrayList的使用 4&#x1f34d;.ArrayList的遍…

modbus协议 Mthings模拟器使用

进制转换 HEX 16进制 (0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F表示0-15) dec 10进制 n(16进制) -> 10 abcd.efg(n) d*n^0 c*n^1 b*n^2 a*n^3 e*n^-1 f*n^-2 g*n^-3&#xff08;10&#xff09; 10 -> n(16进制) Modbus基础概念 高位为NUM_H&…

微信版产品目录如何制作?

微信作为我国最流行的社交媒体平台&#xff0c;拥有庞大的用户群体。许多企业都希望通过微信来推广自己的产品&#xff0c;提高品牌知名度。制作一份精美、实用的微信版产品目录&#xff0c;是企业微信营销的重要手段。微信版产品目录的制作方法&#xff0c;帮助您轻松入门。 ​…

消息推送之SSE

一、简介 市面上很多系统都有 以上三种的消息提醒。但大多分为2类&#xff0c;一类移动端&#xff0c;一类web端比&#xff0c;通常在服务端会有若干张消息推送表&#xff0c;用来记录用户触发不同事件所推送不同类型的消息&#xff0c;前端主动查询&#xff08;拉&#x…

react中如何在一张图片上加一个灰色蒙层,并添加事件?

最终效果&#xff1a; 实现原理&#xff1a; 移动到图片上的时候&#xff0c;给img加一个伪类 &#xff01;&#xff01;此时就要地方要注意了&#xff0c;因为img标签是闭合的标签&#xff0c;无法直接添加 伪类&#xff08;::after&#xff09;&#xff0c;所以 我是在img外…

微搭低代码入门03函数

目录 1 函数的定义与调用2 参数与返回值3 默认参数4 将功能拆分成小函数5 函数表达式6 箭头函数7 低代码中的函数总结 在用低代码开发软件的时候&#xff0c;除了我们上两节介绍的变量、条件语句外&#xff0c;还有一个重要的概念叫函数。函数是执行特定功能的代码片段&#xf…

Zabbix中文监控指标数据乱码

1&#xff09;点击主机&#xff0c;选择Zabbix server 中的 图形 一项&#xff0c;可以看到当前显示的为乱码 2&#xff09; 下载字体文件&#xff1a; https://gitcode.com/open-source-toolkit/4a3db/blob/main/SimHei.zip 解压unzip -x SimHei.zip 3&#xff09; 替换字体文…

限价订单簿中的高频交易

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

JDBC事务管理、四大特征(ACID)、事务提交与回滚、MySQL事务管理

目录 一、什么是JDBC事务&#xff1f; &#xff08;1&#xff09;事务概念。 &#xff08;2&#xff09;JDBC事务的实现。 1、提交。 2、回滚。 &#xff08;3&#xff09;生活中可以类比 JDBC 事务的例子。 1、以网上购物为例。 二、JDBC的四大事务&#xff08;ACID&#xff0…

操作系统离散存储练习题

1. (简答题)分页存储管理系统具有快表&#xff0c;内存访问时间为2ns&#xff0c;检索快表时间为0.5ns&#xff0c;快表命中率为80%&#xff0c;求有效访问时间 -分析&#xff1a;首先访问缓存&#xff08;快表&#xff09;&#xff0c;如果没有找到访问内存&#xff08;页表&…

【AI日报】2024年11月13号

我回来啦&#xff01;&#xff01;发现自己好久不发文章了。 在某头部AI公众号实习的过程中&#xff0c;学到太多太多了&#xff0c;也感谢某位大神的指点&#xff0c;也衷心祝愿他的IP可以越做越好 之后因为时间关系&#xff0c;可能要自己出来单干了。 在实习过程中学到的…

inline内联函数(C++)

a&#xff09;⽤inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调⽤的地⽅展开内联函数&#xff0c;这样调⽤内联函数就不需要建⽴栈帧了&#xff0c;就可以提⾼效率。 b&#xff09;inline对于编译器⽽⾔只是⼀个建议&#xff0c;也就是说&#xff0c;你加了inl…

C++builder中的人工智能(27):如何将 GPT-3 API 集成到 C++ 中

人工智能软件和硬件技术正在迅速发展。我们每天都能看到新的进步。其中一个巨大的飞跃是我们拥有更多基于自然语言处理&#xff08;NLP&#xff09;和深度学习&#xff08;DL&#xff09;机制的逻辑性更强的AI聊天应用。有许多AI工具可以用来开发由C、C、Delphi、Python等编程语…

Harmony- List组件最后一个item显示不全

在使用List组件显示数据的时候会出现最后一个item显示不全的问题&#xff0c;如下 出现在高度问题上&#xff0c;如果List组件上下没有其他占位组件就是正常显示的 解决方案&#xff1a; 1.给List组件加上layoutWeight(1)&#xff0c;使它填满父控件剩余空间; 2.还有一种情况…

neo4j desktop基本入门

下载安装不在赘述&#xff0c;本文只记述一些neo4j的基本入门操作 连接本地neo4j数据库 1. 点击ADD添加连接 端口一般是7687 账户名和密码忘记了&#xff0c;可以通过neo4j web&#xff08;默认为neo4jneo4j://localhost:7687/neo4j - Neo4j Browser&#xff09;重置密码 AL…