JavaScript 闭包

目录

一、前言

二、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);
}

=================================================================

先记录到这里吧~!关于闭包的更深入理解,还在学习中;

希望走过路过的大佬们多多指点呀~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值