目录
一、前言
二、JavaScript 变量作用域
1、变量作用域
2、如何从函数外部访问内部变量
三、JavaScript 闭包
1、闭包的定义
2、闭包的组成
3、实例说明
四、闭包的优缺点
1、优点
2、缺点
五、一个有趣的实例(定时器与闭包)
一、前言
先来看一段代码;
// 两数相乘
function doMultiply(a){
return function(b){
return `${a} * ${b} = ${a * b}`;
}
}
// 与1相乘
const multiply1 = doMultiply(1);
// 与2相乘
const multiply2 = doMultiply(2);
// 与3相乘
const multiply3 = doMultiply(3);
// ......
for(let i = 1 ; i <= 9; i++ ){
console.log(multiply1(i), ";", multiply2(i), ";", multiply3(i), "......");
}
输出结果:
思考?在上述代码中:
- multiply1 、multiply2、multiply3是什么?
- 它们为什么能够访问到不同的变量a?
- 它们是如何从函数外部访问函数内部变量的?
二、JavaScript 变量作用域
1、变量作用域
在JavaScript中,变量的作用域分为两种:全局变量和局部变量;
根据JavaScript中链式作用域的特点,我们知道:
- 函数内部可以直接读取函数外部的全局变量,
- 函数外部无法直接读取函数内部的局部变量;
function fnOut(){
var a = 10;
// console.log(b); // ReferenceError: b is not defined
function fnIn(){
var b = 20;
console.log(a + b); // 30
}
fnIn();
}
fnOut();
在上述代码中:
- fnIn()内部函数可以访问到外部函数fnOut()中定义的变量a;
- fnOut()外部函数无法访问到内部函数fnIn()中定义的变量b;
2、如何从函数外部访问内部变量
有时候,需要访问函数内部的局部变量;正常情况下,无法实现,但可以采取一些不正常的方法;
既然 fnIn() 能访问到 fnOut() 中的变量a,那如果将fnIn() 作为fnOut()的返回值返回,会怎么样呢?
function fnOut(){
var a = 10;
// console.log(b); // ReferenceError: b is not defined
return function fnIn(){
var b = 20;
console.log(a + b); // 30
}
// fnIn();
}
let res = fnOut();
console.log(res);
既然给fnOut()定义了返回值,则使用变量res接收,打印输出返回值res:
是前面定义的内部函数fnIn(),这跟我们的预期结果是一致的;
这样做的意义是什么???
既然res是一个函数,那便可以在外部调用它,即:
function fnOut(){
var a = 10;
// console.log(b); // ReferenceError: b is not defined
return function fnIn(){
var b = 20;
console.log(a + b); // 30
}
// fnIn();
}
let res = fnOut();
// console.log(res);
// 调用res函数
res()
这个结果和一开始的输出结果是相同的;
这意味着:
- 在fnOut()函数的外部,能够调用其内部函数fnIn();
- 不仅能够访问到fnOut函数中定义的变量,更能够访问到fnIn()函数中定义的变量;
将fnOut()函数中的变量a和fnIn()函数中的变量b,都从外部传入,更容易理解:
function fnOut(a){
// var a = 10;
// console.log(b); // ReferenceError: b is not defined
return function fnIn(b){
// var b = 20;
console.log(a + b); // 30
}
// fnIn();
}
let res = fnOut(10);
// console.log(res);
// 调用res函数
res(20)
在调用res时,即可传入内部函数fnIn()的参数b;
上面代码中的res,就是闭包;它不仅保存了fnIn()函数实例的引用,也保存着该函数在创建时的作环境状态,可访问的所有变量;
三、JavaScript 闭包
闭包在JavaScript中是一个非常重要的概念,它可以帮助我们实现许多高级功能。理解闭包的工作原理对于编写高效、可维护的JavaScript代码非常重要。
1、闭包的定义
闭包是一个函数和其周围的状态(词法环境)的引用捆绑在一起形成的实体;
该环境包含了这个闭包创建时作用域内的任何局部变量;
也就是说,闭包让函数有了访问其创建时的作用域的能力;
2、闭包的组成
- 函数:一个普通的函数。
- 环境:函数创建时的作用域,包括所有可访问的变量。
3、实例说明
function doActivity(name){
var actName = `周末的活动有${name}`;
return function(time){
var actTime = `用了${time}小时`;
return actName + ',' + actTime;
}
}
let activity1 = doActivity("打游戏") ;
let activity2 = doActivity("看综艺");
let activity3 = doActivity("写代码");
console.log(activity1(2));
console.log(activity2(3));
console.log(activity3(5));
在上述示例中:
- 定义了一个doActivity()函数,接收一个参数name,并且返回一个新的函数;返回的函数接收一个time参数,并返回处理后的值;
- 使用doActivity()函数创建了三个新函数,分别输出每项活动的进行时间(传入的参数);
- activity1、activity2、activity3,这三个函数都是闭包,是执行doActivity()函数时创建的内部函数实例的引用,以及词法环境的引用;
- 他们虽然有共同的函数定义,但保存了不同的词法环境,即保存了不同的 name 与 actName 的值;
四、闭包的优缺点
1、优点
数据封装和私有化:闭包可以用来模拟私有方法和变量,使得这些变量只能通过闭包内部定义的函数来访问,从而实现数据封装和隐藏。
维持状态:闭包可以维持函数的状态,即使外部函数已经执行完毕,闭包内部定义的函数仍然可以访问外部函数的局部变量,这使得状态可以在多次函数调用之间保持。
模块化代码:闭包可以帮助创建模块,通过返回一个包含多个方法和变量的对象,这些方法和变量被封装在一个闭包内部,外部无法直接访问。
2、缺点
内存泄漏:闭包会存储其创建时的作用域,如果作用域的变量不再被使用且未被清除,会增大内存小号,导致内存泄漏;
影响性能:闭包可能会影响代码的性能,尤其是在频繁创建闭包的情况下,因为每次创建闭包都会捕获当前的作用域;
五、一个有趣的实例(定时器与闭包)
for(var i = 1; i <= 5; i++){
setTimeout(()=>{
console.log(i);
}, 1000)
}
执行上述代码,我们的预期结果应该是:1,2,3,4,5(每隔一秒执行一次定时器内的回调);
实际上是输出了5个6,为什么?
因为JavaScript是单线程;代码中的for循环是同步任务,会被放入执行栈中执行;而setTimeout是异步任务,会被放到任务队列等待同步执行完毕,再执行;
当setTimeout定时器执行的时候,同步任务for循环(创建了5个定时器,在任务队列依次等待)已经执行完毕了,i 的值已经加到了6,所以就会打印出5个6了;
怎么才能输出预期结果?
使用闭包来存储定时器被创建时的 i 值;
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(() => {
console.log(i);
}, 1000);
})(i)
}
还有一个方式是将var改成let,也能实现,为什么?
将for循环中的var关键字改成let关键字;
for (let i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
=================================================================
先记录到这里吧~!关于闭包的更深入理解,还在学习中;
希望走过路过的大佬们多多指点呀~~~