js加密后是如何运行的,js显示密码与隐藏密码

大家好,小编为大家解答js加密后是如何运行的的问题。很多人还不知道js显示密码与隐藏密码,现在让我们一起来看看吧!

你有没有在面试中被问过这样的问题:如果网页卡顿,你认为可能是什么原因?有什么办法可以找到原因并解决吗?

这是一个广泛而深入的问题,其中涉及到很多页面性能优化问题。我还记得在面试中被问到这个问题时,我是如何回答的:

  1. 首先,检查网络请求是否过多,导致数据传输变慢,这个问题可以通过缓存来优化Deepl降重

  2. 也有可能是某个资源的bundle太大,我们可以考虑拆分。

  3. 检查我们的 JavaScript 代码,看看是否有太多循环在主线程上花费了太长时间。

  4. 也许是浏览器在某个帧中渲染了太多东西。

  5. 在页面渲染过程中,可能会出现大量的重复回流和重绘。

  6. 也许还有别的,我不知道……

后来,我意识到页面长时间的感觉卡顿也可能是内存泄漏造成的,在本文中,我将与您一起来讨论这个话题。

什么是内存泄漏?

内存泄漏,是由于疏忽或某些程序错误而无法释放不再使用的内存。简单来说,如果一个变量消耗了 100M 的内存,而你不需要它,但是,没有手动或自动释放它,它仍然会消耗 100M 的内存。这是内存浪费或内存泄漏。

堆栈内存和堆内存

JavaScript 内存分为栈内存和堆内存,栈内存用于简单的变量,堆内存用于复杂的对象。

简单变量是指原始数据类型,例如 String、Number、Boolean、Null、Undefined、Symbol、Bigint。

复杂对象是指引用数据类型,如Object、Array、Function…

JavaScript 中的垃圾回收

根据内存泄漏的定义,一些变量或数据不再使用或不再需要,那么,它就是垃圾变量或垃圾数据。如果它保存在内存中,最终会导致内存使用过多。这时候,我们就需要回收这些垃圾数据。这里,我们介绍一下垃圾回收机制的概念。

垃圾收集机制分为两类:手动和自动。

C和C++使用手动回收机制,即开发者先通过代码手动为某个变量分配一定的内存,当不需要时,开发者会使用手动注释代码释放内存。

另一方面,JavaScript 使用自动收集,这意味着我们不关心我们为变量分配了多少内存或何时释放内存,因为这都是自动的。但这并不意味着我们不需要关心内存管理!否则,将不会出现本文中讨论的内存泄漏。

接下来,让我们讨论一下 JavaScript 垃圾回收机制。

一般来说,全局变量不会自动回收,所以我们将重点关注局部作用域的内存收集。

这是一个代码片段:

function fn1 () {    let a = {        name: 'bytefish'    }
    let b = 3
    function fn2() {        let c = [1, 2, 3]    }
    fn2()
    return a}let res = fn1()

上述代码的调用栈如下图所示:

左图侧是栈空间,用于存储执行上下文和原始类型数据。右边是堆空间,用来存放对象。

当代码执行到 fn2() 时,调用栈中的执行上下文从上到下为 fn2 函数执行上下文 => fn1 函数执行上下文 => 全局执行上下文。

当函数 fn2 完成其内部执行时,是时候通过向下移动箭头来退出 fn2 执行上下文了。fn2执行上下文被清除,堆栈内存空间被释放,如下图所示:

函数fn1内部执行完成后,是时候退出fn1函数执行上下文了,也就是箭头再次向下移动。此时,fn1函数执行上下文会被清除,相应的栈内存空间也会被释放,如图:

此时,我们的程序处于全局执行上下文中。

JavaScript 垃圾收集器,每隔一段时间就会遍历调用堆栈并收集垃圾。假设,此时触发了垃圾回收机制。

垃圾收集器在遍历调用栈时,发现变量b和c都没有被使用,因此,判断为垃圾数据,并进行标记。

因为 fn1 函数执行后返回变量 a 并将其存储在全局变量 res 中,所以将其标识为活动数据并进行相应标记。

在空闲的时候,所有被标记为垃圾数据的变量都会被清除掉,以释放相应的内存,如图:

这是一个快速的总结:

  1. JavaScript 的垃圾收集机制是自动化的,标签用于识别和清理垃圾数据。

  2. 离开局部作用域后,如果该作用域内的变量没有被外部作用域引用,稍后会被清除。

使用 Chrome DevTools 观察内存使用情况

使用 Chrome DevTools 的 Performance 和 Memory 面板,我们可以观察到 JavaScript 应用程序的 Memory 使用情况,这让我们对内存管理机制有了更深入的了解。

首先,让我们准备一个简单的 JavaScript 程序:

<!DOCTYPE html><html>  <body>    <button onclick="myClick()">execute fn1</button>    <>      function fn1() {        let a = new Array(10000);        return a;      }
      let res = [];
      function myClick() {        res.push(fn1());      }</>  </body></html>

这个页面很简单:页面上只有一个按钮,每次我们点击这个按钮,我们的程序都会创建一个新的数组,并存储在数组res中。

在计算机上创建此文件,然后,复制文件地址并在 Chrome 浏览器中打开该文件:

这一步注意:一定要使用文件地址直接打开文件,不要使用VSCode或其他IDE Live Server功能打开文件。后者将热更新代码插入文件中,使我们的内存观察不准确。如有必要,您还应暂时禁用浏览器的扩展,以免干扰我们后续的测试。

然后,我们打开浏览器 DevTools 的 Performance 面板:

这个面板有很多功能。上面的灰点是记录程序内存使用情况的按钮。单击点开始录制,然后,单击按钮重复执行 fn1 得到如下内容:

结果:

下面的折线图是堆内存使用情况。我们可以看到,每次点击按钮,堆内存使用量都会增加,因为,我们的函数 fn1 新建了一个对象,而这个对象存储在数组 res 中,并没有被垃圾回收器回收。

如果折线图呈上升趋势且没有回调迹象,则程序在不断消耗内存,程序很可能出现内存泄漏。

内存面板

内存面板允许我们实时查看内存使用情况。

用法:

我们开始录制后,我们可以看到在图的右侧生成了一个蓝色的直方图,它代表了当前时间轴下的内存量。

或者我们可以打开Medium的主页,用同样的方法记录直方图。

在这里,我们可以看到图中起伏的蓝色和灰色条形图。灰色代表之前占用的内存空间已经被清除和释放。

如果在你的项目中只生成了蓝色的直方图并且从未变灰,那么内存从未被释放,你的程序可能存在内存泄漏。

内存泄漏示例

那么,发生内存泄漏的情况有哪些呢?以下是一些常见的:

  • 闭包使用不当

  • 意外生成的全局变量

  • 分离的 DOM 节点

  • 控制台打印

  • 未清零的定时器

接下来,遍历场景并尝试使用前面描述的 Chrome DevTools 捕获问题。

1.闭包使用不当

在本文开头的示例中,退出 fn1 函数执行上下文后,该上下文中的变量 a 将被作为垃圾数据收集。但是因为fn1函数最终返回变量a并赋值给全局变量res,它产生了对变量a值的引用,所以变量a的值被标记为active,一直在占用相应的内存。假设变量 res 以后不再使用,这是一个未正确使用闭包的示例。

下面我们来看看使用Performance和Memory的闭包导致的内存泄漏问题。为了使内存泄漏的结果更加明显,我们对本文开头的示例稍作改动。代码如下所示:

<!DOCTYPE html><html>  <body>    <button onclick="myClick()">execute fn1</button>    <>      function fn1() {        let a = new Array(10000);
        let b = 3;
        function fn2() {          let c = [1, 2, 3];        }
        fn2();
        return a;      }
      let res = [];
      function myClick() {        res.push(fn1());      }</>  </body></html>

我们在这个页面上设置了一个按钮,每次点击都会将fn1函数的返回值添加到全局变量res中。接下来,我们可以在 Performance 面板中记录内存曲线。

  • 首先,我们点击灰点开始录制。

  • 然后,我们手动进行垃圾回收,以确保我们有一个稳定的初始内存基线。

  • 接着,我们点击几次按钮执行fn1函数。

  • 最后,我们再次进行垃圾回收。

操作如下:

结果:

从结果可以看出:每次调用函数 fn1 后,堆内存空间增加,整体曲线逐步增加。那么,经过最后一次内存清理后,我们可以发现最终的曲线高度高于基线,说明程序中可能存在内容泄漏。

当已知存在内存泄漏时,我们可以使用“内存”面板更明确地识别和定位问题。

每点击一次按钮,动态内存分配图上就会出现一个蓝条,而我们触发垃圾回收后,蓝条没有变成灰条,说明分配的内存还没有被清除。

然后,我们可以使用堆快照来找出导致内存泄漏的函数。

通过将光标移动到蓝色列上,我们可以看到在此期间创建的对象。然后,您可以单击对象以查看创建该对象的函数。这个函数是内存泄漏的罪魁祸首。

2.意外生成的全局变量

我在本文开头提到,全局变量一般不会被垃圾收集器收集。如果没有必要,我们应该尽可能少地使用全局变量。有时开发者不经意间把一些变量丢给了全局世界,比如给一个变量赋值而不声明它,导致它被全局创建。

示例代码如下:

function fn1() {   // `name` is not declared   name = new Array(99999999)}fn1()

在这种情况下,变量name是全局自动创建,并为该名称分配一个大数组。而且因为是全局变量,内存空间永远不会被释放。

所以,你在日常编码中需要多加注意,不要在变量声明之前赋值。或者我们可以开启严格模式,这样当我们在不知不觉中犯错时,就会收到错误警告,例如:

function fn1() {    'use strict';    name = new Array(99999999)}fn1()

3.分离的DOM节点

假设您手动删除了一个 DOM 节点。您应该已经释放了 DOM 节点的内存,但不知何故,某些代码仍然引用了已删除的节点,并且无法释放内存。例如:

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <div id="root">    <div class="child001">I am child element</div>    <button>remove</button></div><>    let btn = document.querySelector('button')    let child001 = document.querySelector('.child001')    let root = document.querySelector('#root')
    btn.addEventListener('click', function() {        root.removeChild(child001)    })</>
</body></html>

单击按钮后,此代码将删除节点 .child001 的内容。虽然节点确实在点击后从dom中移除了,但是全局变量child001仍然有对节点的引用,所以节点的内存并没有被释放。

我们可以使用 Memory 面板对其进行测试:

我们首先在程序开始使用Heap Snapshot功能记录Heap内存使用情况,然后我们点击按钮移除.child001 DOM元素,再次记录Heap内存使用情况。

如果我们在第二个快照中搜索 detached 关键字,我们可以过滤掉从 DOM 树中分离但没有被移除的 DOM 节点。然后我们确实找到了元素.child001,这意味着该元素还没有被垃圾收集器回收。

这也是内存泄漏的常见场景。解决方案如下图所示:

let btn = document.querySelector("button");btn.addEventListener("click", function () {    let child001 = document.querySelector(".child001");    let root = document.querySelector("#root");    root.removeChild(child001);});

更改就像将 .child001 节点的引用移动到点击事件的回调函数一样简单。那么,当我们移除节点并退出回调函数的执行时,对节点的引用会自动清除,不会出现内存泄漏。

让我们验证一下:

结果是我们在第二个堆快照中再也找不到这个元素了,说明它已经被收集了。我们成功解决了内存泄漏问题。

4.控制台打印

控制台打印是否也会导致内存泄漏?是的,如果浏览器并不总是存储有关我们打印的对象的信息,为什么我们每次打开控制台都能看到具体的数据?我们来看一下测试代码:

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <button>btn</button><>    document.querySelector('button').addEventListener('click', function() {        let obj = new Array(1000000)
        console.log(obj);    })</>
</body></html>

我们在点击按钮的回调事件中创建一个大数组对象并打印出来。然后让我们记录一下。

录制开始时,会触发垃圾收集以确定内容的基线。然后多次点击按钮,最后再次触发垃圾回收。查看记录结果,我们看到 Heap 内存曲线逐渐上升并最终保持在远高于初始基线,这意味着每次点击创建的大数组对象 obj 被浏览器保存并且无法收集,因为 控制台日志。

接下来,删除 console.log 并查看结果:

动图:

结果:

可以看到,没有console.log,每次创建obj,都会立即销毁。最后,触发垃圾回收时,新的内存线与原来的baseline高度一致,说明没有内存泄漏。

同样,我们可以使用 Memory 再次验证这一点:

使用console.log:

没使用 console.log

快速总结:在开发环境中,您可以使用控制台打印变量以进行调试,但在生产环境中,尽量不要从控制台打印数据。所以很多 JavaScript 编码风格规范要求我们不要使用 console.log。

如果你真的想打印一个变量,你可以写:

if(isDev) {    console.log(obj)}

这避免了生产中不必要的变量打印,以及console.log、console.error、console.info、console.dir等,这些也不应该在生产中使用。

5. 未清零的定时器

如果未清除计时器,定义计时器也会导致内存泄漏。

看一个代码示例:

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <button>start a timer</button><>
    function fn1() {        let largeObj = new Array(100000)
        setInterval(() => {            let myObj = largeObj        }, 1000)    }
    document.querySelector('button')      .addEventListener('click', function() {        fn1()    })</>
</body></html>

我们点击按钮后执行函数fn1,函数fn1创建一个大数组largeObj,同时,它创建了一个setInterval定时器。定时器的回调只是简单地引用了 largeObj,所以我们来看看它的整体内存分配情况:

点击按钮会执行fn1函数,然后退出函数的执行上下文,函数体中的局部变量应该被清除。但是,图中的记录结果显示,似乎存在内存泄漏,即最终曲线高度高于基线高度。

所以再次使用Memory来确认:

点击按钮后,我们在动态内存分配图中看到一个蓝条,表示浏览器为变量 largeObj 分配了一块内存。但是这块内存后来没有被释放,说明确实存在内存泄漏。

原因是 setInterval 回调中引用了 largeObj,而定时器还没有被清空,所以 largeObj 的内存没有被释放。

我们如何解决这个问题?假设我们只需要让定时器执行3次,我们可以改一下代码:

<body>    <button>start a timer</button>    <>      function fn1() {        let largeObj = new Array(100000);        let index = 0;
        let timer = setInterval(() => {          if (index === 3) clearInterval(timer);          let myObj = largeObj;          index++;        }, 1000);      }
      document.querySelector("button").addEventListener("click", function () {        fn1();      });</>  </body>

然后让我们测试一下:

从这段记录的结果可以看出,最终曲线的高度与初始基线的高度相同,说明没有内存泄漏。

结论

在开发项目的过程中,如果遇到一些可能与内存泄漏有关的性能问题,可以参考本文列出的五种情况进行排查,一定能找到问题并给出解决方案。

虽然 JavaScript 垃圾回收是自动的,但我们有时需要考虑是否手动清除某些变量的内存。例如,如果您知道某个变量在某些情况下不再需要,但它会被外部变量引用,因此无法释放内存,您可以将 null 分配给该变量,以便在后续垃圾回收时释放内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值