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*&…

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;

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…

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

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

基于SpringBoot多数据源解决方案

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

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

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

穿心莲内酯(andrographolide)生物合成CYP72-文献精读106

Two CYP72 enzymes function as Ent-labdane hydroxylases in the biosynthesis of andrographolide in Andrographis paniculata 两种CYP72酶在穿心莲&#xff08;Andrographis paniculata&#xff09;中作为Ent-labdane羟化酶&#xff0c;在穿心莲内酯&#xff08;andrograp…

[SaaS] 内容创意生产平台

1.即梦 2.讯飞绘镜 typemovie 3.Krea.ai 4.Pika 5.runway 6.pixVerse 7.

DiffuEraser: 一种基于扩散模型的视频修复技术

视频修复算法结合了基于流的像素传播与基于Transformer的生成方法&#xff0c;利用光流信息和相邻帧的信息来恢复纹理和对象&#xff0c;同时通过视觉Transformer完成被遮挡区域的修复。然而&#xff0c;这些方法在处理大范围遮挡时常常会遇到模糊和时序不一致的问题&#xff0…

[c语言日寄]assert函数功能详解

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

【数据结构】_链表经典算法OJ:分割链表(力扣—中等)

目录 1. 题目描述及链接 2. 解题思路 2.1 思路1 2.2 思路2 2.3 思路3&#xff08;本题采取该解法&#xff09; 3. 题解程序 1. 题目描述及链接 题目链接&#xff1a;面试题 02.04. 分割链表 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给你一个链表…

基于vue和elementui的简易课表

本文参考基于vue和elementui的课程表_vue实现类似课程表的周会议列表-CSDN博客&#xff0c;原程序在vue3.5.13版本下不能运行&#xff0c;修改两处&#xff1a; 1&#xff09;slot-cope改为v-slot 2&#xff09;return background-color:rgb(24 144 255 / 80%);color: #fff; …

【Unity3D】实现Decal贴花效果,模拟战旗游戏地形效果

目录 一、基础版 二、Post Process 辉光Bloom效果 矩形渐隐 涉及知识点&#xff1a;Decal贴花、屏幕后处理Bloom、屏幕空间构建世界空间、ChracterController物体移动、Terrain地形创建 一、基础版 Unity 2019.4.0f1 普通渲染管线&#xff08;非URP、非HDRP&#xff09; UR…

数据结构与算法学习笔记----求组合数

数据结构与算法学习笔记----求组合数 author: 明月清了个风 first publish time: 2025.1.27 ps⭐️一组求组合数的模版题&#xff0c;因为数据范围的不同要用不同的方法进行求解&#xff0c;涉及了很多之前的东西快速幂&#xff0c;逆元&#xff0c;质数&#xff0c;高精度等…