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

JavaScript中的作用域、闭包、定时器 由浅入深

1. JavaScript中的作用域是什么?

作用域(Scope)是程序中定义变量的区域,它决定了变量的可访问性(可见性)。在JavaScript中,作用域主要分为三种:全局作用域、函数作用域和块级作用域(ES6引入)。

  1. 全局作用域:在代码任何地方都能访问的变量,它们定义在函数外部。在浏览器中,全局作用域是window对象。
  2. 函数作用域:在函数内部定义的变量,只能在函数内部访问。
  3. 块级作用域:由一对花括号{}限定,使用let和const声明的变量只能在块内访问(例如:if、for循环等)。
    作用域链:当访问一个变量时,JavaScript引擎会从当前作用域开始查找,如果当前作用域没有,则向上一级作用域查找,直到全局作用域。如果全局作用域也没有,则报错(ReferenceError)。

案例分析1:作用域

var globalVar = "global"; // 全局作用域
function outer() {var outerVar = "outer"; // outer函数作用域function inner() {var innerVar = "inner"; // inner函数作用域console.log(globalVar); // 可以访问全局变量console.log(outerVar);  // 可以访问外部函数变量console.log(innerVar);  // 可以访问自身变量}inner();console.log(globalVar); // 可以访问全局变量console.log(outerVar);  // 可以访问自身变量// console.log(innerVar); // 报错,innerVar在inner函数内部
}
outer();
// console.log(outerVar); // 报错,outerVar在outer函数内部

2. 闭包会在哪些场景中使用?

闭包(Closure)是指一个函数可以访问并记住其词法作用域,即使该函数在其词法作用域之外执行。简单来说,闭包就是函数内部定义的函数,并且这个内部函数可以访问外部函数的变量。
闭包的使用场景:

  1. 封装私有变量:通过闭包创建私有变量,避免全局污染。
  2. 模块模式:创建模块,暴露公共接口,隐藏私有实现。
  3. 回调函数:在异步编程中,回调函数常常形成闭包,以保留某些状态。
  4. 函数工厂:创建多个相似但配置不同的函数。
  5. 在定时器、事件监听器等需要保留状态的场景。

案例分析2:闭包

// 封装私有变量
function createCounter() {let count = 0; // 私有变量return {increment: function() {count++;return count;},decrement: function() {count--;return count;},getCount: function() {return count;}};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2
// 模块模式
const Module = (function() {let privateVar = 'I am private';function privateMethod() {console.log(privateVar);}return {publicMethod: function() {privateMethod();}};
})(); //自调用,把最终函数返回的结果赋值给Module 
Module.publicMethod(); // 输出: I am private
// Module.privateMethod(); // 报错:未定义
// console.log(Module.privateVar); // 报错:未定义
// 回调函数中使用闭包
function delayedLog(value, delay) {setTimeout(function() {console.log(value);}, delay);
}
delayedLog('Hello after 1 second', 1000);

3.通过定时器循环输出自增的数字,通过js的代码如何实现?

我们经常会遇到需要每隔一段时间输出一个自增的数字,例如从0开始,每秒输出一个数字,依次递增。我们可以使用setIntervalsetTimeout来实现。
但是有一个经典的陷阱:如果直接在循环中使用setTimeout,并且不采取任何措施,由于JavaScript的事件循环机制,循环变量在循环结束后才会被更新,导致输出相同的值(循环结束后的值)或者不符合预期的值。
解决方案:

  1. 使用闭包:在循环体内为每次迭代创建一个闭包,以捕获当前的循环变量。
  2. 使用let声明循环变量:因为let具有块级作用域,每次循环都会创建一个新的变量绑定。
  3. 使用setTimeout的第三个参数(将额外的参数传递给回调函数,但这种方式在循环中并不直接解决递增问题,但可以传递当前值)。

定时器循环输出自增的数字
错误示例:

//错误示例:
for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(i); // 输出5次5}, 1000);
}
// 原因:所有定时器回调共享同一个变量i,循环结束后i为5,所以输出5次5。

正确方法1:使用闭包(IIFE)

for (var i = 0; i < 5; i++) {(function(j) {setTimeout(function() {console.log(j); // 0,1,2,3,4}, 1000 * j); // 为了更明显,这里让每个定时器间隔1秒的倍数})(i);
}

正确方法2:使用let声明循环变量(推荐)

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

正确方法3:使用setTimeout的第三个参数(传递参数)

for (var i = 0; i < 5; i++) {setTimeout(function(j) {console.log(j); // 0,1,2,3,4}, 1000 * i, i); // 第三个参数i会作为回调函数的参数
}

如果希望每隔1秒输出一个数字,并且连续输出(0,1,2,3,4),可以使用setInterval,但要注意清除定时器。
使用setInterval:

let i = 0;
const timer = setInterval(function() {console.log(i);i++;if (i >= 5) {clearInterval(timer);}
}, 1000);

或者使用递归的setTimeout(避免setInterval的连续执行可能带来的问题,如执行时间超过间隔时间):

let i = 0;
function printNumber() {console.log(i);i++;if (i < 5) {setTimeout(printNumber, 1000);}
}
setTimeout(printNumber, 1000);

总结:

  1. 作用域决定了变量的可见性,分为全局、函数和块级作用域。
  2. 闭包常用于封装私有变量、模块模式、回调函数等,可以记住并访问其词法作用域。
  3. 定时器循环输出自增数字时,要注意循环变量的作用域问题,可以使用闭包、let或setTimeout的第三个参数解决。同时,可以使用setInterval或递归setTimeout来实现连续输出。
http://www.lryc.cn/news/609534.html

相关文章:

  • 肾上腺疾病AI诊疗一体化系统应用方向探析
  • 机器学习——学习路线
  • 【拓扑序 容斥原理】P6651 「SWTR-5」Chain|省选-
  • 登录验证码功能实现:Spring Boot + Vue 全流程解析
  • Ethereum:智能合约开发者的“瑞士军刀”OpenZeppelin
  • Neo4j 社区版 Mac 安装教程
  • 数据结构---配置网络步骤、单向链表额外应用
  • Vue3核心语法进阶(Hook)
  • 如何使用EF框架操作Sqlite
  • 20250805问答课题-实现TextRank + 问题分类
  • 量子计算接口开发:Python vs Rust 性能对决
  • uniapp快遞上門提貨的時間選擇的插件
  • PyTorch生成式人工智能(25)——基于Transformer实现机器翻译
  • 代码详细注释:(linux)TCP客户端接收服务器端发的信息
  • AI 大模型分类全解析:从文本到多模态的技术图谱
  • Rust ⽣成 .wasm 的极致瘦⾝之道
  • 从 Hive 数仓出发,全面剖析 StarRocks、MySQL、HBase 的使用场景与区别
  • 【Spark征服之路-4.5-Spark-Streaming核心编程(三)】
  • [Oracle] TO_CHAR()函数
  • 安装MySQL教程时可能遇到的问题
  • 【Linux】重生之从零开始学习运维之GTID复制
  • XXE漏洞原理及利用
  • NSS-DAY17 2025SWPU-NSSCTF
  • Chrontel 【CH7103B-B】CH7103B HDMI to YPbPr Converter
  • 行业报告:.games域名正引领游戏娱乐产业营销新风向
  • 力扣 hot100 Day65
  • 嵌入式学习之51单片机——串口(UART)
  • 回归预测 | MATLAB实现BP神经网络多输入单输出回归预测+SHAP可解释分析
  • 分布式光伏气象站:为分散电站装上 “智慧之眼”
  • 零基础掌握 Scrapy 和 Scrapy-Redis:爬虫分布式部署深度解析