美文网首页
通过两个面试题解析JS底层原理

通过两个面试题解析JS底层原理

作者: 哎哟码呀 | 来源:发表于2020-03-29 20:32 被阅读0次

    先来看一个例子:

    setTimeOut(()=>{
        console.log('set1');
    });
    new Promise((resolve,reject)=>{
        console.log('p1')
      resolve();
    }).then(()=>{
        console.log('then1');
    })
    console.log('1');
    

    问:执行顺序?
    答案:p1 --> 1 --> then1 --> set1 ;
    new Promise() 的时候是同步执行。

    微任务:

    Promise,process.nextTick

    宏任务:

    整体的Scirpt代码,setTimeOut , setInterval

    看看如下例子:


    image.png

    答案:pr1 ---> 2 ---> then1 ---> set1 --->then2 ---> then 4 ---> set2

    解析过程:
    确保微任务执行完了才去执行宏任务。

    一 、js执行机制

    image.png

    另一个例子:
    问:下面的代码执行会出现什么样的结果?

    var b = [];
    for(var i=0;i<15;i++){
        b.push(new Array(20*1024*1024));
    }
    

    结果,js内存不够了,V8引擎的内存溢出,崩溃了。


    image.png

    so,V8引擎内存有多大?


    image.png
    image.png
    疑问:为什么64位的系统就只设置1.4G?设计的这么小?

    原因:js回收的时候会中断执行。js回收100MB内存,则需要3ms。所以不能设计太大,因为会整个中断掉。

    疑问:为什么1.4GB 是比较合适的?

    原因:js的设计是为了跑前端脚本的,不是持续的,执行完了就没了。而不像后端,持续执行的,所以后端要大一些

    ,所以前端1.4GB是比较合适的。而且是足够了。

    概念解释:

    新生代:新变量存放的地方

    老生代:没有被回收的老变量存放的地方

    如果新生代要进行复制更改的话,就会被放到老生代里面去。算法:如果新生代的内存占用整个空间的25%的时候就会进行一次复制;

    那么是如何进行复制的?

    新生代有两个空间,当from空间大于25%的时候,会把from空间的活着的变量标记,并且复制到新生代To空间去。然后清空from空间。当To空间大于25%的时候,在此标记活着的变量,复制到from空间去。清空To。

    啥时候新生代的变量会去老生代?

    当新生代的空间大于25%,并且还活着的变量,复制过后,就去老生代空间。

    二、内存如何回收?

    image.png

    当一个变量 既不是全局,也不是局部且失去引用的时候,就会被回收。

    如何查看内存

    浏览器:window.performance
    Node:process.memoryUsage();
    
    image.png

    Node 查看内存:

    打开终端(前提是你安装了node):

    输入 node 进入node环境
    oricess.memoryUsage()
    会显示如下:
    {
        rss:21258240,//总申请到的内存
        heapTotal:5656576,//总堆内存
        heapUsed:3051464,//已经使用的内存
        external:1401021//Node 和 js 不同的这个。因为node的源码是C++写的,可以支配一些内存给C++,所以会有一些额外的内存来给C++使用。
    }
    

    接下来我们做个小测试:

    function getme(){
        var mem = process.memoryUsage();
        var format = function(bytes){
            return (bytes/1024/1024).toFixed(2)+"MB";
        }
        console.log("heapTotal:" + format(mem.heapTotal) + 'heapUsed:'+ format(mem.heapUsed));
    }
    
    var a =[];
    var size = 20*1024*1024;
    function b(){
        var arr1 = new Array(size);
        var arr2 = new Array(size);
        var arr3 = new Array(size);
        var arr4 = new Array(size);
        var arr5 = new Array(size);
    }
    b();
    getme();
    setInterval(()=>{
        a.push(new Array(20*1024*1024));
        getme();
    },1000);
    

    每一秒钟执行一次;我们看下执行结果:


    image.png
    image.png

    直到最后就崩了。。

    梳理下:

    最开始的时候有800多M,因为我们定义了5个局部变量并且没被回收。当到达1400的时候,发现要回收,回收了上面5个数组,当我们到最后,再去回收,发现全局只有一个a变量,无法被回收,所以最后崩溃了。

    //5个全局变量 如下:
    var size = 20*1024*1024;
    var arr1 = new Array(size);
    var arr2 = new Array(size);
    var arr3 = new Array(size);
    var arr4 = new Array(size);
    var arr5 = new Array(size);
    

    三、容易引发内存使用不当的操作

    image.png

    1、反面教材1(滥用全局变量):

    var size = 20*1024*1024;
    var arr1 = new Array(size);
    var arr2 = new Array(size);
    var arr3 = new Array(size);
    var arr4 = new Array(size);
    var arr5 = new Array(size);
    var arr6 = new Array(size);
    var arr7 = new Array(size);
    var arr8 = new Array(size);
    var arr9 = new Array(size);
    var arr10 = new Array(size);
    var arr11 = new Array(size);
    var arr12 = new Array(size);
    var arr13 = new Array(size);
    var arr14 = new Array(size);
    var arr15 = new Array(size);
    

    然后运行,直接报错,内存溢出。
    如何正确的使用全局变量?

    var size = 20*1024*1024;
    var arr1 = new Array(size);
    //使用完后释放
    arr1 = undifined; //释放内存
    

    额外小知识:

    undifined 和 null 的区别:
    null 是个关键字
    undifined 是一个变量,和我们平时定义的 var a,bc 一样
    
    image.png

    我们测试如下代码:

    var size = 20*1024*1024;
    var arr1 = new Array(size);
    arr1 = undifined;
    var arr2 = new Array(size);
    arr2 = undifined;
    var arr3 = new Array(size);
    arr3 = undifined;
    var arr4 = new Array(size);
    arr4 = undifined;
    var arr5 = new Array(size);
    arr5 = undifined;
    var arr6 = new Array(size);
    arr6 = undifined;
    var arr7 = new Array(size);
    arr7 = undifined;
    var arr8 = new Array(size);
    var arr9 = new Array(size);
    var arr10 = new Array(size);
    var arr11 = new Array(size);
    var arr12 = new Array(size);
    var arr13 = new Array(size);
    var arr14 = new Array(size);
    var arr15 = new Array(size);
    

    运行后正常没有报错:


    image.png

    所以,尽量不要定义全局变量,如果一定要定义,那么定义完了,请释放。

    2、反面教材2(缓存不限制):

    缓存一定是全局的,如果不限制,早晚都会崩溃的。尤其是服务端,缓存一定要设置大小。
    比较好的处理方式,给缓存加锁。

    var b = [];
    for(var i=0;i<15;i++){
        b.push(new Array(20*1024*1024));
    }
    

    运行后是崩溃的


    image.png

    改成如下:

    var b = [];
    for(var i=0;i<15;i++){
        if(b.length > 4){
            a.shift();  
        }
        b.push(new Array(20*1024*1024));
    }
    

    运行后正常,避免无限制的缓存造成内存崩溃。

    缓存是你优化的好帮手,如果你一定要用的话,请在缓存上加一个锁。

    3、反面教材3(操作大文件):

    前端上传大文件,如果不处理 会直接卡死,

    解决办法:切片上传;

    使用node读取大文件的时候,如果是你使用

    fs.readFile();  
    

    将会一次性将大文件读取到内存中,文件较大会发生卡顿或者崩溃。

    4、扩展知识点,老生代的回收算法:

    新生代算法,标记活的复制到另一个空间去,牺牲空间,换取时间。

    老生代则不可能,因为有1.4G,老生代则是,标记-删除-整理的操作。

    新生代:标记活着的变量

    老生代:标记死亡变量,当内存大了,删除死亡的变量。整理,

    [ ,1002,, 100]//如果不整理,数组这样的就不会是连续的了,如果不经常整理,数组就没办法继续添加了。数组必须是连续的。通过整理变成[ 1002,100,,],连续的数组。

    5、扩展额外的例子:

    var a ={n:1};
    var b=a;
    a.x = a = {n:2};
    
    console.log(a);
    console.log(b);
    

    问:a,b分别是什么值?

    答案:


    image.png

    解析:

    对象是引用类型的--> 所有的对象复制其实是给了这个对象的引用地址;

    假设 {n:2} 的地址是 1002,{n:1} 的地址 1000

    当var b = a 的时候,a,b的地址都是1000;

    当a.x=a={n:2}的时候,先会执行a.x = a ,这时候x的地址是1000,即为a.x = {n:1,x:1000的地址};

    a又等于{a:2},所以a的地址变成了1002;

    所以打出来的 a = {a:2}, b={n:1,x:{n:2}};

    关注我,学习更多前端原理

    相关文章

      网友评论

          本文标题:通过两个面试题解析JS底层原理

          本文链接:https://www.haomeiwen.com/subject/vxzuuhtx.html