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

2020-03-29  本文已影响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}};

关注我,学习更多前端原理
上一篇下一篇

猜你喜欢

热点阅读