温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JS前端的内存处理的方法是什么

发布时间:2023-04-11 15:07:46 来源:亿速云 阅读:133 作者:iii 栏目:开发技术

这篇文章主要介绍了JS前端的内存处理的方法是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇JS前端的内存处理的方法是什么文章都会有所收获,下面我们一起来看看吧。

一、内存的储存和代码执行的场所关系

对于任何语言来说,内存管理、垃圾回收等知识都是进阶路上绕不开的坎。出来面试估计也没少被问到“前端的内存处理你了解么? 你知道js中的垃圾处理机制吗? 什么情况会导致内存泄漏呢?

1. 储存空间

两种结构:

  • 栈空间:

  • 存储原始类型

  • 执行上下文 (代码空间:主要存储可执行代码)

  • 堆空间:存储引用类型

为什么不都用栈存呢?

因为需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率。

2. 内存的生命周期

内存分配:声明变量、函数、对象的时候 内存的使用:读写内存,使用变量 函数等 内存回收:使用完毕,由垃圾回收机制自动回收不再使用的内存

3. js 中的内存分配和使用

// 分配
const num = 123;  // 分配到栈
const str = 'sss';// 分配到栈
const obj = {};   // 分配到堆
// 使用
const a = 10;
console.log(a); // 使用

4. 调用栈下移ESP(记录当前执行状态的指针)

当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。 比方下面这个例子通过ESP 状态来展示就如图所示。

var foo1 = () => {
    console.log('foo1')
    foo2()
}
var foo2 = () => {
    console.log('foo2')
}
foo1()

JS前端的内存处理的方法是什么

被ESP指针移开后的函数作用域foo1 明显属于不在被引用,后续将会直接被GC回收

二、 js中的垃圾回收机制

有C语言经验的开发者,一定明白内存声明分配好之后,需要手动 free 的操作,这就是手动回收。而 Js 本身是自动回收机制,所以开发者不需要过多关注内存分配和释放的问题。这些工作都让 V8 引擎中的垃圾回收器(GC)给承包了。

最早接触 js 的时候,市面上对于 js 内存管理、垃圾回收主要讲的是下面两种概念:

1. 引用计数法

引用计数法的算法主要依赖于引用的概念,这个回收机制最早是在 IE 在使用的。目前主流浏览器都使用标记清除法了。看一个对象是否有指向他的引用,如果没有其他对象指向他了,说明当前这个对象不再被需要了。

他的缺陷在于:循环引用

如果两个对象相互引用,尽管他们已不再被使用,但是引用计数无法识别,导致内存泄漏。

2. 标记清除法(Mark-Sweep)

将“不再使用的的对象”定义为“无法到达的对象

从根部js的全局对象触发,定时递归扫描内存中的对象,凡是无法从根部到达的对象,就会被标记为不再使用,稍后进行回收。

执行过程如下:

  • GC在运行的时候会给内存中的所有变量都加上标记

  • 将从根部触发能够触及到的对象标记清除

  • 剩下的还有标记的变量被视为准备删除的变量

  • GC销毁带有标记的值 回收内存空间

三、代际假说和分代收集

代际假说(The Generational Hypothesis)是现代浏览器垃圾回收策略的基础。整个模型可以看看下图

JS前端的内存处理的方法是什么

新生代(副垃圾回收器)

存放的是生存时间短、占用空间较小的的对象,通过 Scavenge 算法,是把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域。新的对象都要放到对象区,当快满的时候,将还存活的对象复制到空闲区后进行角色互换。并且执行对象晋升策略,对象区域和空闲区域翻转两次还存在的对象,升级到老生代。这个复制翻转的过程也避免内存碎片的产生。

老生代(主垃圾回收器)

存放的生存时间久的对象或者大的对象,使用标记清除的算法进行垃圾回收。一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。这种行为称为全停顿(Stop-The-World)。实际上浏览器为了避免垃圾回收卡顿通过增量标记方式将回收任务拆解成多个小任务穿插在js主线程中执行。

四、常见的内存泄漏

1. 全局变量

function foo() {
    bar1 = 'aaa'; // 相当于声明在window.bar1
    this.bar2 = 'aaaa'
}
foo(); // 执行函数事this指向window ,相当于一个函数给全局变量增加了两个变量

2. 未被清理的定时器和回调函数

//setInterval
//setTimeout
setInterval(() => {
    console.log('test')
}, 500)
//没用用 clearInterval clearTimeout 做清除

3. 闭包

个人最喜欢《你不知道的js》里对闭包的描述

一个内部函数,有权访问包含其的外部函数的变量 —— 《你不知道的js》 或者也可以用“内存逃逸”这种高逼格的属于形容。

// 闭包 gc 案例
var one = null;
var replace = function() {
    var originalOne = one;
    var unused =function() {
        if(originalOne) {
            console.log(111);
        }
    }
    one = {
        longString: '111',
        method: function() {
            console.log()
        }
    }    
}
setInterval(replace, 500)

每次调用 replace, one 得到一个包含字符串和一个对于新闭包 method 的对象 unused 引用了 originOne

5. DOM 引用

var elements= {
    image: document.getElementById('111');
}
elements.image = null;

6. 怎么避免呢?

  • 尽量减少全局变量

//尽可能少写
window.object = {} // 这类代码
  • 使用完引用数据后,及时解除引用.null

let obj = {}
...
obj = null;
  • 避免死循环等持续执行的操作(例如 边界判断不清晰的 for 或 while 循环)

  • 多使用 WeakSet / WeakMap 特性

// Vue3 中就大量用了WeakMap 优化实例引用
const bucket = new WeakMap();
...
const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);
    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;
    trigger(target, key);
  },
});
function track(target, key) {
  if (!activeEffect) return;
  let depsMap = bucket.get(target);
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()));
  }
  // 再根据key 从 depsMap 中取得 deps, 它是一个 Set 类型,里面存储着左右与当前 key 相关联的副作用函数: effects
  let deps = depsMap.get(key);
  // 如果 deps 不存在,同样新建一个 Set 并与 key 关联
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  deps.add(activeEffect);
  activeEffect.deps.push(deps);
}
...

关于“JS前端的内存处理的方法是什么”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“JS前端的内存处理的方法是什么”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

js
AI