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

写出一个简单的JavaScript闭包示例,并解释为什么它形成了闭包。

写出一个简单的JavaScript闭包示例,并解释为什么它形成了闭包。

闭包示例代码

function outerFunction() {
    // 定义一个变量,该变量处于 outerFunction 的作用域内
    let message = '这是一个闭包示例';

    // 定义一个内部函数 innerFunction
    function innerFunction() {
        // 内部函数可以访问外部函数作用域中的变量 message
        console.log(message);
    }

    // 返回内部函数
    return innerFunction;
}

// 调用 outerFunction 并将返回的 innerFunction 赋值给变量 closure
let closure = outerFunction();

// 调用 closure,实际上就是调用 innerFunction
closure(); 

代码解释(大白话版)

代码执行过程
  1. 定义外部函数 outerFunction:就好像我们建了一个大房子(外部函数的作用域),在这个大房子里我们放了一个物品 message(定义了一个变量 message)。
  2. 定义内部函数 innerFunction:在这个大房子里面又隔出了一个小房间(内部函数的作用域),小房间里的人(内部函数)可以看到大房子里的物品 message,并且可以使用它。
  3. 返回内部函数:我们把小房间的钥匙(内部函数 innerFunction)交出去了,虽然大房子的门关上了(外部函数执行完毕),但是拿到钥匙的人(变量 closure)仍然可以打开小房间,并且通过小房间看到大房子里的物品 message
  4. 调用 closure:拿到钥匙的人打开小房间,看到了大房子里的物品 message,并把它打印了出来。
为什么形成了闭包

闭包形成的原因是内部函数 innerFunction 访问了它外部函数 outerFunction 作用域中的变量 message。当外部函数 outerFunction 执行完毕后,按照正常情况,它里面的变量 message 应该被销毁,因为它的作用域已经结束了。但是由于内部函数 innerFunction 引用了这个变量,所以 JavaScript 引擎不会销毁 message,而是会保留它,让内部函数在需要的时候仍然可以访问。这就形成了一个闭包,简单来说,闭包就是一个函数能够记住并访问它外部函数作用域中的变量,即使外部函数已经执行完毕。

在这个例子中,closure 变量保存了 innerFunction,当我们调用 closure() 时,innerFunction 仍然可以访问 outerFunction 作用域中的 message 变量,这就是闭包的体现。

JavaScript 中闭包的优缺点

优点

1. 数据保密和封装

闭包可以把一些变量藏起来,就像你有一个带锁的箱子,只有特定的人(函数)才能打开它拿里面的东西,这样可以避免外部随意访问和修改这些数据,起到保护数据的作用。

代码示例

function createBankAccount() {
    // 这是藏在箱子里的钱,外部不能直接拿到
    let balance = 0;

    return {
        // 存钱的操作
        deposit: function(amount) {
            balance = balance + amount;
            return balance;
        },
        // 取钱的操作
        withdraw: function(amount) {
            if (amount <= balance) {
                balance = balance - amount;
                return balance;
            }
            return '余额不足';
        },
        // 查看余额的操作
        getBalance: function() {
            return balance;
        }
    };
}

// 创建一个银行账户
let myAccount = createBankAccount();

// 存钱
console.log(myAccount.deposit(100)); 
// 取钱
console.log(myAccount.withdraw(50)); 
// 查看余额
console.log(myAccount.getBalance()); 

解释:这里的 balance 变量就像是被锁在 createBankAccount 这个“箱子”里,外部不能直接访问它。只能通过 depositwithdrawgetBalance 这些“钥匙”(函数)来对它进行操作,保证了数据的安全。

2. 让变量一直“活着”

闭包能让外部函数里的变量一直存在于内存中,不会因为外部函数执行完就消失。就好像你在一个地方放了个东西,即使你离开了那个地方,东西还在那里,下次回来还能接着用。

代码示例

function makeCounter() {
    let count = 0;

    return function() {
        count = count + 1;
        return count;
    };
}

// 创建一个计数器
let counter = makeCounter();

// 第一次调用计数器
console.log(counter()); 
// 第二次调用计数器
console.log(counter()); 

解释count 变量在 makeCounter 函数里,当 makeCounter 执行完返回内部函数后,count 并没有消失。每次调用 counter 函数时,count 的值都会继续增加,因为闭包让它一直存在于内存中。

3. 灵活使用函数

闭包可以让我们把函数变得更灵活,就像搭积木一样,可以根据不同的需求组合出不同的功能。

代码示例

function multiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

// 创建一个乘以 2 的函数
let double = multiplier(2);
// 创建一个乘以 3 的函数
let triple = multiplier(3);

// 使用乘以 2 的函数
console.log(double(5)); 
// 使用乘以 3 的函数
console.log(triple(5)); 

解释multiplier 函数根据传入的 factor 参数返回一个新的函数。我们可以用这个函数创建出不同的乘法函数,比如乘以 2 的 double 函数和乘以 3 的 triple 函数,非常灵活。

缺点

1. 占内存

闭包会让一些变量一直待在内存里不被清理,就像你家里有一些不用的东西一直堆着占地方。如果闭包用得太多或者用得不好,会让内存占用越来越大,可能导致电脑或者程序变慢。

代码示例

function createBigClosure() {
    // 这里创建了一个很大的数组,占很多内存
    let bigArray = new Array(1000000).fill(0);

    return function() {
        return bigArray.length;
    };
}

let closure = createBigClosure();
// 即使 createBigClosure 执行完了,bigArray 还在内存里占着地方

解释bigArray 是一个很大的数组,由于闭包引用了它,即使 createBigClosure 函数执行完了,bigArray 也不会被清理掉,一直占用着内存。

2. 运行速度慢

使用闭包时,程序查找变量会比较麻烦,就像你在一个很大的仓库里找东西,要一层一层地找,所以会比直接找东西慢一些。

代码示例

function outer() {
    let outerVar = 10;
    function inner() {
        return outerVar;
    }
    return inner;
}

let innerFunc = outer();
// 调用 innerFunc 时,要去闭包的作用域里找 outerVar,比较慢
console.log(innerFunc()); 

解释:当调用 innerFunc 时,程序需要在闭包的作用域里找 outerVar,这比直接在当前作用域里找变量要慢一些,因为要多走一些“路”。

3. 代码难懂难维护

闭包会让代码变得复杂,就像一团乱麻,很难理清里面的关系。特别是闭包嵌套很多层的时候,别人看你的代码或者你自己过段时间再看,可能都不知道代码是怎么运行的。

代码示例

function outer() {
    let outerVar = 1;
    return function middle() {
        let middleVar = 2;
        return function inner() {
            return outerVar + middleVar;
        };
    };
}

let innerFunc = outer()();
console.log(innerFunc()); 

解释:这里有三层闭包,要理解 inner 函数怎么拿到 outerVarmiddleVar 需要费点脑筋,这就增加了代码的理解和维护难度。

闭包在实际开发中用途广泛,下面用大白话详细介绍几种常见的应用场景。

1. 实现私有变量和方法

想象一下你有一个小房间,里面放着一些重要的东西,你不想让别人随意进出这个房间拿走或改变里面的东西。在编程里,闭包就可以帮你实现这样的“小房间”。

比如你要创建一个人的对象,这个人有个名字,但你不想让别人直接修改这个名字,只能通过特定的方式来查看和修改。

function createPerson() {
    // 这个名字就像放在小房间里的东西
    let name = '张三';

    return {
        // 这个方法就像是一把钥匙,能让你查看小房间里的名字
        getName: function() {
            return name;
        },
        // 这个方法也是一把钥匙,能让你修改小房间里的名字
        setName: function(newName) {
            name = newName;
        }
    };
}

let person = createPerson();
console.log(person.getName()); 
person.setName('李四');
console.log(person.getName()); 

在这个例子中,name 变量被闭包保护起来,外部没办法直接访问它,只能通过 getNamesetName 方法来操作,就像只有拿着特定钥匙的人才能进出小房间一样。

2. 函数柯里化

函数柯里化就像是搭积木,你可以把一个大的积木(多参数函数)拆分成一个个小积木(单参数函数),然后根据需要一块一块地组合起来。

比如说有一个加法函数,它本来要同时接收两个数字才能算出它们的和。但通过闭包,你可以让它先接收一个数字,然后返回一个新的函数,这个新函数再接收另一个数字,最后算出和。

function add(a, b) {
    return a + b;
}

// 这个函数就像一个积木拆分器
function curry(func) {
    return function(a) {
        return function(b) {
            return func(a, b);
        };
    };
}

let curriedAdd = curry(add);
console.log(curriedAdd(3)(5)); 

这里的 curriedAdd 函数就像一个可以分步使用的积木,先给它一个数字 3,它返回一个新的函数,再给这个新函数一个数字 5,就得到了最终的结果。

3. 事件处理

在网页开发中,经常会有一些按钮、链接之类的元素,当你点击它们时会触发一些操作。闭包可以帮助这些操作记住一些状态信息。

想象一下有一个按钮,你每点击一次,它就会记录你点击的次数。

<!DOCTYPE html>
<html lang="en">

<body>
    <button id="myButton">点击我</button>
    <script>
        let count = 0;
        const button = document.getElementById('myButton');

        button.addEventListener('click', function() {
            count++;
            console.log(`你点击了 ${count}`);
        });
    </script>
</body>

</html>

在这个例子中,当你点击按钮时,事件处理函数就像一个小助手,它能记住外部的 count 变量,每次点击就把这个变量的值加 1,然后告诉你点击了多少次。

4. 实现循环中的延迟操作

有时候你需要在循环里设置一些延迟执行的操作,就像你要给几个朋友依次发消息,但每个消息都要隔一段时间再发。闭包可以保证每个延迟操作都能拿到正确的循环变量值。

for (let i = 0; i < 5; i++) {
    (function(index) {
        setTimeout(function() {
            console.log(`这是第 ${index} 次延迟输出`);
        }, index * 1000);
    })(i);
}

这里用一个小技巧(立即执行函数)创建了闭包,把当前的 i 值传给内部的函数。这样每个 setTimeout 函数就像一个小信使,能准确地记住自己是第几次循环,然后按顺序依次输出信息。

5. 模块化开发

在开发一个大项目时,就像建一座大房子,你会把它分成很多小房间(模块),每个小房间有自己的东西(变量和方法),而且要避免不同房间的东西混在一起。闭包可以帮你实现这样的模块化。

const myModule = (function() {
    // 这个变量就像小房间里的一个宝贝
    let privateVariable = 10;

    // 这个方法就像小房间里的一个工具
    function privateMethod() {
        return privateVariable * 2;
    }

    return {
        // 这个方法就像小房间的一扇窗户,外面的人可以通过它使用里面的工具
        publicMethod: function() {
            return privateMethod();
        }
    };
})();

console.log(myModule.publicMethod()); 

在这个例子中,通过闭包创建了一个独立的模块 myModule。模块内部有自己的私有变量和方法,外部只能通过 publicMethod 这个“窗户”来间接使用里面的东西,这样就避免了和其他代码产生冲突。

相关文章:

  • QT基础八、与时间相关的UI控件
  • 【Kubernets】Kubernets资源类型Deployment详细介绍
  • 推动智驾普及,谁是自主品牌前视一体机计算方案市场TOP1?
  • 电力通信物联网应用,国密网关守护电力数据安全
  • 我的AI工具箱Tauri版-IntegrationOfDecorationDesignStyles室内装修设计风格融合
  • DVWA 靶场(含代码审计)
  • 武汉火影数字|VR沉浸式空间制作 VR大空间打造
  • 开源免费文档翻译工具 可支持pdf、word、excel、ppt
  • Transformer为什么需要多头注意力(Multi-Head Attention)?如果没有多头会怎么样?
  • DuodooBMS源码解读之 sale_delivery模块
  • Vue面试2
  • selenium工作原理
  • 海外企业真的需要跨境专线网络吗?
  • 广州SMT贴片加工如何优化生产成本与品质管控?
  • 本地部署DeepSeek R1大模型
  • 什么是事务?并发事务引发的问题?什么是MVCC?
  • 如何在自定义组件中使用v-model实现双向绑定
  • Linux shell脚本,手机上跑的.sh脚本写法
  • Oeko-TexStandard100认证会产生哪些成本?
  • DeepSeek掘金——SpringBoot 调用 DeepSeek API 快速实现应用开发
  • 记事本代码做网站/关键词分词工具
  • 如何建立公司网站推广/郑州seo排名优化
  • 吴江企业建设网站/今日军事新闻热点事件
  • 图片制作动图/南昌seo全网营销
  • 网上发布信息的网站怎么做/营销平台有哪些
  • 通化县住房和城乡建设局网站/长沙有实力的关键词优化价格