1 内存管理
    内存由可读写单元组成,表示一片可操作空间
    人为的去操作一片空间的申请、使用和释放叫管理
    在JavaScript中申请-使用-释放

    1. // 申请
    2. let obj = {};
    3. // 使用
    4. obj.foo = function() {}
    5. // 释放
    6. obj = null;

    2 JavaScript中的垃圾回收
    JavaScript的内存管理是自动的;对象不再引用是垃圾;对象不能从根上访问到时垃圾;js执行引擎识别到垃圾就会执行垃圾回收;
    可达对象:可以访问到的对象就是可达对象(引用、作用域链等);可达的标准就是从根出发,是否能够被找到;JavaScript中的根就可以理解为全局变量对象;

    1. // 定义变量后123在全局可达
    2. let obj = {foo: '123'};
    3. // 添加引用后引用计数加一
    4. let foo = obj;
    5. // 删除引用后obj还在,123依然可达
    6. foo = null;
    1. function objGroup(obj1, obj2) {
    2. obj1.prev = obj2;
    3. obj2.next = obj1;
    4. return {
    5. o1: obj1,
    6. o2: obj2,
    7. };
    8. }
    9. let obj = objGroup({ name: 'obj1' }, { name: 'obj2' });
    10. console.log(obj)
    11. // {
    12. // o1: { name: 'obj1', prev: { name: 'obj2', next: [Circular] } },
    13. // o2: { name: 'obj2', next: { name: 'obj1', prev: [Circular] } }
    14. // }
    15. // obj通过函数使全局可达,被赋值为{o1,o2}对象
    16. // {o1,o2}对象又由o1、o2组成,o1、o2相互引用
    17. // 此时delete {o1,o2}对象的o1,并delete o2的next,o1将会失去引用,被标识为垃圾,会被回收

    3 GC算法
    GC就是Garbage collection的简写;GC可以找到内存中的垃圾、并释放和回收空间;
    GC里的垃圾包括程序中不再需要使用的对象;

    1. function fn1() {
    2. name = 'lg';
    3. return `${name} is a coder`
    4. }
    5. fn1(); // name 当函数调用完毕,从程序需求角度考虑,函数执行完毕,不再使用,应该要被回收

    程序中不能再访问到的对象;

    1. function fn1() {
    2. name = 'lg';
    3. return `${name} is a coder`
    4. }
    5. fn1(); // name 当函数调用完毕,从程序需求角度考虑,函数执行完毕,不再使用,应该要被回收

    总结: GC是一种机制,垃圾回收期完成具体的工作;工作的内容就是查找垃圾、释放空间、回收空间;算法就是工作时查找和回收遵守的规则;
    常见的GC算法,引用计数、标记清除、标记整理、分代回收;
    3.1 引用计数算法
    核心:设置引用数,判断当前引用数是否为0;
    有引用计数器;
    引用关系改变时修改引用数字;
    优点:发现垃圾时立即回收;最大限度减少程序暂停(减少程序卡顿);
    缺点:无法回收循环引用的对象;资源(时间)开销大;
    3.2 标记清除算法
    核心: 分标记和清除两个部分;先遍历所有对象标记活动对象,再遍历所有对象清除没有标记的对象,这样就可以回收空间了
    过程: 从全局对象下递归遍历所有对象,标记所有除了未引用的对象,然后再遍历一遍清除未标记的对象,并清除所有标记,然后把释放的空间放到空闲链表中等待被申请;
    优点:相对于引用计数算法,对于互相引用的对象可以清除;
    缺点:部分情况下,释放掉的空间地址是不连续的导致空间碎片化,浪费空间;
    3.3 标记整理算法
    标记整理可以看做是标记清除的增强操作;标记阶段的操作与标记清除算法一致,多了一部分内容:在清除阶段会先执行整理,移动对象的位置;
    优点:减少碎片化空间;
    缺点:不会立即回收垃圾对象;
    4 V8引擎
    V8是一款主流的JavaScript执行引擎;特点:采用即时编译;内存设置上限(64位1.5G,32位800M);
    4.1 V8垃圾回收策略
    采用分代回收的思想;把当前内存空间分为两类:新生代、老生代;对于不同的对象采用不同的算法;
    常用的GC算法:
    分代回收
    空间复制
    标记清除
    标记整理
    标记增量
    4.2 如何回收新生代对象
    V8内存空间分两部分,小空间用于存放新生代对象(32M|16M);新生代对象是指存活时间较短的对象;
    回收过程采用复制算法加标记整理;新生代空间等分为两个空间from(使用空间),to(空闲空间);
    首先把活动对象存储在From空间,等空间使用达到一定程度,就开始使用GC算法中的标记整理后,将From中的活动对象拷贝到To空间;拷贝完成后From空间的活动对象就有了备份,便开始回收操作,直接释放From空间,之后From和To空间互换,这样就完成了空间的释放和回收操作;
    在拷贝过程中如果发现了一轮GC后仍存活的新生代或者To空间的使用率达到25%,就会将存活的新生代对象或者To里面的活动对象移动到老生代(晋升);
    4.3 如何回收老生代
    老生代区域有内存大小限制(64位1.4G,32位700M);
    老生代对象是指当前存活时间较久的对象(如全局作用域下的一些对象,闭包内部的一些对象);
    回收过程采用标记清除、标记整理、增量标记算法
    首先使用标记清除完成垃圾空间的回收;当新生代对象往老生代对象移动的过程中,如果老生代的空闲空间不足以存储移动过来的新生代对象时,就触发标记整理,会对碎片空间进行回收;最后采用增加标记的方法进行效率优化;
    垃圾回收的时候会阻塞程序的执行,所以将垃圾回收分片执行;
    标记增量,就是将一整段的垃圾回收操作分成多个小步骤,好让程序执行和垃圾回收交替执行;
    无论哪种算法,都会先执行对象的遍历和标记动作(老生代),遍历的过程要做标记,但是标记不一定要一次做完,因为有直接可达和间接可达;在标记完直接可达对象后,先暂停垃圾标记让程序继续执行一段,然后对于二级的可达对象也进行标记,然后继续交替执行,最后完成标记以后开始回收和释放了,完成后程序继续执行;虽然程序间隔执行,但是对于最大1.5G的采用非增量回收的时间,V8也不会超过1s;
    4.4 新老对比
    新生代更像是用空间换时间;因为采用复制算法,每时每刻都会有一个空闲空间存在;但是新生代本来空间较小,空闲空间就更小,这部分的空间浪费相对于时间的节约,性价比更高;
    老生代不适合复制算法,因为存放数据多,复制的话消耗时间更多
    5 performance工具
    GC的目的是为了实现内存空间的良性循环;良性循环的基础是合理使用;
    performance工具提供了多种监控方式,方便时刻关注内存是否合理;
    5.1 使用步骤
    打开浏览器输入网址
    进入开发者面板,选择performance/性能
    开启录制,在地址栏回车
    执行用户行为,一段时间后停止录制
    分析界面中记录的内存信息
    5.2 内存问题的体现(网络正常)
    页面出现延迟加载和经常性的暂停;
    页面持续性出现糟糕的性能;
    页面的性能随着时间延长越来越差;
    5.3 监控内存的几种方式
    界定内存问题的标准
    内存泄漏:内存使用持续升高
    内存膨胀: 在大多数设备上都存在性能问题
    频繁的垃圾回收:通过内存变化图进行分析
    监控内存的几种方式
    浏览器任务管理器
    timeline时序图记录
    堆快照查找分离DOM
    判断是否存在频繁的垃圾回收
    浏览器任务管理器监控内存
    shift + esc,调出任务管理器
    列表中右键勾选javascript内存
    前面的内存列表示DOM占的内存,如果持续升高,说明一直有DOM操作;JavaScript内存列表示js的堆,实时内存表示所有可达对象正在使用的内存,如果持续升高,说明一直在创建新对象或者现有对象一直在增长;
    该工具只能说明有问题,但是无法定位具体问题
    timeline记录内存
    开发者工具中性能,然后录制开始,等稳定后录制结束;勾选内存,在对应的timeline里面就可以看到具体的内存变化了;
    5.4 使用堆快照查找分离DOM
    界面元素都存活在DOM树上;DOM节点分为垃圾对象和分离DOM;
    如果当前的DOM节点在DOM树上已经脱离,而且js代码中也没有引用的则视为垃圾;
    如果当前的DOM节点在DOM树上已经脱离,但是在js代码中有引用则为分离DOM;这种情况就是内存泄漏;
    打开浏览器开发者面板,找到内存,分析类型选择堆快照,执行某操作后点击快照按钮,点击生成的快照,在里面查找deta,看是否有分离DOM;如果有的话,找到对应的代码,清空节点引用;
    5.5 判断是否存在频繁GC
    GC工作时应用程序是停止的,频繁且过长的GC会导致应用假死;用户在使用的过程中会感知程序卡顿;
    标志:Timeline中频繁的上升下降;任务管理器中数据频繁的增加减少;
    6 JavaScript性能
    6.1 如何精准测试JavaScript性能
    本质上就是采集大量的执行样本进行数学统计和分析;
    6.2 jsperf工具运用
    https://jsperf.com
    使用Github账号登录
    填写个人信息(非必须)
    填写详细的测试用例信息(title、slug)
    填写准备代码
    填写必要有setup和teardown代码
    填写测试代码片段
    6.3 慎用全局变量
    全局变量定义在全局上下文,是所有作用域的顶端;
    全局执行上下文一直存在于上下文执行栈,直到程序退出;
    如果局部作用域中有全局同名变量,则可能造成遮蔽和污染;
    6.4 缓存全局变量
    将使用中无法避免的全局变量缓存到局部

    1. var i, str;
    2. for(i = 0;i<1000;i++) {
    3. str += i
    4. }
    5. // slower
    6. let str =''
    7. for(let i=0;i<1000;i++) {
    8. str += i;
    9. }
    10. // faster

    6.5 通过原型对象添加附加方法
    在原型对象上添加实例对象需要的方法比直接定义在构造函数中,性能更优

    1. const fn1 = function () {
    2. this.foo = function () {
    3. console.log('1111');
    4. };
    5. };
    6. let f1 = new fn1();
    7. // slower
    8. const fn2 = function () {};
    9. fn2.prototype.foo = function() {
    10. console.log('1111')
    11. }
    12. let f2 = new fn2();
    13. // faster

    6.6 避开闭包陷阱
    闭包使用不当很容易出现内存泄漏
    闭包变量手动置null;
    6.7 避免属性访问方法使用
    JavaScript的面向对象
    JavaScript不需要属性的访问方法,所有属性都是外部可见的
    使用属性访问方法只会增加一层重定义,没有访问的控制力;
    通过成员属性访问方法获取成员相比直接获取成员性能更低;

    1. function Person1() {
    2. this.name= 'ss';
    3. this.getName = function() {
    4. return this.name;
    5. }
    6. }
    7. const p1 = new Person1();
    8. const a = p1.getName();
    9. // slower
    10. function Person2() {
    11. this.name= 'ss';
    12. }
    13. const p2 = new Person2();
    14. const b = p2.name;
    15. // faster

    6.8 for循环优化
    for循环数组的长度提前获取,每次判断条件的时候不用重复获取,在数组较大时优化明显

    1. const arr = [1,2,3,4];
    2. for(let i; i<arr.length;i++) {
    3. console.log(arr[i])
    4. }
    5. for(let i,len=arr.length; i<len;i++) {
    6. console.log(arr[i])
    7. }

    6.9 选择最优的循环方式
    同样的数组全部循环完毕,forEach表现更好,之后是for,之后是for in

    1. const arr = [1,2,3,4];
    2. arr.forEach(item => {
    3. console.log(item)
    4. });
    5. for(let i,len=arr.length; i<len;i++) {
    6. console.log(arr[i])
    7. }
    8. for(let item in arr) {
    9. console.log(item)
    10. }

    6.10 文档碎片优化节点添加
    DOM节点的添加操作必然有回流和重绘,在一个for循环里面频繁通过appendchild添加,比先创建一个文档碎片在文档碎片里面添加之后把文档碎片appendchild进dom,性能要高;(document.createDocumentFragment)

    1. for (let i = 0; i < 3; i++) {
    2. const op = document.createElement('p')
    3. op.innerHTML = i;
    4. document.appendChild(op);
    5. }
    6. // slower
    7. const fragEle = document.createDocumentFragment();
    8. for (let i = 0; i < 3; i++) {
    9. const op = document.createElement('p')
    10. op.innerHTML = i;
    11. fragEle.appendChild(op);
    12. }
    13. document.body.appendChild(fragEle);
    14. // faster

    6.11 克隆优化节点操作
    当想要新增节点的时候,先复制一个已有的类似节点,然后稍作修改,这样比直接创建要性能高;(cloneNode方法)

    1. // html中已经有一个id为example的p标签
    2. for (let i = 0; i < 3; i++) {
    3. const op = document.createElement('p')
    4. op.innerHTML = i;
    5. document.appendChild(op);
    6. }
    7. // slower
    8. const oldP = document.getElementById('example')
    9. for (let i = 0; i < 3; i++) {
    10. const newP = oldP.cloneNode(false)
    11. op.innerHTML = i;
    12. document.appendChild(op);
    13. }
    14. // faster

    6.12 直接量替换new Object

    1. const a1 = [1,2,3,4] // faster
    2. const a2 = new Array(4); // slower
    3. a2[0] = 1;
    4. a2[1] = 2;
    5. a2[2] = 3;
    6. a2[3] = 4;

    7 JS性能优化
    7.1 JSBench 使用 jsbench,方法类似于jsperf
    7.2 堆栈中的js执行过程
    以下面闭包函数的执行过程为例

    1. let a = 10;
    2. function foo(b) {
    3. let a = 2;
    4. function bar(c) {
    5. console.log(a + b + c);
    6. }
    7. return bar;
    8. }
    9. let fn = foo(2);
    10. fn(3);

    首先代码执行的时候会先生成一个执行环境栈ECStack:
    代码开始执行,首先会在执行环境栈中创建一个全局上下文EC(G):
    首先有个变量对象VO:
    执行第1行代码 a=10,原始类型直接存放到栈内存当中的;
    第2行:foo = ?非原始类型,就要在重新开辟出一块堆内存来存对象(堆区),地址16进制,假设叫AB1;
    AB1中存储的内容有:function foo(){….},name: // 函数名称,length: //形参个数
    此时foo=?的结果就是foo的地址,foo=AB1, 函数创建的时候它会有自己的作用域[[scope]] VO
    第9行: fn = ?此时遇到了函数的调用
    当遇到foo的调用的时候,就会在当前环境栈中创建一个新的函数执行上下文(EC(foo))
    EC(foo)里面
    首先确定this指向,虽然本次调用没遇到,this=window;
    然后初始化作用域链<自身作用域foo.ao(活动对象), VO>
    AO:
    arguments: {0: 2};
    b = 2;
    第3行 a = 2;
    第4行 bar=? 引用类型,开辟堆内存,比如叫AB2
    AB2 中存储的内容: function bar(){…}, name: bar, length:1…
    此时 bar = AB2, 同时它有自己的作用域[[scope]] foo.AO
    第7行 return,此时在外部EC(G)里面,fn就有了结果
    按常规,此时foo执行完毕以后,空间会被回收,但是bar在外部有引用,产生闭包,此时空间不能回收
    EC(G):
    第9行 fn=AB2
    第10行 fn(3)
    函数重新执行的时候,会重现开辟一个执行上下文,EC(bar)

    EC(bar)里面:
    首先确定this指针 this = window;
    然后作用域链
    AO:
    arguments: {0: 3};
    c = 3;
    到 a + b + c, c已知,a 当前作用域没有,然后跟着作用域链往上找foo.ao里面 a=2, b 也没有,同样在foo.ao里面找着
    计算结果 a =2, b=2, c=3, a +b + c= 7;
    第5行 console.log(7);
    执行完毕,看要不要回收,发现没引用,就回收,这个叫出栈;

    • 垃圾回收,栈内存由主线程回收,堆内存由GC垃圾回收机制来回收;

    图示(来源于教材)
    1608100212(1).jpg
    8 减少判断层级
    多层if…else嵌套的时候,可以通过减少层级来优先性能

    1. function fn1(part, chapter) {
    2. const parts = ['JS', 'TS', 'VUE', 'REACT', 'ANGULAR'];
    3. if (part) {
    4. if (parts.includes(part)) {
    5. console.log('属于当前模块');
    6. if (chapter > 6) {
    7. console.log('付款先');
    8. }
    9. }
    10. } else {
    11. console.log('请确认模块信息');
    12. }
    13. }
    14. fn1('JS', 7);
    15. function fn2(part, chapter) {
    16. const parts = ['JS', 'TS', 'VUE', 'REACT', 'ANGULAR'];
    17. if (!part) {
    18. console.log('请确认模块信息');
    19. return;
    20. }
    21. if (!parts.includes(part)) return;
    22. console.log('属于当前模块');
    23. if (chapter > 6) {
    24. console.log('付款先');
    25. }
    26. }
    27. fn2('JS', 7);

    9 减少作用链查找层级

    1. // 空间换时间
    2. // var a = 'sfs';
    3. // function fn() {
    4. // a = 'ddd';
    5. // function f1() {
    6. // var b = 'sff';
    7. // console.log(a)
    8. // console.log(b)
    9. // }
    10. // f1();
    11. // }
    12. // fn();
    13. var a = 'sfs';
    14. function fn() {
    15. var a = 'ddd';
    16. function f1() {
    17. var b = 'sff';
    18. console.log(a)
    19. console.log(b)
    20. }
    21. f1();
    22. }
    23. fn();

    10 减少数据读取次数

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8" />
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    6. <title>减少数据访问次数</title>
    7. </head>
    8. <body>
    9. <button id="skip" class="skip"></button>
    10. <script>
    11. const btn = document.getElementById('skip');
    12. // function hasClass(ele, glass) {
    13. // return ele.className == glass;
    14. // }
    15. // console.log(hasClass(btn, 'skip'));
    16. function hasClass(ele, glass) {
    17. const eleGlass = ele.className;
    18. return eleGlass == glass;
    19. }
    20. console.log(hasClass(btn, 'skip'));
    21. </script>
    22. </body>
    23. </html>

    11 字面量与构造式

    1. // let fn = () => {
    2. // let obj = new Object()
    3. // obj.name = 'Jason'
    4. // obj.age = 18
    5. // obj.sex = 'man'
    6. // return obj
    7. // };
    8. let fn =() => {
    9. let obj = {
    10. name: 'Jason',
    11. age: 18,
    12. sex: 'man',
    13. }
    14. return obj;
    15. }
    16. console.log(fn())
    17. var str1 = '哈哈哈'
    18. var str2 = new String('哈哈哈')
    19. console.log(str1)
    20. console.log(str2)

    12 减少循环体中的活动

    1. function fn() {
    2. var i
    3. var arr = ['Jason', 18, 'live for front']
    4. for(i=0;i<arr.length;i++) {
    5. console.log(arr[i])
    6. }
    7. }
    8. function fn() {
    9. var i
    10. var arr = ['Jason', 18, 'live for front']
    11. var len = arr.length
    12. for(i=0;i<len;i++) {
    13. console.log(arr[i])
    14. }
    15. }
    16. function fn() {
    17. var arr = ['Jason', 18, 'live for front']
    18. var len = arr.length
    19. while(len--){
    20. console.log(arr[len])
    21. }
    22. }
    23. fn();

    13 减少声明及语句数

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8" />
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    6. <title>减少声明及语句</title>
    7. </head>
    8. <body>
    9. <div id="box"></div>
    10. <script>
    11. const box = document.getElementById('box');
    12. function fn(ele) {
    13. var w = ele.clientWidth;
    14. var h = ele.clientHeight;
    15. return w * h;
    16. }
    17. function fn(ele) {
    18. return ele.clientWidth * ele.clientHeight;
    19. }
    20. fn();
    21. function f1() {
    22. var a = 'sfsf';
    23. var b = 11;
    24. var c = 'gssf';
    25. return a + b + c;
    26. }
    27. function f2() {
    28. var a = 'sfsf',
    29. b = 11,
    30. c = 'gssf';
    31. return a + b + c;
    32. }
    33. </script>
    34. </body>
    35. </html>

    这种思路跟之前说的缓存变量有冲突?代码运行之前,会有个编译的过程,虽然js是脚本语言,但是在执行之前还是有很短暂的编译过程;这时候,类似缓存多个值的这种方法,就会有更多的语句,另外,js引擎在做词法分析的时候,遇到关键字,会按照一定的规则进行拆分,把内容变成词法单元,然后再做语法分析,组成AST语法树,有了语法树再去转成代码,最后才去执行
    14 惰性函数与性能
    惰性函数,函数的执行内容需要环境判断或者不变量的判断,如果后续相同的连续调用的时候每次都要进行判断就会造成浪费,所以在第一次执行判断以后,就给函数重新赋值;函数被改写,下次执行同样的函数时就没了判断的过程,直接执行;

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8" />
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    6. <title>惰性函数与性能</title>
    7. </head>
    8. <body>
    9. <button id="btn">点击测试</button>
    10. <script>
    11. var btn = document.getElementById('btn');
    12. function foo() {
    13. console.log(this);
    14. }
    15. // function addEvent(obj, type, fn) {
    16. // if (obj.addEventListener) {
    17. // obj.addEventListener(type, fn, false);
    18. // } else if (obj.attachEvent) {
    19. // obj.attachEvent('on' + type, fn);
    20. // } else {
    21. // obj['on' + type] = fn;
    22. // }
    23. // }
    24. function addEvent(obj, type, fn) {
    25. if (obj.addEventListener) {
    26. addEvent = (obj, type, fn) => obj.addEventListener(type, fn, false);
    27. } else if (obj.attachEvent) {
    28. addEvent = (obj, type, fn) => obj.attachEvent('on' + type, fn);
    29. } else {
    30. addEvent = (obj, type, fn) => obj['on' + type] = fn;
    31. }
    32. return addEvent;
    33. }
    34. addEvent(btn, 'click', foo)
    35. addEvent(btn, 'click', foo)
    36. addEvent(btn, 'click', foo)
    37. </script>
    38. </body>
    39. </html>

    15 事件委托

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    6. <title>事件委托</title>
    7. </head>
    8. <body>
    9. <ul id="outer">
    10. <li>第一个</li>
    11. <li>第二个</li>
    12. <li>第三个</li>
    13. </ul>
    14. <script>
    15. // var list = document.querySelectorAll('li');
    16. // function showText (ele) {
    17. // console.log(ele.target.innerHTML)
    18. // }
    19. // for(let item of list){
    20. // item.onclick = showText
    21. // }
    22. var ul = document.getElementById('outer');
    23. ul.addEventListener('click', showText, true);
    24. function showText(ele) {
    25. var obj = ele.target;
    26. if(obj.nodeName.toLowerCase()==='li') {
    27. console.log(obj.innerHTML)
    28. }
    29. }
    30. </script>
    31. </body>
    32. </html>