JS宏进阶:闭包与代理

在JavaScript中,闭包和代理是两种重要的概念,它们各自具有独特的功能和用途。闭包,它指的是一个函数能够访问并操作其父函数作用域中的变量,即使父函数已经执行完毕。它允许内部函数访问外部函数的变量,从而提供了强大的功能。而代理(Proxy)则是另一种高级功能,它允许开发者定义基本操作的自定义行为,如属性查找、赋值、枚举、函数调用等。

一、闭包

1、定义

闭包是一个函数及其相关的引用环境的组合。在JavaScript中,当一个函数在另一个函数的内部定义,并且这个内部函数引用了外部函数的变量时,就形成了一个闭包。即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的变量。

2、工作原理

2.1、词法作用域

词法作用域(也称为静态作用域),JavaScript采用这种方式就意味着函数的作用域在函数定义时就已经确定,而不是在函数执行时确定。

当一个函数在另一个函数内部定义时,内部函数会捕获外部函数的词法作用域,包括外部函数的参数和变量。这样,就实现访问外部函数变量或参数的目的。

2.2、执行上下文和作用于链

当一个函数被调用时,JavaScript引擎会为其创建一个执行上下文,并构建作用域链。作用域链是一个对象列表,用于解析函数中的变量。而内部函数的作用域链包含了外部函数的作用域,因此内部函数可以访问外部函数的变量。

2.3、闭包的持久化

由于内部函数持有了对外部函数作用域的引用,即使外部函数执行完毕,这些作用域也不会被销毁。只要内部函数存在,外部函数的变量就会保留在内存中,从而实现“持久化”。

3、闭包使用实例

function createCounter(){let count = 0;return function() {count++;return count;};
}let counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2

上述示例代码是在本专栏第一张中的闭包函数示例,当时主要介绍函数,并没有深入分析他的原理。现在我们理解了闭包的定义,相信你能很轻松的解释其中原理:

createCounter是一个外部函数,它定义了一个局部变量count,随后,内部返回一个匿名函数,在这个函数中,访问并更改了外部函数的变量count,随后将其作为返回值返回给用户。当createCounter()执行后就会将其赋值给counter,由于 createCounter的返回值是一个函数,所以counter也是一个函数,而且该函数的作用于在createCounter里面,所以每次调用counter时,createCounter里面的变量count都会被修改(这里是递增)。

4、闭包的应用场景

4.1、数据的封装和隐藏
function Counter() {var count = 0; // 私有变量function increment() {count++;console.log(count);}return {increase: increment, // 返回的对象通过increment方法访问count,形成闭包};
}var counter = new Counter();
counter.increase(); // 输出: 1

闭包可以用来模拟类的私有属性和方法。通过在构造函数中定义并返回一个对象,该对象的方法利用闭包访问构造函数内部的私有变量。

4.2、回调函数和事件处理器

JS中,回调函数和事件处理器经常需要访问外部函数的变量。闭包使得这些函数即使在异步执行时也能访问外部变量。例如,new XMLHttpRequest 对象中,有一个事件函数onreadystatechange函数,在使用它时就有可能利用到闭包:

var xhr = new XMLHttpRequest();
var count = 0;xhr.open('GET', 'https://xxx.xxx.com/api', true);xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {count++; // 访问外部变量var innerVariable = "Response received";console.log(innerVariable);}
};xhr.send();

再如,在数组的forEach、map等高级函数中利用闭包

const numbers = [1, 2, 3, 4, 5];function createCounter() {let count = 0; // 私有变量return function() {count++;return count;};
}const counter = createCounter();numbers.forEach(function(number) {console.log(`Number: ${number}, Count: ${counter()}`);
});
4.3、数据缓存

利用闭包存储先前计算的结果,避免重复计算,提高性能。

function expensiveCalculation(value) {let cache = {};return function cachedCalculation(newValue) {if (newValue in cache) {return cache[newValue];} else {let result = performExpensiveOperation(newValue); // 假设这是一个耗时的操作cache[newValue] = result;return result;}};
}let memoizedCalc = expensiveCalculation();
memoizedCalc(10); // 第一次计算
memoizedCalc(10); // 第二次从缓存中获取结果

二、代理

1、定义

JS中代理是ES6(ECMAScript 2015)中引入的一个新特性。它提供了一种机制,通过该机制可以拦截和修改对目标对象的访问和操作。

2、工作原理

代理对象充当了一个“中间人”的角色,它拦截了对目标对象的操作,并在操作执行前后添加自定义的行为。代理对象通过构造函数Proxy来创建,该构造函数接受两个参数:目标对象和一个处理器对象(称为handler)。

3、代理对象简介

3.1、创建语法

let proxy = new Proxy(target, handler);

target:被代理的目标对象。

handler:定义代理行为的对象。处理器对象中可以定义各种陷阱函数(trap),用于拦截对目标对象的操作。

3.2、处理器对象handler介绍

处理器对象中可以定义多种陷阱函数,每种陷阱函数对应一种基本操作。如下表所示(标红是常用的):

陷阱函数描述参数
get(target, propKey, receiver)拦截对目标对象属性的读取操作。

target:目标对象。

propKey:要读取的属性名。

receiver:最初接收调用的对象(通常是Proxy实例本身)。

set(target, propKey, value, receiver)拦截对目标对象属性的设置操作。

target:目标对象。

propKey:要设置的属性名。

value:要设置的新属性值。

receiver:最初接收调用的对象(通常是Proxy实例本身)。

has(target, propKey)拦截对目标对象属性的存在性检查(使用in操作符)。

target:目标对象。

propKey:要检查的属性名。

deleteProperty(target, propKey)拦截对目标对象属性的删除操作(使用delete操作符)。

target:目标对象。

propKey:要删除的属性名。

apply(target, thisArg, argumentsList)拦截对目标函数的调用操作。

target:目标函数。

thisArg:函数调用时使用的this值。

argumentsList:函数调用时传递的参数列表(类数组对象)。

construct(target, argumentsList, newTarget)拦截使用new操作符创建目标对象的实例的操作。

target:目标构造函数。

argumentsList:调用new时传递的参数列表(类数组对象)。

newTarget:最初接收调用的构造函数(通常是Proxy实例本身)。

getOwnPropertyDescriptor(target, propKey)拦截对目标对象属性的Object.getOwnPropertyDescriptor方法的调用。

target:目标对象。

propKey:要获取描述符的属性名。

defineProperty(target, propKey, descriptor)拦截对目标对象属性的Object.defineProperty方法的调用。

target:目标对象。

propKey:要定义的属性名。

descriptor:属性描述符对象。

getPrototypeOf(target)拦截对目标对象的Object.getPrototypeOf方法的调用。target:目标对象。
setPrototypeOf(target, proto)拦截对目标对象的Object.setPrototypeOf方法的调用。

target:目标对象。

proto:要设置的新原型。

isExtensible(target)拦截对目标对象的Object.isExtensible方法的调用。target:目标对象。
preventExtensions(target)拦截对目标对象的Object.preventExtensions方法的调用。target:目标对象。
getOwnPropertyNames(target)拦截对目标对象的Object.getOwnPropertyNames方法的调用。target:目标对象。
getOwnPropertySymbols(target)拦截对目标对象的Object.getOwnPropertySymbols方法的调用。target:目标对象。

4、代理的应用场景

数据验证:可以拦截赋值操作,并在赋值前检查值的类型和范围等。确保数据的准确性

懒加载:也就是延迟加载,当需要访问某个属性时才加载该属性的值。这可以减少初始加载时间,提高应用性能。

对象封装:它可以提供统一的访问接口,隐藏对象的内部实现细节。例如,可以拦截对目标对象的所有操作,并在操作执行前后添加日志记录或权限检查。

日志:用于记录对对象的所有操作,以便进行调试和监控。通过拦截和记录对对象的各种操作,可以方便地跟踪对象的状态变化。

数据绑定:代理可以用于实现数据绑定。当数据对象的属性发生变化时,可以自动更新到界面上,提高开发效率和代码的可维护性。

权限控制:用于控制对对象的访问权限。例如,可以拦截对目标对象的属性读取操作,并根据用户的权限决定是否允许读取。(在JS宏中没啥用,因为允不允许访问,数据始终在表格里面,安全性不高)

5、示例代码

let target = {name: 'Alice',age: 25
};let handler = {get(target, prop, receiver) {console.log(`Reading ${prop}`);return Reflect.get(target, prop, receiver);},set(target, prop, value, receiver) {console.log(`Writing ${prop}`);return Reflect.set(target, prop, value, receiver);}
};let proxy = new Proxy(target, handler);console.log(proxy.name); // 输出: Reading name, Alice
proxy.age = 30; // 输出: Writing age
console.log(proxy.age); // 输出: Reading age, 30

上述是一个简单的代理示例,展示了如何拦截和修改对目标对象的属性读取和赋值操作。

三、闭包与代理的比较

闭包代理
定义能够访问另一个函数作用域中的变量的函数拦截对象上的操作,并在它们被执行之前或之后执行自定义代码的对象
特点函数嵌套函数,访问外部变量,变量持久化拦截对象操作,自定义行为
优点封装数据,避免全局变量污染,实现私有方法和变量增强对象功能,数据验证,日志记录
缺点内存消耗,性能问题性能开销
应用场景数据隐藏与封装,工厂函数,模拟私有方法和变量数据验证与格式化,访问控制,事件监听

四、总结

闭包和代理是JavaScript中两种强大的特性,它们各自具有独特的功能和用途。闭包主要用于数据隐藏与封装、工厂函数以及模拟私有方法和变量等场景;而代理则更适用于增强对象功能、数据验证、日志记录以及访问控制等场景。在实际开发中,可以根据具体需求选择合适的特性来实现所需的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/893948.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【1.安装ubuntu22.04】

目录 参考文章链接电脑参数安装过程准备查看/更改引导方式查看/更改磁盘的分区格式关闭BitLocker加密压缩分区关闭独显直连制作Ubuntu安装盘下载镜像制作启动盘 进入BIOS模式进行设置Secure Boot引导项顺序try or install ubuntu 进入安装分区启动引导器个人信息和重启 参考文章…

代码随想录算法【Day34】

Day34 62.不同路径 思路 第一种&#xff1a;深搜 -> 超时 第二种&#xff1a;动态规划 第三种&#xff1a;数论 动态规划代码如下&#xff1a; class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m, vector<int>(n,…

计算机毕业设计PySpark+hive招聘推荐系统 职位用户画像推荐系统 招聘数据分析 招聘爬虫 数据仓库 Django Vue.js Hadoop

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

强化学习数学原理(三)——迭代算法

一、值迭代过程 上面是贝尔曼最优公式&#xff0c;之前我们说过&#xff0c;f(v)v&#xff0c;贝尔曼公式是满足contraction mapping theorem的&#xff0c;能够求解除它最优的策略和最优的state value&#xff0c;我们需要通过一个最优v*&#xff0c;这个v*来计算状态pi*&…

【机器学习】自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测

一、使用pytorch框架实现逻辑回归 1. 数据部分&#xff1a; 首先自定义了一个简单的数据集&#xff0c;特征 X 是 100 个随机样本&#xff0c;每个样本一个特征&#xff0c;目标值 y 基于线性关系并添加了噪声。将 numpy 数组转换为 PyTorch 张量&#xff0c;方便后续在模型中…

AI 浪潮席卷中国年,开启科技新春新纪元

在这博主提前祝大家蛇年快乐呀&#xff01;&#xff01;&#xff01; 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间&#xff0c;AI 技术也展现出了巨大的潜力&#xff0c;为中国年带…

vim的特殊模式-可视化模式

可视化模式&#xff1a;按 v进入可视化模式 选中 y复制 d剪切/删除 可视化块模式: ctrlv 选中 y复制 d剪切/删除 示例&#xff1a; &#xff08;vim可视化模式的进阶使用&#xff1a;vim可视化模式的进阶操作-CSDN博客&#xff09;

【微服务与分布式实践】探索 Eureka

服务注册中心 心跳检测机制&#xff1a;剔除失效服务自我保护机制 统计心跳失败的比例在15分钟之内是否低于85%&#xff0c;如果出现低于的情况&#xff0c;Eureka Server会将当前的实例注册信息保护起来&#xff0c;让这些实例不会过期。当节点在短时间内丢失过多的心跳时&am…

sunrays-framework配置重构

文章目录 1.common-log4j2-starter1.目录结构2.Log4j2Properties.java 新增两个属性3.Log4j2AutoConfiguration.java 条件注入LogAspect4.ApplicationEnvironmentPreparedListener.java 从Log4j2Properties.java中定义的配置读取信息 2.common-minio-starter1.MinioProperties.…

相互作用感知的蛋白-小分子对接模型 - Interformer 评测

Interformer 是一个应用于分子对接和亲和力预测的深度学习模型&#xff0c;基于 Graph-Transdormer 架构的模型&#xff0c;利用相互作用&#xff08;氢键、疏水&#xff09;感知的混合密度网络&#xff08;interaction-aware mixture den sity network&#xff0c; MDN&#x…

Ceisum无人机巡检直播视频投射

接上次的视频投影&#xff0c;Leader告诉我这个视频投影要用在两个地方&#xff0c;一个是我原先写的轨迹回放那里&#xff0c;另一个在无人机起飞后的地图回显&#xff0c;要实时播放无人机拍摄的视频&#xff0c;还要能转镜头&#xff0c;让我把这个也接一下。 我的天&#x…

【方法论】ChatGPT与DeepSeek的联合应用,提升工作效率的新解决方案

标题&#xff1a;ChatGPT与DeepSeek的联合应用&#xff0c;提升工作效率的新解决方案 【表格】ChatGPT与DeepSeek联合应用流程 阶段工具主要任务优势备注初稿生成ChatGPT基于用户输入生成初步内容高效、快速生成内容&#xff0c;适应多种主题适合生成长篇文章、报告、分析等验…

第29篇:Python开发进阶:数据库操作与ORM

第29篇&#xff1a;数据库操作与ORM 目录 数据库操作概述 什么是数据库数据库管理系统&#xff08;DBMS&#xff09;常见的数据库类型 Python中的数据库操作 使用sqlite3模块连接到MySQL数据库连接到PostgreSQL数据库 SQL基础 数据库基本操作表的创建与管理数据的插入、查询、…

庆祝2025到来:C++编程的新篇章

作者&#xff1a;w(&#xff9f;Д&#xff9f;)w吓洗宝宝了 发布时间&#xff1a;2025年1月19日00:00 引言 新年伊始&#xff0c;万象更新。在这充满希望的2025年&#xff0c;我们迎来了新的机遇和挑战。作为C编程爱好者的一员&#xff0c;我感到无比激动和自豪。C作为一种强…

2024年终总结——今年是蜕变的一年

2024年终总结 摘要前因转折找工作工作的成长人生的意义 摘要 2024我从国企出来&#xff0c;兜兜转转还是去了北京&#xff0c;一边是工资低、感情受挫&#xff0c;一边是压力大、项目经历少&#xff0c;让我一度找不到自己梦寐以求的工作&#xff0c;我投了一家又一家&#xff…

【漫话机器学习系列】065.梯度(Gradient)

梯度&#xff08;Gradient&#xff09; 在数学和机器学习中&#xff0c;梯度是一个向量&#xff0c;用来表示函数在某一点的变化方向和变化率。它是多变量函数的一阶偏导数的组合。 梯度的定义 设有一个标量函数 &#xff0c;它对 ​ 是可微的&#xff0c;则该函数在某一点的…

Python 合并 Excel 单元格

合并 Excel 单元格是 Excel 数据处理和表格设计中的一项常用操作。例如&#xff0c;在制作表格标题时&#xff0c;经常会将多个单元格合并&#xff0c;使标题能够跨列显示&#xff0c;更加醒目和美观。此外&#xff0c;当对数据进行分类时&#xff0c;为了使同一类别的数据在视…

基于SpringBoot多数据源解决方案

最近在学习SpringBoot的时候&#xff0c;需要同时用两个不同的数据库连接服务&#xff0c;在网上学习了之后&#xff0c;下文以连接一个MySQL数据库和一个SqlServer数据库为例。 配置数据源连接信息 在配置文件中&#xff0c;配置对应的数据库连接信息&#xff0c;相比于单数…

LLM - 大模型 ScallingLaws 的指导模型设计与实验环境(PLM) 教程(4)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/145323420 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 Scaling Laws (缩放法则) 是大模型领域中,用于描述 模型性能(Loss) 与…

二叉树的最大深度(C语言详解版)

一、摘要 嗨喽呀大家&#xff0c;leetcode每日一题又和大家见面啦&#xff0c;今天要讲的是104.二叉树的最大深度&#xff0c;思路互相学习&#xff0c;有什么不足的地方欢迎指正&#xff01;好啦让我们开始吧&#xff01;&#xff01;&#xff01; 二、题目简介 给定一个二…