JS前端的内存处理的方法全面详解

目录
  • 一、内存的储存和代码执行的场所关系
    • 1. 储存空间
    • 2. 内存的生命周期
    • 3. js 中的内存分配和使用
    • 4. 调用栈下移ESP(记录当前执行状态的指针)
  • 二、 js中的垃圾回收机制
    • 1. 引用计数法
    • 2. 标记清除法(Mark-Sweep)
  • 三、代际假说和分代收集
    • 新生代(副垃圾回收器)
    • 老生代(主垃圾回收器)
  • 四、常见的内存泄漏
    • 1. 全局变量
    • 2. 未被清理的定时器和回调函数
    • 3. 闭包
    • 5. DOM 引用
    • 6. 怎么避免呢?

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

对于任何语言来说,内存管理、垃圾回收等知识都是进阶路上绕不开的坎。出来面试估计也没少被问到“前端的内存处理你了解么? 你知道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()

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

二、 js中的垃圾回收机制

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

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

1. 引用计数法

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

他的缺陷在于:循环引用

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

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

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

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

执行过程如下:

  • GC在运行的时候会给内存中的所有变量都加上标记
  • 将从根部触发能够触及到的对象标记清除
  • 剩下的还有标记的变量被视为准备删除的变量
  • GC销毁带有标记的值 回收内存空间

三、代际假说和分代收集

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

新生代(副垃圾回收器)

存放的是生存时间短、占用空间较小的的对象,通过 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);
}
...

参考

【1】 《浏览器工作原理与实践》

【2】 一文搞懂垃圾回收

以上就是JS前端的内存处理的方法全面详解的详细内容,更多关于JS前端内存处理的资料请关注我们其它相关文章!

(0)

相关推荐

  • package.json的版本号更新优化方法

    目录 引言 node.js 部分,我们得有一个更改仓库代码的脚步留给ci执行 CI :如何让发布包的行为直接和代码仓库中的版本号同步? 你可能遇到最多的坑 可以优化的地方 可以再优化的地方 引言 本文的起因是有在代码仓库发包后,同事问我“为什么package.json 里的版本还是原来的,有没有更新?”,这个时候我意识到,我们完全没有必要在每次发布的时候还特意去关注这个仓库的版本号,只要在发布打tag的时候同步一下即可,于是有了本文的实践. node.js 部分,我们得有一个更改仓库代码的脚步留

  • js多维数组降维的5种方法

    目录 一,递归 二,将多维数组转为字符串,再转化为一维数组 三,利用数组的方法 四.利用contact 五.利用扩展运算符 多维数组降维也就是数组扁平化 数组扁平化的方法有很多种,但是我主要用五种,如下: 一,递归 // 一,递归 let arr = [1, [2, 3, 4, 5], [6, 7, 8, 9, [10, 11, 12, [14, 15, 16]]]] let newArr = [] const getArr=(array)=>{ array.forEach((item)=>{

  • JavaScript实现获取img的原始尺寸的方法详解

    在前端开发中我们几乎不需要获取img的原始尺寸,因为只要你不刻意设置图片的宽高它都会按照最佳比例渲染.但是在微信小程序开发时,它的image标签有一个默认高度,这样你的图片很可能出现被压缩变形的情况,所以就需要获取到图片的原始尺寸对image的宽高设置. 微信小程序获取image原始尺寸的方法 <view style="width:100%;" > <image src="https://sf3-ttcdn-tos.pstatp.com/img/mosaic

  • js前端上传文件缩略图技巧示例详解

    目录 引言 文件对象简介 Blob File FileReader FormData 文件对象之间的关系 缩略图的实现 总结 引言 通常情况下,前端提交给服务器的数据格式为JSON格式,但很多时候用户想上传自己的头像.视频等,这些非文本数据的时候,就不能直接以JSON格式上传到后端了. 当我们要获取用户上传的文件,可以使用input表单项,将type属性值设置为“file”. <form action=""> <input type="file"

  • JS实现换肤功能的方法实例详解

    本文实例讲述了JS实现换肤功能的方法.分享给大家供大家参考,具体如下: 首先准备HTML页面如下: <div id="container"> <div id="header"> <h3>无人驾驶要征服世界,得先解决这些问题</h3> </div> <div id="nav"> <input type="button" id="blue&qu

  • JS利用prototype给类添加方法操作详解

    本文实例讲述了JS利用prototype给类添加方法操作.分享给大家供大家参考,具体如下: 1.如何定义一个简单的类? 以下是一个没有任何属性和方法的类的定义: function MyClass(){}; 你可能会想,这不就是个简单的函数声明?没错,这个函数就是一个类的定义的实现.如何使用这个类呢?看下面的代码: var cls1 = new MyClass(); 这样,利用new就可以生成MyClass的一个实例了.所以在js中可以说函数就是类,类就是函数. 2.给类增加属性和方法 funct

  • js前端面试之同步与异步问题详解

    前言 我本来是打算写一篇co源码精读(为啥读co,因为它短),然鹅发现自己存在一系列基础问题没有搞透彻,打算写一个js基础系列文章,总结自己的理解(copy),希望与你在学习路上一同进步.首先问问自己当面试官问到js中的同步和异步,这个问题该怎么回答?理解一个问题无非是what-why-how js同步和异步问题是什么-->为什么会产生异步问题-->如何解决. 一.JavaScript起源 技术的出现,和应用场景密切相关的.JavaScript诞生于1995年.当时,它的主要目的是处理以前由服

  • JS前端模块化原理与实现方法详解

    本文实例讲述了JS前端模块化原理与实现方法.分享给大家供大家参考,具体如下: 1.什么是前端模块化 模块化开发,一个模块就是一个实现特定功能的文件,有了模块我们就可以更方便地使用别人的代码,要用什么功能就加载什么模块. 2.模块化开发的好处 1)避免变量污染,命名冲突 2)提高代码利用率 3)提高维护性 4)依赖关系的管理 3.前端模块化的进程 前端模块化规范从原始野蛮阶段现在慢慢进入"文艺复兴"时代,实现的过程如下: 3.1 函数封装 我们在讲到函数逻辑的时候提到过,函数一个功能是实

  • 使用纯前端JavaScript实现Excel导入导出方法过程详解

    公司最近要为某国企做一个**统计和管理系统, 具体要求包含 Excel导入导出根据导入的数据进行展示报表图表展示(包括柱状图,折线图,饼图),而且还要求要有动画效果,扁平化风格Excel导出,并要提供客户端来管理Excel 文件... 要求真多! 现在总算是完成了,于是将我的经验分析出来. 在整个项目架构中,首先就要解决Excel导入的问题. 由于公司没有自己的框架做Excel IO,就只有通过其他渠道了. 嗯,我在github上找到了一个开源库xlsx,通过npm方式来安装. npm inst

  • Vue.js框架路由使用方法实例详解

    Vue.js框架路由使用方法实例详解 html代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name='viewport' content='width=device-width,initial-

  • Android中webview与JS交互、互调方法实例详解

    Android中webview与JS交互.互调方法实例详解 前言: 对于试水的功能,一般公司都会采用H5的方式来开发,可以用很少的资源与很短的项目工期来完成. 但许多情况下,H5页面会需要一些原生持有的一些如用户信息之类的数据,一些交互也需要调用原生的,如toast之类要保持同一个手机风格一致的交互行为.这个时候就需要能够让JS主动调用原生的方法来进行操作或者获取数据.或者是原生调用JS的方法在H5加载的时候传递一些参数. 对于原生调用JS的方法 我们需要实现一个WebViewClient,在这

  • js中获取URL参数的共用方法getRequest()方法实例详解

    下面通过一段代码给大家介绍js中获取URL参数的共用方法getRequest()方法,具体代码如下所示: getRequest : function() { var url = location.search; //获取url中"?"符后的字串 var theRequest = new Object(); if (url.indexOf("?") != -1) { var str = url.substr(1); strs = str.split("&am

  • nodejs 使用 js 模块的方法实例详解

    Intro# 最近需要用 nodejs 做一个爬虫,Google 有一个 Puppeteer 的项目,可以用它来做爬虫,有关 Puppeteer 的介绍网上也有很多,在这里就不做详细介绍了. node 小白,开始的时候有点懵逼,模块导出也不会. 官方文档上说支持 *.mjs 但是还要改文件扩展名,感觉有点怪怪的,就没用,主要是基于js的模块使用. 模块导出的两种方式# 因为对 C# 比较熟悉,从我对 C# 的理解中,将 nodejs 中模块导出分成两种形式: 1.一个要实例化才能调用的模块 2.

随机推荐