首页 > 其他分享 >【闭包】前端的“保护神”——闭包详解+底层原理

【闭包】前端的“保护神”——闭包详解+底层原理

时间:2024-12-21 17:30:17浏览次数:11  
标签:闭包 function 保护神 函数 count 详解 console log

目录

 一、闭包是什么?概念

二、闭包为什么存在?作用

1. 创建私有变量

2. 实现数据封装与信息隐藏

3. 模拟私有方法

4. 保存函数执行时的状态

5. 回调函数和事件处理

6. 模块化编程

7. 懒加载与延迟执行

 三、闭包怎么用?实践+业务场景

1. 封装私有变量

2. 延迟执行(定时器、异步回调)

3. 事件监听和回调函数

5. 防抖和节流

业务场景:权限控制和角色管理

四、深入底层了解闭包的运行原理(难度指数⭐⭐⭐⭐)


 一、闭包是什么?概念


闭包是指 函数可以“记住”并访问定义时的作用域,即使这个函数在外部被调用时,依然能访问到其定义时的父函数的局部变量。

  • 父函数和子函数

    • 闭包通常发生在一个函数(父函数)内部定义了另一个函数(子函数),且子函数可以访问父函数的局部变量。
  • 通过return暴露子函数

    • 当父函数返回子函数时,子函数就形成了闭包。因为子函数不仅仅是返回的函数,它还“记住”了父函数的作用域。
  • 作用域链和内存管理

    • 通常,父函数的局部变量在父函数执行完毕后会被销毁,但由于闭包的存在,这些局部变量会被保留在内存中,直到闭包不再被引用。
    • 闭包使得父函数的局部变量不被销毁,同时也避免了全局作用域的污染,因为它们只在闭包内部可见。


二、闭包为什么存在?作用

1. 创建私有变量

闭包最常见的作用之一是实现 私有变量。在 JavaScript 中,变量通常是公开的,任何函数都能访问它们。而闭包允许我们创建只能通过特定函数访问的私有变量,这样就可以避免外部代码随意访问或修改它们。

  • 示例:
function createCounter() {
    let count = 0; // 这是一个私有变量
    return {
        increment: function() {
            count++;
            console.log(count);
        },
        decrement: function() {
            count--;
            console.log(count);
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
counter.increment(); // 输出: 1
counter.increment(); // 输出: 2
console.log(counter.getCount()); // 输出: 2
// count 变量是私有的,外部无法直接访问

在这个例子中,count 变量通过闭包被封装在 createCounter 函数中,外部无法直接访问和修改它,只有通过 incrementdecrementgetCount 方法才能操作它。

2. 实现数据封装与信息隐藏

闭包提供了数据封装的能力,可以将状态和行为封装在一个函数内部,并通过暴露的接口与外部进行交互。这有助于信息隐藏,防止外部代码不小心或恶意地修改内部数据。

  • 示例:

function bankAccount(initialBalance) {
    let balance = initialBalance; // 私有变量
    return {
        deposit: function(amount) {
            balance += amount;
            console.log(`Deposited: $${amount}`);
        },
        withdraw: function(amount) {
            if (balance >= amount) {
                balance -= amount;
                console.log(`Withdrew: $${amount}`);
            } else {
                console.log('Insufficient funds');
            }
        },
        getBalance: function() {
            return balance;
        }
    };
}

const myAccount = bankAccount(1000);
myAccount.deposit(500); // Deposited: $500
myAccount.withdraw(200); // Withdrew: $200
console.log(myAccount.getBalance()); // 1300
// 不能直接访问或修改 balance

这里的 balance 变量在 bankAccount 函数的作用域内被封装,外部无法直接访问或修改它,只有通过 depositwithdrawgetBalance 方法才能与其交互。

3. 模拟私有方法

除了私有变量,闭包也可以用来模拟 私有方法。你可以将某些功能封装在闭包内部,外部只能通过公开的方法调用它们,从而达到隐藏细节、减少外部依赖的目的。

  • 示例:

function car(model) {
    let speed = 0; // 私有变量
    function accelerate() {
        speed += 10;
        console.log(`Accelerating... Speed is now ${speed} km/h`);
    }
    return {
        start: function() {
            console.log(`${model} is starting`);
            accelerate();
        }
    };
}

const myCar = car('Toyota');
myCar.start(); // Toyota is starting
               // Accelerating... Speed is now 10 km/h

在这个例子中,accelerate 函数是私有的,外部无法直接调用它,只有通过 start 方法间接调用。

4. 保存函数执行时的状态

闭包能够保持其外部函数的执行上下文,即使外部函数已经执行完毕。这样,我们可以保存函数的 状态,在后续的调用中继续使用这些状态。这对于处理 异步操作回调函数 中的状态非常有用。

  • 示例:
function makeAdder(x) {
    return function(y) {
        return x + y; // 闭包可以记住 x 的值
    };
}

const add5 = makeAdder(5);
console.log(add5(10)); // 15
const add10 = makeAdder(10);
console.log(add10(10)); // 20

在这个例子中,makeAdder 返回的函数是一个闭包,它“记住”了 x 的值。即使 makeAdder 执行结束后,x 仍然在闭包中保存,并且在后续的调用中可以使用它。

5. 回调函数和事件处理

在前端开发中,闭包广泛应用于 事件处理异步回调。它们能够保持对外部数据(如事件触发时的状态、函数参数等)的访问,即使在异步操作完成后,闭包仍然能够访问这些数据。

  • 示例:事件处理中的闭包
function setupButton() {
    let counter = 0; // 闭包中的私有状态
    document.getElementById('myButton').addEventListener('click', function() {
        counter++;
        console.log(`Button clicked ${counter} times`);
    });
}

setupButton();

在这个例子中,事件回调函数可以访问 counter 变量,它即使在 setupButton 函数执行完毕后仍然保持状态。

6. 模块化编程

闭包帮助我们将代码分成独立的模块,每个模块有自己的私有数据和方法。这样不仅可以避免全局命名冲突,还可以提高代码的可维护性和可复用性。

  • 示例:
const counterModule = (function() {
    let count = 0; // 私有变量
    return {
        increment: function() {
            count++;
            console.log(count);
        },
        decrement: function() {
            count--;
            console.log(count);
        }
    };
})();

counterModule.increment(); // 1
counterModule.decrement(); // 0

通过立即执行函数表达式(IIFE),counterModule 模块中的 count 是私有的,外部无法直接访问。闭包保证了每个模块都有独立的作用域和私有数据。

7. 懒加载与延迟执行

闭包还可以用于延迟执行函数和延迟计算,常见于懒加载场景。例如,某些数据或资源的加载操作可以通过闭包延迟到需要时再执行。

  • 示例:

function fetchData() {
    let data = null;
    return function() {
        if (data === null) {
            console.log('Fetching data...');
            data = 'Some data'; // 模拟数据加载
        }
        return data;
    };
}

const getData = fetchData();
console.log(getData()); // Fetching data... Some data
console.log(getData()); // Some data

这里,data 只在第一次调用 getData() 时被加载,之后就不会再进行加载操作,闭包保存了 data 的状态。

 三、闭包怎么用?实践+业务场景

1. 封装私有变量

闭包常常用于封装私有变量和创建数据的封装(即模块化编程)。在 JavaScript 中,通常没有内建的私有变量机制,但闭包可以帮助你达到类似的效果。

  • 示例:计数器
function createCounter() {
    let count = 0;  // 私有变量
    return {
        increment: function() {
            count++;
            console.log(count);
        },
        decrement: function() {
            count--;
            console.log(count);
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
counter.increment();  // 输出: 1
counter.increment();  // 输出: 2
counter.decrement();  // 输出: 1
console.log(counter.getCount());  // 输出: 1

解析

  • count 是一个私有变量,只能通过 incrementdecrementgetCount 方法访问。
  • 外部无法直接访问 count,实现了数据的封装。

2. 延迟执行(定时器、异步回调)

闭包经常用于处理异步操作和定时任务。例如,使用 setTimeoutsetInterval 时,闭包允许你保留函数的执行上下文,从而延迟执行某些操作。

  • 示例:延迟执行任务

function createDelayedTask(message, delay) {
    return function() {
        setTimeout(function() {
            console.log(message);
        }, delay);
    };
}

const delayedTask = createDelayedTask('Hello, World!', 2000);
delayedTask();  // 2秒后输出: Hello, World!

解析

  • createDelayedTask 返回一个闭包,这个闭包可以记住其外部环境中的变量(如 messagedelay)。
  • 通过 setTimeout 延迟输出 message,即使函数 createDelayedTask 已经执行完毕。

3. 事件监听和回调函数

闭包在事件监听器和回调函数中非常常见。它可以让回调函数访问外部作用域中的变量,从而保持对数据的引用。

5. 防抖和节流

防抖和节流是常见的性能优化技巧。防抖(Debouncing)通常用于限制某些操作频繁触发(如输入框中的搜索建议),而节流(Throttling)则是控制某些操作的触发频率(如窗口大小调整事件)。

业务场景:权限控制和角色管理

闭包可以用于权限管理和角色管理的场景中,通过闭包来封装不同角色的权限信息,从而提供灵活的权限控制。

  • 示例:权限管理
function createRoleChecker(role) {
    const permissions = {
        admin: ['read', 'write', 'delete'],
        user: ['read'],
        guest: []
    };

    return function(permission) {
        if (permissions[role] && permissions[role].includes(permission)) {
            console.log(`${role} has ${permission} permission.`);
        } else {
            console.log(`${role} does not have ${permission} permission.`);
        }
    };
}

const adminChecker = createRoleChecker('admin');
const userChecker = createRoleChecker('user');
const guestChecker = createRoleChecker('guest');

adminChecker('write');  // admin has write permission.
userChecker('write');   // user does not have write permission.
guestChecker('read');   // guest does not have read permission.

解释

  • createRoleChecker 返回一个闭包,它保存了角色的权限信息。
  • 每次调用 roleChecker 时,可以判断特定角色是否拥有某个权限。
  • 通过闭包,你可以灵活地管理角色和权限数据,避免权限数据暴露。

四、深入底层了解闭包的运行原理(难度指数⭐⭐⭐⭐)

思考:

  • 下面代码输出什么?
  • A(2) 执行时,局部变量 xy 存储在内存中,它们什么时候会被销毁
function A(y) {
    let x = 2;
    function B(z) {
        console.log(x + y + z);
    }
    return B;
}

let C = A(2);
C(3);

上述执行的过程中到底在做什么:

  1. A(2) 的调用:

    • JavaScript 引擎会为 A(2) 创建一个 执行上下文。
    • 当调用 A(2) 时,y 赋值为 2,并且在 A 内部创建了一个局部变量 x = 2 和一个函数 B(z)
    • 然后,A 返回了函数 B
  2. 形成闭包:

    • 函数 BA 内部定义,因此它形成了闭包,能够访问 A 内部的变量 xy,即使 A 执行完毕,B 仍然可以访问这些变量。
    • A(2) 执行完,JavaScript 会销毁 A 的执行上下文,但由于 B 是通过闭包持有对 A 作用域的引用,因此 xy 并没有被销毁,它们的内存空间会保留下来。
  3. B 赋值给 C:

    • 通过 let C = A(2);,变量 C 被赋值为函数 B,且 C 具有 A 中的作用域(闭包),能够访问 xy
  4. 调用 C(3):

    • 当调用 C(3) 时,实际执行的是 B(3)。JavaScript 会创建C(3) 的执行上下文:即 B(3) 的执行上下文。
    • B 中,xy 来自 A 的作用域,z 来自 B 的参数。因此,x + y + z 被计算为 7,并打印出来。
    • C(3) 调用结束,C(3) 的执行上下文B(3) 的执行上下文会被销毁。 但闭包仍然存在,因为 B 被保存在变量 C 中,并且 C 仍然引用着闭包。当 CB 被垃圾回收时,闭包才会被销毁。因此,在 C(3) 执行后,虽然 B 的执行上下文栈帧被销毁,但闭包中的内存(如 xy)会继续存在,直到 C 不再引用 B

留作业:如果闭包 B 被赋值给多个其他变量,这些变量会如何影响 xy 的内存空间

评论区做答。

标签:闭包,function,保护神,函数,count,详解,console,log
From: https://blog.csdn.net/weixin_45188218/article/details/144620085

相关文章

  • 39.在 Vue3 中使用 OpenLayers 导出 GeoJSON 文件及详解 GEOJSON 格式
    一、引言在Web地图开发领域,Vue3作为一款流行的前端框架,结合强大的OpenLayers地图库,能够实现丰富多样的地图功能。其中,将地图数据以GeoJSON格式导出是一项常见且实用的需求,本文将深入探讨如何在Vue3环境下借助OpenLayers达成这一目标,并详细剖析GeoJSON格式文件。......
  • MySQL 8.0 新特性详解
    MySQL8.0引入了许多重要的功能和改进,这些特性显著提升了数据库的性能、可用性和开发体验。以下是MySQL8.0的主要新特性及其详细解析:降序索引支持MySQL8.0支持降序索引,而之前版本即使语法支持,实际仍为升序。通过降序索引,查询性能在某些场景中得到显著优化。示例CR......
  • MySQL 全局参数配置详解
    引言合理的全局参数配置对于MySQL数据库的性能和稳定性至关重要。通过调整这些参数,可以优化服务器资源的使用效率,提高查询响应速度,并确保系统的可靠性和安全性。本文将详细介绍几个关键的MySQL全局参数及其最佳实践配置建议,帮助读者构建一个高效稳定的MySQL环境。一、连......
  • CPP虚函数详解与实例
    CPP虚函数详解与实例在CMU_15445的Project3中大量使用了虚函数,抽象类的方法主要在Expression(表达式)以及Executor(Plan_Node的执行)中,在完成Part1的时候仅关注了功能的实现,还没有完全搞清楚为什么要使用虚函数以及抽象类,以及虚函数背后的原理,本次补充一下.......
  • 【C++】智能指针详解
    ......
  • Python中所有子图标签Legend显示详解
    在数据可视化中,图例(legend)是一个非常重要的元素,它能够帮助读者理解图表中不同元素的含义。特别是在使用Python进行可视化时,matplotlib库是一个非常强大的工具,能够轻松创建包含多个子图的图表,并在每个子图中显示图例。本文将详细介绍如何在Python的matplotlib库中为所有子图显示标......
  • python | 一文看懂Python闭包机制与变量作用域规则
    本文来源公众号“python”,仅用于学术分享,侵权删,干货满满。原文链接:一文看懂Python闭包机制与变量作用域规则在Python编程中,闭包和变量作用域是两个重要的概念。闭包(Closure)是一种函数对象,它能够记住并捕获创建时的环境变量,因此即使在函数调用结束后,闭包也能访问这些变量。闭......
  • Java实现单词的翻译(详解爬虫操作)
    JAVA通过Crawler实现英语单词的翻译首先声明一点,这种方法仅限于低频次的交互来获取翻译信息,一旦一秒内大量的请求会被重定向,那就直接不能用了如果希望可以批量查询英语单词翻译,可以查看我的下一篇博客。接着我们上一讲Java如何用HaspMap统计次数并排序详解-ivanlee717-博......
  • Python网络爬虫技术详解与实战案例
    Python网络爬虫技术详解与实战案例引言网络爬虫(WebCrawler)是一种自动化程序,用于在互联网上收集数据。通过向网页发送HTTP请求,获取网页数据,然后提取和分析网页内容,网络爬虫能够实现数据收集、信息提取和数据分析等多种应用场景。Python作为一种功能强大且易于学习的编程语......
  • 基础 (map,pair的使用详解)/题目 两数之和 讲解 哈希表的使用
    力扣题目链接(opensnewwindow)https://leetcode.cn/problems/two-sum/给定一个整数数组nums 和一个目标值target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。示例:给......