【本周面试题】第4周 - 内存机制相关

一、解释一下内存泄露是什么意思?

2018-12-03 20:57:24

 

内存泄漏指任何对象在您不再拥有或需要他之后,其仍然存在内存中。

也就是程序中分配的堆内存空间没有被及时释放或无法释放,导致的内存占用过多,造成程序运行速度减慢甚至卡顿、崩溃。

在浏览器中识别内存泄漏的方法:

  • 打开开发者工具,选择 Memory
  • 在右侧的Select profiling type字段里面勾选 timeline
  • 点击左上角的录制按钮。
  • 在页面上进行各种操作,模拟用户的使用情况。
  • 一段时间后,点击左上角的 stop 按钮,面板上就会显示这段时间的内存占用情况。

一段”高清“gif展示给你:

 

详见:【进阶1-4期】JavaScript深入之带你走进内存机制

二、堆栈存储数据的特点 相关面试题:

一共给自己出了三道题:

1. 基本类型值的拷贝:

1 var a = 1;
2 var b = a;
3 console.log(b);
4 b = 2;
5 console.log(a);

 

2. 引用类型值的拷贝:(浅拷贝)

1 var c = {
2   n: 1
3 }
4 var d = c;
5 d.n = 3;
6 console.log(c.n);

 

3. 基本类型值的拷贝:【经典】

1 var e = {
2   n: 1
3 }
4 var f = e;
5 e.x = e = { n: 4};
6 console.log(e.x);
7 console.log(f);

 

 

答案和解析:

第一题打印:

> 1

> 1

首先第一个b弹出1,毫无疑问。可以理解成b = a = 1。

实际上他是执行:

var b; 给b创建一块空间

b = a;先查找a的值(1),再赋予给b变量。

然后第二个,依旧弹出1。

之前你理解b和a相等了,b变成2是不是a也要变成2啊?不是的,因为b和a不是一块空间。

通俗点理解,就像是拷贝了a的一个副本给b。此时我们修改(副本)b的值,对a毫无影响。

所以尽管b为2了,a还是1。

 

第二题打印:

> 3

刚看完上边说a和b不影响,再看这里是不是懵逼了。说好的不影响,怎么d.n改成了3,c.n也改成了3呢?

其实啊,这就是堆栈空间存值的不同和拷贝堆内存数据的特点了:

首先要知道,堆栈空间存的值不同:

var c = {}; 定义一个对象c。

js引擎要做的事情如下:

  • var c; //栈内存开辟一块空间放c
  • {n: 1}; // 堆内存开辟一块空间放对象({n: 1}可以当成一本书,书架就是堆空间,就好像在书架上放了一本书)
  • c = {n: 1}; 栈中找到c,赋予对象所在堆内存的地址。(拿书的索书号给了c,并不是书本身给了c。索书号就是对象在堆空间的地址)
  • 然后, var d; // 同c,给d开辟一块空间放栈内存
  • d = c; // 获取c的值(就是那个索书号地址),copy一份给d。d拿到的同样是c的副本。但是这个副本是一个指针,二者同时指向堆空间的{n: 1};

之后的代码,运行d.n = 3。就是把堆内存中唯一一处的那个{n:1}对象的n值给修改了。

此时,堆内存中{n:1}这个对象,现在变成了{n:3}。

 

第三题打印:

> undefined

> 一个对象:展开如下:

{
    n: 1,
    x: {
        n:4
    }
}

 

此题是一个经典面试题,

首先第1-4行,和第二题定义c一个套路。

栈空间存了变量e和指针,堆空间存了对象{n:1};将e的指针再赋予f。

关键是第5行,这里如果你按从左向右顺序理解的话结果应该是:

各种可能

 

但这道题的坑点就在于js不是按照你想的、你阅读的顺序执行的。

首先,js中正确的赋值顺序是从右向左的。

也就是应该先从最后边开始,先将等号右边的赋值给等号左边。

但这道题还有坑点就是运算符的优先级:点比等号的优先级高。

也就是说,先执行的是

e.x = {n : 4}

 

然后执行:

e = { n: 4}

 

首先说先执行的代码执行后的结果

e.x被赋值后,相当于堆内存中,对象{n:1}多了一个属性x,并且值是{n : 4};

此时,e和f的值情况分别是这样的(注意下边的obj1和obj2分别是我自己为了区分对象给命名的)

【本周面试题】第4周 - 内存机制相关_第1张图片

 

然后,执行e = { n : 4}; 此时js引擎要做的事情就是:

开辟一个新的堆空间放新对象obj2 --> {n : 4}

然后将这个新对象的地址重新赋值给e。这一步可以这么理解:

var e = 'obj1的地址';
e = 'obj2的地址';

 

最终e在栈空间的值就是obj2的地址。

【本周面试题】第4周 - 内存机制相关_第2张图片

 

最后,执行console.log(f):

虽然e和obj1切断了关系,但是f还和obj1有关系。所以f打印出来的就是整个obj1。

思考:e.x为什么是undefined而不报错? 

这里e被重新指向obj2后,obj2里边并没有x值,所以e.x是不存在的。

正常情况下,找不到一个变量会报错: 变量 is not defined。但是为什么对象的属性值不存在时只是输出undefined呢?

这一点,我们获取e.x会去对象身上找,如果不存在x,会去对象的原型链上找,一直找到最顶端,都找不到时就返回“undefined”

 

三、引擎和作用域的对话

2018-12-05  21:14:46

观察下边的这段代码,进行一次引擎和作用域的对话:

1 function foo(a){
2   var b = a;
3   return a + b;
4 }
5 var c = foo(2);

找出里边的几处RHS查询、几处LHS查询?分别是哪里?

 

 

答案:

【本周面试题】第4周 - 内存机制相关_第3张图片

 

四、垃圾回收机制 

背诵,具体文案见本周主题阅读第四条

 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。

 

 

五、从内存看,null和undefined的本质区别是什么?

null也是js基本类型之一,它是一个字面量,特指对象的值未设置。表示缺少的标识,指示变量未指向任何对象。

undefined也是js基本类型之一,是全局对象的一个属性。声明一个变量,未进行初始化时,这个变量的值就是undefiend,

给一个全局变量赋值null,相当于将这个变量的指针对象及值清空,如果是给对象的属性赋值null,或者局部变量赋值为null,相当于给这个属性分配一块空的内存空间,然后值是null。

js会回收全局变量为null的对象。

 

六、ES6语法中,const声明一个只读的常亮,那为什么下面可以修改const的值

const foo = {};

// 为foo添加一个属性,可以成功
foo.prop = 123;
for.prop; // 得到123

// 将foo指向另一个对象,报错:
foo = {};//TypeError: 'foo' is read-only

  

const实际上并不是指变量的值不可改动,而是变量指向的那个内存地址所保存的数据不得改动。

对于简单类型的数据(string、number、boolean、null、undefined),值就保存在变量指向的那个内存地址,因此等同于常量

但对于引用类型的数据(复合类型的数据:对象、数组),变量指向的内存地址,保存的是一个指向堆内存实际数据的指针,const只能保证这个指针

是固定的(即指向的地址不变)。至于这个地址对应的堆内存中数据结构的变化,他是控制不了的。

因此,在上例中,为foo添加属性,实际上操作的是堆内存中foo对象的数据结构,const管不着,

而改变foo的指针,指向另一个对象,const是不答应的。

 

七、前端中内存泄露的几种情况:

(见本周阅读主题篇第六条)

 

 

setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)

 

 

八、扩展题:浅拷贝和深拷贝

学了堆栈内存空间,应该就理解了什么叫简单数据类型存在栈内存,复杂数据类型存在堆内存了。

然后面试中,经常会问、业务中也经常会遇到的问题就是深浅拷贝的问题了。

栈内存中简单数据类型直接拷贝就能得到一个副本,但是复杂数据类型的拷贝如果也想得到一个副本,就需要深拷贝了。

具体源码见文章《js中的浅拷贝和深拷贝》

 

你可能感兴趣的:(【本周面试题】第4周 - 内存机制相关)