JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康

这篇具有很好参考价值的文章主要介绍了JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康,javascript入门到实战,javascript,前端,开发语言

​🌈个人主页:前端青山
🔥系列专栏:JavaScript篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript-数据缓存与内存泄露

目录

说说你对事件循环的理解

一、是什么

二、宏任务与微任务

微任务

宏任务

三、async与await

async

await

四、流程分析

说说 JavaScript 中内存泄漏的几种情况?

一、是什么

二、垃圾回收机制

标记清除

引用计数

小结

三、常见内存泄露情况

说说你对事件循环的理解

JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康,javascript入门到实战,javascript,前端,开发语言

一、是什么

首先,JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

JavaScript中,所有的任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行

  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

同步任务与异步任务的运行流程图如下:

JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康,javascript入门到实战,javascript,前端,开发语言

从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环

二、宏任务与微任务

如果将任务划分为同步任务和异步任务并不是那么的准确,举个例子:

console.log(1)
​
setTimeout(()=>{
    console.log(2)
}, 0)
​
new Promise((resolve, reject)=>{
    console.log('new Promise')
    resolve()
}).then(()=>{
    console.log('then')
})
​
console.log(3)

如果按照上面流程图来分析代码,我们会得到下面的执行步骤:

  • console.log(1),同步任务,主线程中执行

  • setTimeout() ,异步任务,放到 Event Table,0 毫秒后console.log(2)回调推入 Event Queue

  • new Promise ,同步任务,主线程直接执行

  • .then ,异步任务,放到 Event Table

  • console.log(3),同步任务,主线程执行

所以按照分析,它的结果应该是 1 => 'new Promise' => 3 => 2 => 'then'

但是实际结果是:1=>'new Promise'=> 3 => 'then' => 2

出现分歧的原因在于异步任务执行顺序,事件队列其实是一个“先进先出”的数据结构,排在前面的事件会优先被主线程读取

例子中 setTimeout回调事件是先进入队列中的,按理说应该先于 .then 中的执行,但是结果却偏偏相反

原因在于异步任务还可以细分为微任务与宏任务

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

  • Promise.then

  • MutaionObserver

  • Object.observe(已废弃;Proxy 对象替代)

  • process.nextTick(Node.js)

宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

  • script (可以理解为外层同步代码)

  • setTimeout/setInterval

  • UI rendering/UI事件

  • postMessage、MessageChannel

  • setImmediate、I/O(Node.js)

这时候,事件循环,宏任务,微任务的关系如图所示

JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康,javascript入门到实战,javascript,前端,开发语言

按照这个流程,它的执行机制是:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中

  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

回到上面的题目

console.log(1)
setTimeout(()=>{
    console.log(2)
}, 0)
new Promise((resolve, reject)=>{
    console.log('new Promise')
    resolve()
}).then(()=>{
    console.log('then')
})
console.log(3)

流程如下

// 遇到 console.log(1) ,直接打印 1
// 遇到定时器,属于新的宏任务,留着后面执行
// 遇到 new Promise,这个是直接执行的,打印 'new Promise'
// .then 属于微任务,放入微任务队列,后面再执行
// 遇到 console.log(3) 直接打印 3
// 好了本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,发现 .then 的回调,执行它,打印 'then'
// 当一次宏任务执行完,再去执行新的宏任务,这里就剩一个定时器的宏任务了,执行它,打印 2

三、async与await

async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行

async

async函数返回一个promise对象,下面两种方法是等效的

function f() {
    return Promise.resolve('TEST');
}
​
// asyncF is equivalent to f!
async function asyncF() {
    return 'TEST';
}

await

正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值

async function f(){
    // 等同于
    // return 123
    return await 123
}
f().then(v => console.log(v)) // 123

不管await后面跟着的是什么,await都会阻塞后面的代码

async function fn1 (){
    console.log(1)
    await fn2()
    console.log(2) // 阻塞
}
​
async function fn2 (){
    console.log('fn2')
}
​
fn1()
console.log(3)

上面的例子中,await 会阻塞下面的代码(即加入微任务队列),先执行 async外面的同步代码,同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码

所以上述输出结果为:1fn232

四、流程分析

通过对上面的了解,我们对JavaScript对各种场景的执行顺序有了大致的了解

这里直接上代码:

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')

分析过程:

  1. 执行整段代码,遇到 console.log('script start') 直接打印结果,输出 script start

  2. 遇到定时器了,它是宏任务,先放着不执行

  3. 遇到 async1(),执行 async1 函数,先打印 async1 start,下面遇到await怎么办?先执行 async2,打印 async2,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码

  4. 跳到 new Promise 这里,直接执行,打印 promise1,下面遇到 .then(),它是微任务,放到微任务列表等待执行

  5. 最后一行直接打印 script end,现在同步代码执行完了,开始执行微任务,即 await下面的代码,打印 async1 end

  6. 继续执行下一个微任务,即执行 then 的回调,打印 promise2

  7. 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印 settimeout

所以最后的结果是:script startasync1 startasync2promise1script endasync1 endpromise2settimeout

说说 JavaScript 中内存泄漏的几种情况?

JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康,javascript入门到实战,javascript,前端,开发语言

一、是什么

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存

对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃

JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康,javascript入门到实战,javascript,前端,开发语言

C语言中,因为是手动管理内存,内存泄露是经常出现的事情。

     char * buffer;
buffer = (char*) malloc(42);
​
// Do something with buffer
​
free(buffer);

上面是 C 语言代码,malloc方法用来申请内存,使用完毕之后,必须自己用free方法释放内存。

这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为"垃圾回收机制"

二、垃圾回收机制

Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

通常情况下有两种实现方式:

  • 标记清除

  • 引用计数

标记清除

JavaScript最常用的垃圾收回机制

当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“

垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉

在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了

随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存

举个例子:                                                                                                                                                                         

引用计数

语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放

如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏

const arr = [1, 2, 3, 4];
console.log('hello world');

上面代码中,数组[1, 2, 3, 4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它还是会持续占用内存

如果需要这块内存被垃圾回收机制释放,只需要设置如下:

arr = null

通过设置arrnull,就解除了对数组[1,2,3,4]的引用,引用次数变为 0,就被垃圾回收了

小结

有了垃圾回收机制,不代表不用关注内存泄露。那些很占空间的值,一旦不再用到,需要检查是否还存在对它们的引用。如果是的话,就必须手动解除引用

三、常见内存泄露情况

意外的全局变量

function foo(arg) {
    bar = "this is a hidden global variable";
}

另一种意外的全局变量可能由 this 创建:

function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();

上述使用严格模式,可以避免意外的全局变量

定时器也常会造成内存泄露

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

如果id为Node的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource的引用,定时器外面的someResource也不会被释放

包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放

function bindEvent() {
  var obj = document.createElement('XXX');
  var unused = function () {
    console.log(obj, '闭包内引用obj obj不会被释放');
  };
  obj = null; // 解决方法
}

没有清理对DOM元素的引用同样造成内存泄露

const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用

包括使用事件监听addEventListener监听的时候,在不监听的情况下使用removeEventListener取消对事件监听文章来源地址https://www.toymoban.com/news/detail-776924.html

到了这里,关于JavaScript中的数据缓存与内存泄露:解密前端性能优化与代码健康的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 解密Redis:应对面试中的缓存相关问题2

    面试官:Redis集群有哪些方案,知道嘛? 候选人:嗯~~,在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群。 面试官:那你来介绍一下主从同步。 候选人:嗯,是这样的,单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从

    2024年02月14日
    浏览(10)
  • RSAUtil 前端 JavaScript JSEncrypt 实现 RSA (长文本)加密解密

    文章归档:https://www.yuque.com/u27599042/coding_star/cl4dl599pdmtllw1 import JSEncrypt from ‘jsencrypt’ import {stringIsNull} from “@/utils/string_utils.js”:https://www.yuque.com/u27599042/coding_star/slncupw7un3ce7cb import {isNumber} from “@/utils/number_utils.js”:https://www.yuque.com/u27599042/coding_star/tuwmm3ghf5lgo4bw 注意: 此方

    2024年04月22日
    浏览(12)
  • 计算机内存中的缓存Cache Memories

    这篇写一下计算机系统中的 缓存Cache 应用场景和实现方式介绍。 Memory hierarchy 在讲缓存之前,首先要了解计算机中的内存结构层次Memory hierarchy。也就是下图金字塔形状的结构。 从上到下,内存层次结构如下: 寄存器 :这是计算机中最快速的存储区域。它们位于处理器内,

    2024年02月15日
    浏览(8)
  • Jtti:Linux内存管理中的slab缓存怎么实现

    在Linux内存管理中,slab缓存是一种高效的内存分配机制,用于管理小型对象的内存分配。slab缓存的实现是通过SLAB分配器来完成的,它在Linux内核中对内存分配进行优化。 SLAB分配器将内存分为三个区域:slab、partial、和empty。 Slab区域: Slab区域用于保存完整的内存对象。当有

    2024年02月15日
    浏览(22)
  • 前端中的强缓存与协商缓存

    强缓存通常用于减少网络延迟,提高页面加载速度,适用于访问量较大且资源变化不频繁的情况。 协商缓存则适用于需要确保始终使用最新资源的情况,适用于资源变化频繁或需要根据用户个性化需求加载不同资源的情况。 常见配置 html配置为不缓存(nostore),其余静态资源配

    2024年02月19日
    浏览(16)
  • 机器学习中的数据泄露(Data Leakage)

    1.主要参考了这篇博客: (311条消息) Kaggle教程 机器学习中级7 数据泄露_李乾文的博客-CSDN博客 我觉得可以用一句不太严谨的大白话来解释就是: 数据泄露就是使用了未来信息(对于时序预测任务)。 数据泄露是指,在训练数据中包含目标信息,但在预测时没有可用的类似数

    2024年02月16日
    浏览(9)
  • 加密越来越简单——用JavaScript实现数据加密和解密

    在当今互联网的世界中,安全性越来越受到关注,数据加密成为了必不可少的一环。Javascript作为前端开发的主要语言之一,也有着重要的加密应用。本篇博客将讨论Javascript加密的概念、常用算法以及代码示例。 Javascript加密 ,简单来说就是通过Javascript实现数据的加密和解密

    2024年02月15日
    浏览(11)
  • JavaScript + GO 通过 AES + RSA 进行数据加解密

    浏览器端搞些小儿科的加密,就好比在黑暗夜空中,点缀了几颗星星,告诉黑客「这里有宝贵信息,快来翻牌」 浏览器端的加密,都是相对安全的。 它的具体安危,取决于里面存在的信息价值,是否值得破解者出手一试。 就跟那个经典的笑话一样: 某个客户自己开发了一套

    2024年02月14日
    浏览(13)
  • 【JavaScript】3.4 JavaScript在现代前端开发中的应用

    JavaScript 是现代前端开发的核心。无论是交互效果,还是复杂的前端应用,JavaScript 都发挥着关键作用。在本章节中,我们将探讨 JavaScript 在现代前端开发中的应用,包括如何使用 JavaScript 来处理用户交互、动态内容、前端路由、API 请求等。 JavaScript 是处理用户交互的主要工

    2024年02月04日
    浏览(15)
  • 初识 Redis - 分布式,内存数据存储,缓存

    目录 1. 什么是 Redis 1.1 Redis 内存数据存储 1.2 Redis 用作数据库 1.3 Redis 用作缓存 (cache) 1.4 用作消息中间件 The open source , in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker. 开源的 内存数据存储 ,被数百万开发人员用作 数据库、缓存、流引擎

    2024年02月15日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包