JavaScript中闭包的概念与其造成的性能问题

news2024/12/25 1:33:14

如果一个函数没有访问任何外部作用域的变量或方法,那么它其实是拥有闭包特性的,因为在 JavaScript 中,每个函数都可以理解为对其创建时所处作用域的引用。从这个角度来说,即使普通函数本身并没有捕获外部变量,但它们仍然与词法作用域相关联,所以我们可以“轻微地”说它是闭包。但是它没有表达出闭包的特点,也就是访问父级作用域的变量,所以我们一般不把一个没有引用外部变量的函数称为是闭包的。

闭包的作用

封装变量

闭包可以用于创建私有变量和方法。内部函数可以访问外部函数中的变量,但外部作用域无法访问内部函数中的变量。这种机制可以用来创建封装的、私有的状态和行为。

function counter() {
    let count = 0;

    return {
        increment: function() {
            count++;
        },
        display: function() {
            console.log(count);
        }
    };
}

let counter1 = counter();
counter1.increment();
counter1.display(); // 输出: 1

class语法糖中的private私有变量也是用这种方式去定义的:

保存状态

闭包允许函数记住它被创建时的作用域。这对于在事件处理程序中保存状态或跟踪对象的状态非常有用。

function createCounter() {
    let count = 0;
    return function() {
        count++;
        console.log(count);
    };
}

let counter = createCounter();
counter(); // 输出: 1
counter(); // 输出: 2

「避免全局变量污染」

通过使用闭包,可以减少全局作用域中的变量污染。

(function() {
    let privateVar = 10;
    // 代码块内的变量对外部不可见
})();

// 这里无法访问 privateVar

存在的问题

内存泄漏

闭包中的变量不会被垃圾回收,即使函数执行完毕,它仍然保留对外部作用域的引用。如果闭包持有大量数据或者在循环中被频繁创建,可能导致内存泄漏。

function setupCounter() {
    let count = 0;
    let increment = function() {
        count++;
        console.log(count);
    };
    return increment;
}

let counter = setupCounter();
// 多次调用 setupCounter 后,并未释放相关资源

「避免方法」:在不再需要使用闭包的时候,手动将闭包变量设为null,以便释放其引用。此外,可以尽量减少闭包的创建,或者考虑在循环中使用闭包时,仔细处理变量的作用域和生命周期。

function setupCounter() {
    let count = 0;
    let increment = function() {
        count++;
        console.log(count);
    };
    let cleanUp = function() {
        count = null; // 手动释放引用
        increment = null; // 清空引用
    };
    // 在不再需要使用闭包时手动调用 cleanUp
    return {
        increment: increment,
        cleanUp: cleanUp
    };
}

意外的变量共享

function createIncrementFunctions() {
    let increments = [];
    for (var i = 0; i < 5; i++) {
        increments[i] = function() {
            console.log(i); // 意外共享变量 i
        };
    }
    return increments;
}
let myIncrements = createIncrementFunctions();
for (let j = 0; j < 5; j++) {
    myIncrements[j]();
}

在上面代码中,执行完createIncrementFunctions后,increments数组中的函数都形成了闭包,都能访问到createIncrementFunctions作用域下的变量i,然后这个i此时已经变为了5,所以在访问increments数组中存储的每个函数时,都访问到已经改变的变量i,要解决这个问题一般有两个方法:使用自执行函数或使用let定义变量i,本质上都是为了在每次为increments[i]赋值的时候创建一个新的作用域,让每个i对应的函数都能访问到自己作用域上的i,代码如下:

function createIncrementFunctions() {
    let increments = [];
    for (var i = 0; i < 5; i++) {
        increments[i] = (function(num) {
            return function() {
                console.log(num); // 通过立即执行函数保存变量的值
            };
        })(i);
    }
    return increments;
}

其实大家使用自执行函数纯粹是为了代码方便好看,改为普通函数写法如下:

function createIncrementFunctions() {
  let increments = [];
  for (var i = 0; i < 5; i++) {
    function fn(num) {
      return function () {
        console.log(num); // 通过立即执行函数保存变量的值
      };
    }
    increments[i] = fn(i)
  }
  return increments;
}

其实就是在利用fn返回的函数的闭包特性,让5次赋值每次都形成了num变量的引用,从而在内存中保留了5个不同的作用域。但是,如果我们在处理一组很大的数据,需要循环几十万次,那么岂不是要在内存中保留几十万个作用域?那势必会造成性能问题。

性能问题

function createFunctionsArray() {
  let functions = [];
  for (let i = 0; i < 100000; i++) {
    functions[i] = function () {
      // 一些逻辑
      console.log(i)
    };
  }
  return functions;
}
let funcs = createFunctionsArray();
funcs[3](); // 3

当我们在10w次的for循环中产生了闭包,浏览器中的JS使用内存将会非常大,我们可以右击Chrome标签栏打开任务管理器,就可以查看当前标签的性能指标

图片

那如果我们使用箭头函数来实现这个功能呢,还会有这么大的内存占用吗?笔者把Document111改为箭头函数实现

function createFunctionsArray() {
  let functions = [];
  function createFunction(value) {
    return () => {
      console.log(value);
    };
  }
  for (let i = 0; i < 100000; i++) {
    functions[i] = createFunction(i);
  }
  return functions;
}
let funcs = createFunctionsArray();

图片

可以看到内存占用还是那么大,因为箭头函数虽然没有闭包这个概念,但是他会保留当前的作用域,所以这个时候还是创建了10w个作用域。这个例子再次提醒我们,不管是否闭包,在JS中时刻都可能有内存泄露的风险,在进行大规模数据处理的时候一定要做好性能评估。

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

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

相关文章

JAVA集中学习第四周学习记录(四)

系列文章目录 第一章 JAVA集中学习第一周学习记录(一) 第二章 JAVA集中学习第一周项目实践 第三章 JAVA集中学习第一周学习记录(二) 第四章 JAVA集中学习第一周课后习题 第五章 JAVA集中学习第二周学习记录(一) 第六章 JAVA集中学习第二周项目实践 第七章 JAVA集中学习第二周学…

WPF篇(9)-CheckBox复选框+RadioButton单选框+RepeatButton重复按钮

CheckBox复选框 CheckBox继承于ToggleButton&#xff0c;而ToggleButton继承于ButtonBase基类。 案例 前端代码 <StackPanel Orientation"Horizontal" HorizontalAlignment"Center" VerticalAlignment"Center"><TextBlock Text"…

Redis的过期策略与内存淘汰机制详解

文章目录 Redis的过期策略1. 定时删除2. 惰性删除3. 定期删除 Redis的内存淘汰机制1. noeviction2. volatile-random3. volatile-ttl4. volatile-lru5. volatile-lfu6. allkeys-random7. allkeys-lru8. allkeys-lfu LRU与LFU算法总结 Redis作为一种高性能的键值对存储系统&…

C语言 之 理解指针(9)与指针相关的理解题

文章目录 代码1代码2代码3代码4代码5代码6代码7 本篇内容接上一篇&#xff0c;对指针进行一个更深入的理解 建议没看过 上一篇的可以看看上一篇&#xff0c;当然不看也可以。 建议先自行完成再看答案哟 代码1 #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, …

B站千亿级点赞系统服务架构设计

B站千亿级点赞系统服务架构设计 原文链接&#xff1a;https://www.bilibili.com/read/cv21576373/ 原文作者&#xff1a;哔哩哔哩技术团队-芦文超 点赞的功能太过于简单不再赘述&#xff0c;大家可以点击原文链接简单看下便可知晓。 本讲结合B站知名UP主陆总监的一期视频(h…

【vue3|第21期】Vue3中Vue Router的push和replace方法详解

日期&#xff1a;2024年8月9日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xff…

Android系统Android.bp文件详解

文章目录 1. 基本语法结构2. 常见模块类型3. 模块属性常见属性包括&#xff1a; 4. 具体示例5. 高级功能5.1. 条件编译5.2. 变量定义与使用5.3. 模块继承 6. 总结 Android.bp 是 Android 构建系统&#xff08;Android Build System&#xff09;中的配置文件&#xff0c;用于描述…

C语言典型例题31

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 习题2.8 请编写程序将China译为密码&#xff0c;密码的规律是&#xff1a;用原来字母后面的第4个字母代替原来的字母。 例如:C后面的4个字母是G&#xff0c;h后面第4个字母为l 代码&#xff1a; //《C程序设计教程…

TinyLLaVA: A Framework of Small-scale Large Multimodal Models

发表时间&#xff1a;22 Feb 2024 论文链接&#xff1a;https://arxiv.org/pdf/2402.14289 作者单位&#xff1a;SKLCCSE, Institute of Artificial Intelligence, Beihang University, Beijing, China Motivation&#xff1a;当前的大语言模型的参数量太大了&#xff0c;作…

flask学习-day1

介绍 django是大而全&#xff0c;flask是轻量级的框架 django提供非常多组件&#xff1a;orm/session/cookie/admin/form/modelform/路由/视图/模板/中间件/分页/auth/contentype/缓存/信号/多数据库连接 flask本身没有太多的功能&#xff1a;路由/试视图/模板/session/中间件…

【C++ 面试 - 基础题】每日 3 题(八)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

24/8/9算法笔记 决策树VS线性回归

from sklearn.tree import DecisionTreeRegressorfrom sklearn.linear_model import LinearRegressionfrom sklearn import datasetsfrom sklearn.model_selection import train_test_split import numpy as np X,y datasets.load_diabetes(return_X_yTrue)#糖尿病数据 X_tra…

03、MySQL-DQL(数据查询语言)

目录 1、编写顺序 2、基本查询 3、条件查询 4、聚合函数 5、分组查询 6、排序查询 7、分页查询 8、执行顺序 1、编写顺序 SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后条件列表 ORDER BY 排序字段列表 LIMIT 分页参数2、基本查…

Cesium初探-相机

在 Cesium 中&#xff0c;相机&#xff08;Camera&#xff09;是一个非常重要的概念&#xff0c;它代表了用户观察 3D 场景的视角。相机不仅决定了用户看到的内容&#xff0c;还定义了观察的角度、距离和方向。理解 Cesium 中的相机是如何工作的对于创建有效的 3D 地图和地球应…

关于Redis的面试题(一)

一、为什么要使用Redis 内存数据库&#xff0c;速度很快工作单线程worker&#xff0c;串行化&#xff0c;原子操作&#xff0c;IO线程是多线程的。避免上下文切换使用 IO模型&#xff0c;天生支撑高并发kv模型&#xff0c;v具有类型结构具有本地方法&#xff0c;计算数据移动二…

政府经济学(练习题)

政府经济学&#xff08;练习题&#xff09; 一、单项选择题 政府经济活动的主体是&#xff08; &#xff09;。 A.各级政府 B.各级人大 C.各级政协 D.各级党委政府经济的依据主要是&#xff08; &#xff09;。 A.私人财产所有权 B.社会公共权力 C.道德劝说 D.法律制度1776年亚…

通过指令深入了解Linux 3

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 文章目录 1.Linux下的基本指令1.1 more指令&#xff08;有更好的平替&#xff09;1.2 less指令1.3 head指令1.4 tail指令1.5 date指令…

二、Matlab图像处理基础

文章目录 一、Matlab图像处理工具箱二、图像文件的读取2.1 文件信息的读取2.2 图像文件的读取2.3 图像文件的保存2.4 图像文件的显示2.5 像素信息的显示 本章知识点总结 一、Matlab图像处理工具箱 在帮助文档可以搜索到图像处理工具箱的介绍 二、图像文件的读取 2.1 文件信息…

论文笔记:OneBit: Towards Extremely Low-bit Large Language Models

202402 arxiv 1 背景 模型量化主要通过把模型的线性层【nn.Linear】&#xff08;Embedding 层和 Lm_head 层除外&#xff09;转化为低精度表示实现空间压缩 此前工作的基础是利用 Round-To-Nearest&#xff08;RTN&#xff09;方法把高精度浮点数近似映射到附近的整数网格然而…

Sqlserver 创建只读权限用户

Sqlserver 创建只读权限用户 1、右键登录名->新建登录名 2、在常规选项卡中配置登录名、密码、数据库 3、服务器角色选项卡中配置只读角色 4、配置用户映射 5、安全对象选项卡和状态选项卡默认即可