聊聊JavaScript性能优化!

news2024/12/26 10:51:23

随着软件开发行业的发展,性能优化是一个不可避免的话题,那么什么样的行为才能算作性能优化呢?本质上来说,任何能提高运行效率,降低运行开销的行为,都可以算作性能优化的操作。那么JavaScript语言的优化从理解内存空间的使用,再到垃圾回收帮助我们去编写高质量的JavaScript代码。

内存管理

内存管理流程分为三步:申请内存空间、使用内存空间和释放内存空间,在JavaScript中并没有提供内存响应的api,所以JavaScript不能像c或java那样调用api去做内存响应的管理,但是我们依然能够在JavaScript脚本中进行内存声明周期的管理。

//申请
let obj = {};
//使用
obj.name="name";
//释放
obj = null;

在JavaScript中内存管理是自动的,我们在创建对象、数组的时候会自动分配内存空间,后续在代码执行过程中无法找到引用关系,这些对象就会被看做垃圾。另外代码中对象存在,而由于代码错误导致找不到该对象了,这也是垃圾。知道了有哪些垃圾,JavaScript执行引擎就会出来回收,这个过程就是JavaScript的垃圾回收。在JavaScript中可以访问到的对象就是可达对象(引用、作用域链),可达的标准就是从全局变量出发是否能找到。

GC的定义与作用

GC就是垃圾回收机制的简写,当GC工作的时候可以找到内存中的垃圾对象,然后对对象空间进行释放和回收,方便后续代码进行使用。那么GC中的垃圾是什么呢?

  1. 程序中不再需要使用的对象
function func(){
    name = "test";
    return `${name} is a developer`
}
func(); //当函数调用完后,不再需要使用name
  1. 程序中不能再访问到的对象
function func(){
    const name = "test";
    return `${name} is a developer`
}
func(); //当函数调用完后,外部空间访问不到name了

GC算法是什么

GC算法就是GC工作时查找和回收所遵循的规则,例如如何查找空间,如何释放空间,回收空间的过程中如何去分配。

常见的GC算法:

  • 引用计数
  • 标记清除
  • 标记整理
  • 分代回收

引用计数算法实现原理

它的核心思想是内部通过一个引用计数器来维护当前对象的引用数,从而去判断对象的引用数是否为0来判断对象是不是一个垃圾对象。如果为0则GC开始工作进行对象空间的回收。

它的优点是发现垃圾时立即回收,因为它根据当前对象的引用数为0来判断是不是一个垃圾,如果是0则立即进行释放。引用计数算法能够最大限度的减少程序暂停,因为引用计数算法时刻监控着那些引用计数为0的对象,当内存占满的时候就会去找那些引用计数为0的对象进行释放,这样就能保证内存不会有占满的时候。

它的缺点是无法将那些循环引用的对象进行回收,而且它所消耗的时间长。什么是循环引用呢?

function fn(){
    const obj1 = {};
    const obj2 = {};
    obj1.name = obj2;
    obj2.name = obj1;
}
fn();

标记清除算法实现原理

标记清除算法将整个垃圾回收分为两个阶段:一是遍历所有的对象找到活动对象进行标记,二是遍历所有的对象将没有标记的对象进行清除,同时把第一阶段做的标记给抹掉。经过这两次方便将垃圾空间进行回收。

它的优点是可以解决循环引用的对象进行回收,它的缺点是产生空间碎片化,当前所回收的垃圾对象在内存空间地址上不连续,由于不连续导致回收之后分散到各个角落,后续使用的时候如果新的申请空间大小匹配则可以直接使用,否大过大或过小就不适合使用。其次它也不会立即回收对象。

标记整理算法实现原理

标记整理算法可以看做是标记清除算法的增强版,它的第一阶段遍历所有的对象找到可达对象进行标记,它的第二阶段会在清除之前先去进行整理的操作,移动对象的位置让他们在地址上产生连续,然后再做清除的操作。

它的优点是解决了标记清除的空间碎片化,它的缺点是不会立即回收对象。

V8引擎

V8引擎是一款主流的JavaScript执行引擎,目前Chorme浏览器和Node.js都在使用V8引擎执行JavaScript代码,它的速度很快,采用即时编译,将JavaScript代码转成直接执行机器码。他还有一大特点是内存是有上限的,在64位操作系统中内存的上限是不超过1.5G,在32位操作系统中内存的上限是不超过800M的。

垃圾回收策略

V8垃圾回收策略采用分代回收的思想,将内存空间按照规则分成两类,一类是新生代存储区,另一类是老生代存储区。它会根据不同代采用高效的GC算法,从而针对不同的对象进行回收的操作。

V8中常用的GC算法

  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量

V8内存分配

V8把内存空间分成了两部分,左侧小空间用于存储新生代对象(32M(64位操作系统)|16M(32位操作系统)),这里新生代对象指的是存活时间较短的对象,比如局部作用域的变量。

新生代对象回收实现

新生代对象回收过程采用复制算法+标记整理,它将内存区分为两个等大的空间,使用空间为From状态,空闲空间为To状态。在代码执行过程中,如果需要申请空间的话,会将活动对象分配存储于From空间,当From空间应用到一定的程度之后,就会触发GC操作,这时候会采用标记整理操作来将From空间进行活动对象的标记,找到活动对象之后会进行整理操作将位置变得连续,便于后续不会产生碎片化的空间,做完之后将活动对象拷贝到To空间,拷贝完成之后就可以将From空间进行回收释放操作了,然后将From和To进行空间交换即可。

2.png

老生代对象回收实现

老生代对象存放在右侧老生代区域,它是指存活时间较长的对象,例如在全局对象下的变量、闭包中的变量数据等。它在内存管理中同样有限制,在64为操作系统中为1.4G,在32位操作系统中为700M。

它的垃圾回收过程采用标记清除、标记整理、增量标记算法。首先它采用标记清除算法来进行垃圾空间的回收,然后当新生代对象往老生代存储区进行迁移的时候采用标记整理进行空间优化,最后采用增量标记算法进行效率优化。

performance内存监控

使用步骤如下:

  1. 打开chrome浏览器输入目标网址
  2. 进入开发人员工具面板,选择性能选项
  3. 开启录制功能,访问具体页面
  4. 执行用户行为,一段时间后停止录制,从而得到报告
  5. 从报告中分析界面中记录的内存信息

内存问题的表现有哪些?

  1. 当网络没问题的时候,页面出现延迟加载或经常性暂停,则内部可能出现频繁的垃圾回收
  2. 页面出现持续性的糟糕的性能,则内部可能出现性能膨胀
  3. 如果感受到页面的性能随着时间的延长越来越差,则内部可能出现内存泄露

有以上表现则通过performance进行内存分析,查找有问题的代码进行修改。

内存监控的几种方式

首先看一下内存问题的标准有哪些:

  1. 内存泄露:指的是内存使用持续升高,但是没有下降的节点
  2. 内存膨胀:指的是当前应用程序为了达到某个效果而需要庞大的内存空降。在大多数设备上都存在性能问题。
  3. 频繁垃圾回收:通过内存变化图来分析

内存监控的几种方式有哪些?

  1. 浏览器任务管理器
  2. Timeline时序图记录
  3. 堆快照查找分离DOM
  4. 判断是否存在频繁的垃圾回收

浏览器任务管理器

打开浏览器任务管理找到JavaScript使用的内存这一栏,查看打开页面的使用的内存如果一直增长则判断内存出现问题。它只能判断不能定位。

3.png

Timeline记录内存

打开performance,勾选memory内存选项,然后可以看到新增了一个区域,里面有JS Heap、Documents、Nodes、Listeners、GPU Memory。

4.png

堆快照查找分离DOM

分离DOM指的是DOM从DOM树上分离了,但是在代码中引用了。这种DOM在界面上看不见,但是在内存中占据空间,所以这是一种内存泄露,那么可以从堆快照中查找这种分离DOM。

打开浏览器的开发者工具,找到内存选项卡,点击Take snapshot即可拍快照。

5.png

输入deta筛选条件即可查看哪些是分离DOM

6.png

判断是否频繁的垃圾回收

  • 使用Timeline查看是否频繁的上升下降
  • 浏览器任务管理器中数据频繁的增加减小

V8引擎执行流程

V8引擎本身也是一个应用程序,它是JavaScript的运行环境,V8引擎主要用来解析和编译JavaScript代码,它的内部也有很多的子模块,如图所示:

7.png
V8引擎是渲染引擎中执行JavaScript代码的一部分,Scanner是一个扫描器,他可以扫码JavaScript代码进行词法的分析,把JavaScript分析成tokens,parser是一个解析器,解析的过程就是语法分析的过程,它可以将tokens转换成抽象语法树,Ignition是V8提供的一个解释器,负责把抽象语法树AST转换成字节码。TurboFan是V8提供的编译器模块,把上一步提供的字节码编译成汇编代码去执行。

变量局部化

尽可能定义的变量存放在局部作用域中,减少数据访问时查找作用域链的路径,提高代码的执行效率。例如:

//示例一
var i,str="";
function test(){
	for(i=0;i<1000;i++){
		str+=i;
	}
}
test();

//示例二
function test(){
	let str="";
	for(let i=0;i<1000;i++){
		str+=i;
	}
}
test();

在JSBench中查看运行速度,示例二要比示例一更快一些。对于数据的存储和读取,希望能够减少作用域的访问层级。

8.png

缓存数据

在代码编写的过程中,有一些数据在不同的地方会有多次的使用,这样的数据可以提前保存起来,以便后续使用,核心还是减少访问查询的层级。

减少访问层级

比如以下代码:

//示例一
function Person(){
	this.name = "jie";
	this.age = 18;
}
let p = new Person();
console.log(p.age);

//示例二
function Person(){
	this.name = "jie";
	this.age = 18;
	this.getAge = function(){
		return this.age;
	}
}
let p = new Person();
console.log(p.getAge());

示例一访问的层数较示例二少,访问的更快一些。

9.png

防抖和节流

在一些高频率事件触发的场景下不希望对应的事件处理函数多次执行,例如场景:

  1. 滚动事件
  2. 输入的模糊匹配
  3. 轮播图切换
  4. 点击操作

出现以上场景的原因是,浏览器默认情况下都会有自己的监听事件间隔,如果检测到多次的事件监听执行,那么就会造成不必要的资源浪费。这时就需要防抖和节流。

防抖

防抖指在高频的操作只识别一次点击,可以认为是第一次或最后一次。

/**
 * handle: 最终需要执行的事件监听
 * wait: 事件触发之后多久执行
 * immediate: 控制执行第一次还是最后一次,false是最后一次
 */
function myDebounce(handle,wait,immediate){
    if(typeof handle !== 'function') throw new Error('handle must be an function');
    if(typeof wait === 'undefined') wait = 300;
    if(typeof wait === 'boolean') {
        immediate = wait;
        wait = 300;
    } 
    if(typeof immediate !== 'boolean') immediate = false;
    let timer = null;
    return function proxy(...args){
        let self =this;
        init = immediate&&!timer;
        clearTimeout(timer);
        timer = setTimeout(()=>{
            timer = null;
            !init?handle.call(self,...args):null;
        },wait);
        //立即执行
        init?handle.call(self,...args):null;
    }
}

节流

节流指在高频的操作下可以自己设置频率,让本来会执行很多次的事件触发,按着定义的频率减少触发的次数。

function myThrottle(handle,wait){
    if(typeof handle !== 'function') throw new Error('handle must be an function');
    if(typeof wait === 'undefined') wait = 400;
    let previous = 0;
    let timer = null;
    return function proxy(...args){
        let now = new Date();
        let self = this;
        let interval = wait - (now - previous);
        if(interval <= 0){
            //是一个非高频的操作,可以执行handle
            clearTimeout(timer);
            timer = null;
            handle.call(self,...args);
            previous = new Date();
        }else if(!timer){
            //此时在定义的频率范围内,则不执行handle,这时候可以定义定时器,在规定时间后执行
            timer = setTimeout(()=>{
                clearTimeout(timer);
                timer = null;
                handle.call(self,...args);
                previous = new Date();
            },interval);
        }
    };
}

减少判断层级

当有if else多层嵌套的时候,提前用return去减少嵌套层级。if else适合于区间的条件判断,switch case适合于确定枚举值的判断。

//示例一
function doSomething(part, chapter){
    const parts = ['ES2016','工程化','Vue','React','Node'];
    if(part){
        if(parts.includes(part)){
            console.log('属于当前课程');
            if(chapter > 5){
                console.log('您需要提供VIP身份');
            }
        }
    }else{
        console.log('请确认模块信息');
    }
}
doSomething('ES2016',6);

//示例二
function doSomething(part, chapter){
    const parts = ['ES2016','工程化','Vue','React','Node'];
    if(!part){
        console.log('请确认模块信息');
        return;
    }
    if(!parts.includes(part)) return;

    console.log('属于当前课程');
    if(chapter > 5){
        console.log('您需要提供VIP身份');
    }
}
doSomething('ES2016',6);

可以看出示例二的ops更多一些。

10.png

减少循环体活动

将循环体中经常使用又不变动的数据放到循环体的外部,做一个缓存,这样代码在执行过程中少做一些事情。

//示例一
function test(){
    let i;
    let arr = ['React','Vue','Angular'];
    for(i=0;i<arr.length;i++){
        console.log(arr[i]);
    }       
}
test();

//示例二
function test(){
    let i;
    let arr = ['React','Vue','Angular'];
    let len = arr.length;
    for(i=0;i<len;i++){
        console.log(arr[i]);
    }       
}
test();

可以看出示例二的ops更多一些。

11.png

再者可以使用while循环替代for循环,从后往前的循环少做一些判断,从而更快一些。

//示例一
function test(){
    let i;
    let arr = ['React','Vue','Angular'];
    let len = arr.length;
    for(i=0;i<len;i++){
        console.log(arr[i]);
    }       
}
test();

//示例二
function test(){
    let arr = ['React','Vue','Angular'];
    let len = arr.length;
    while(len--){
        console.log(arr[len]);
    }
}
test();

12.png

字面量与构造式

字面量的比构造式的快一些,字面量创建引用数据类型直接堆区中创建存放内容即可,而构造式创建是调用函数的方式会多做一些操作,所以速度会慢一些。

//示例一
function test(){
    let obj = new Object();
    obj.name = "test";
    obj.age = 20;
    return obj;
}
console.log(test());

//示例二
function test(){
    let obj = {
        name: "test",
        age: 20
    }
    return obj;
}
console.log(test());

13.png

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

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

相关文章

linux网络管理,设置主机名

目录标题 使用nmtui配置&#xff08;图形化设置&#xff09;使用nmcli设置修改配置文件cockpit配置示意图使用ip命令配置临时生效的网络连接设置主机名查看主机名修改主机名 使用nmtui配置&#xff08;图形化设置&#xff09; [rootkongd ~]# nmcli c reload [rootkongd ~]# nm…

项目分析v1

用户&#xff1a; 登录&#xff1a; 不能重复登录。 在服务端使用一个hashset记录用户的登录状态&#xff0c;如果用户id不在集合里面&#xff0c;就可以登录&#xff0c;登录时将用户id添加到集合中。用户下线时&#xff0c;将set中的元素删除。 登录成功后&#xff0c;服务端…

深入了解SpringMVC框架,探究其优缺点、作用以及使用方法

一、什么是Spring MVC SpringMVC是一种基于Java的Web框架&#xff0c;与Spring框架紧密结合&#xff0c;用于开发具备WebApp特性的Java应用程序。Spring MVC是Spring Framework的一部分&#xff0c;因此它具有与Spring框架相同的特性和理念。 二、SpringMVC的优缺点 1. 优点…

const、指针、引用

一、const和指针&#xff1a; 分类&#xff1a; 1.1 指向常量的指针 上面的两种形式所表示的含义为&#xff1a;pt的指向可以随便修改&#xff0c;但pt所 指向的东西不得通过pt修改。 1.2 指向变量的常指针 指针的指向不允许改动&#xff0c;但指向的东西可以修改。&#…

1.4W字!让我带你读懂springmvc的世界!

目录 一.前提了解 1.tomcat和servlet的关系&#xff1f; 2.springmvc想要实现web开发必须满足的条件是什么&#xff1f; 二.什么是SpringMVC 三.基于SpringMVC创建web项目 ①创建项目并选择依赖 ②设置热部署&#xff08;部分代码改动不需要手动重新run即可生效&#xff0…

工地烟火AI监控识别分析系统 yolov7

工地烟火AI监控识别分析系统通过yolov7网络模型技术&#xff0c;工地烟火AI监控识别分析系统对工地或者厂区现场监控区域内的烟火进行实时分析报警。YOLOv7 的发展方向与当前主流的实时目标检测器不同&#xff0c;研究团队希望它能够同时支持移动 GPU 和从边缘到云端的 GPU 设备…

请求重定向(forward)和请求转发(redirect)的区别详解(看这一篇就够了)

在Java中进行页面跳转的方式有两种&#xff1a;重定向和请求转发&#xff0c;但是两者的内部实现是完全不一样的&#xff0c;主要区别分为以下5种&#xff1a; 定义不同请求方式不同数据共享不同最终 URL 地址不同代码实现不同 1&#xff0c;定义不同 请求重定向&#xff08;f…

五面阿里Java岗,从小公司到阿里的面经总结

​​​​​​​ 面试 笔试常见的问题 面试常见的问题下面给的面试题基本都有。 1 手写代码&#xff1a;手写代码一般会考单例、排序、线程、消费者生产者 排序。 2 写SQL很常考察group by、内连接和外连接 2.面试1-5面总结 1&#xff09;让你自我介绍 2&#xff09;做两道算法…

【软考高级】2022年系统分析师综合知识

1.( )是从系统的应用领域而不是从系统用户的特定需要中得出的&#xff0c;它们可以是新的功能性需求&#xff0c;或者是对已有功能性需求的约束&#xff0c;或者是陈述特定的计算必须遵守的要求。 A.功能性需求 B. 用户需求 C.产品需求 D.领域需求 2.对于安全关键系…

红黑树理论详解与Java实现

文章目录 基本定义五大性质红黑树和2-3-4树的关系红黑树和2-3-4树各结点对应关系添加结点到红黑树注意事项添加的所有情况 添加导致不平衡叔父节点不是红色节点&#xff08;祖父节点为红色&#xff09;添加不平衡LL/RR添加不平衡LR/RL 叔父节点是红色节点&#xff08;祖父节点为…

知识图谱学习笔记——(四)知识图谱的抽取与构建

一、知识学习 声明&#xff1a;知识学习中本文主体按照浙江大学陈华钧教授的《知识图谱》公开课讲义进行介绍&#xff0c;并个别地方加入了自己的注释和思考&#xff0c;希望大家尊重陈华钧教授的知识产权&#xff0c;在使用时加上出处。感谢陈华钧教授。 &#xff08;一&…

汇编三、51单片机汇编指令1

1、指令格式 (1)举例&#xff1a;将立即数0x30送入累加器A MOV  A, #0x30 标号 操作码 目标地址&#xff0c;数据源 ;注解 (2)标号&#xff0c;注解可选项&#xff0c;不一定有。 2、指令执行时间和指令存储空间 (1)指令执…

谁是太阳膜界的真正王者?

小编一文教会你如何选择好的太阳膜 随着天气越来越热&#xff0c;有很多车友迫切的想为自己的爱车&#xff0c;贴上隔热膜&#xff0c;特别是新能源车主。 现在的新能源车都有很大的前挡玻璃和全景天窗&#xff0c;提升了爱车的档次和美观度。但是随之而来的隔热和安全也受到了…

文心一言创意图

文章目录 本土优化创意图成语和典故 本土优化 此前文心一格最让人诟病的就是那张“爱国猫”的图像了&#xff0c;十分离谱&#xff0c;让人一猜就是训练集的问题。 但百度作为全村的希望&#xff0c;对文心一言的优化也是肉眼可见的&#xff0c;迅速做了针对本土的优化&#…

5月新书预告

“读书不觉已春深&#xff0c;一寸光阴一寸金。”相信许多小伙伴儿都把这个五一假期安排的满满当当&#xff0c;还有一部分人抱着书本养精蓄锐、精进技能。小编也没闲着&#xff0c;为大家收集了几本精品新书。 《现代软件工程》是《持续交付》的作者David Farley的另一本力作&…

Codeforces Round 868 (Div. 2)A.B.C

A. A-characteristic 题目链接&#xff1a; Problem - A - Codeforces 题面&#xff1a; 题意&#xff1a; 有一个数组a&#xff0c;里面只存在1和-1&#xff0c;现在可以选择任意两个位置&#xff0c;但是不能重合&#xff0c;如果两个位置的数乘积为1&#xff0c;那么特点…

学生台灯什么牌子好对眼睛好?专业护眼灯的学生台灯分享

据报告统计&#xff0c;2022年我国儿童青少年总体近视率为52.7%&#xff0c;其中6岁儿童为14.3%&#xff0c;小学生为35.6%&#xff0c;初中生为71.1%&#xff0c;高中生为80.5%&#xff0c;这些数据让人不寒而栗&#xff01; 专家表示&#xff0c;导致儿童青少年近视的因素&am…

Shell脚本2

自定义局部变量 :定义在一个脚本文件中的变量 只能在这个脚本文件中使用的变量&#xff0c;局部变量 语法&#xff1a; var_namevalue 变量定义规则 变量名称可以有字母,数字和下划线组成, 但是不能以数字开头 等号两侧不能有空格 在bash环境中, 变量的默认类型都是字符串…

thinkphp+vue+html基于web的旅游景点酒店线路管理系统6722q

数据库分析 整个系统所包括的信息有景点信息、用户信息、酒店信息、旅行社信息、留言信息等。可将这些信息抽象为下列系统所需要的数据项和数据结构: 1.景点管理(编号&#xff0c;景点名称&#xff0c;景点等级&#xff0c;天气情况&#xff0c;位置&#xff0c;住宿&#xff0…

真无线耳机哪款性价比高?高性价比无线耳机排行榜

近几年&#xff0c;蓝牙耳机凭借使用便捷而受到广大用户的欢迎&#xff0c;逐渐取代有线耳机成为人们生活中必不可少的存在。下面&#xff0c;我来给大家推荐几款性价比高的蓝牙耳机&#xff0c;一起来看看吧。 一、南卡小音舱Lite2蓝牙耳机 参考价&#xff1a;299 蓝牙版本…