面试官:JS中变量定义时内存有什么变化?

简介: 面试官:JS中变量定义时内存有什么变化?

前言

前段时间看面试题发现了一道比较有意思的题目

var obj = {
  num1: 117
}
var res = obj;// -----------1
obj.child = obj = { num2: 935 };// -----------2
var x = y = res.child.num2;// ----------3
console.log(obj.child);
console.log(res.num1);
console.log(y);

原题是我之前的:面试题中的Q9

其中我针对问题给出了我的看法,也借这篇文章复习一下变量对内存的操作

概念

JavaScript引擎是使用堆内存和栈内存来管理内存的:

栈内存用于存储程序的函数调用,变量声明以及一些占用小的变量值,如布尔,部分整数等,它们的生命周期受到函数的调用和退出以及变量的作用域的控制。当函数被调用或者变量创建时,相关的变量和函数调用会被压入栈内存,如果函数退出或者变量作用域销毁,相关的变量和函数就会从栈内存中弹出。

堆内存的作用是存储变量值,如字符串,对象,数组及函数,它们的生命周期受到JavaScript垃圾回收机制的控制,当不再需要这些变量时,垃圾回收机制会将它们销毁。

分析

我们以一个最简单的代码开始分析

var num = 10;
console.log(num)

上述代码中在内存发生了什么?

  1. 首先,程序创建了一个新的变量num
  2. 然后,程序分配了10给变量num。数字(小整数)是存储在栈内存中的。
  3. 然后,程序调用了console.log(num)函数,该函数从栈内存中访问变量num,并将其值打印到控制台。
  4. 最后,程序结束,栈内存被释放,因此变量num和它的值也将被删除。

短短两句代码,JS引擎竟发生如此多的操作;

那么如果我们操作字符串呢?

var str = "hello";
console.log(str)

这段代码在内存中发生了什么?

  1. 在堆上为字符串'hello'分配内存,比如地址为0x0001。
  2. 将变量str压入栈内存中,并引用0x0001,如下图
  3. 然后,程序调用了console.log函数,该函数从栈内存中访问变量str,并将其值打印到控制台。
  4. 最后程序结束了,栈中的变量及地址删除,堆中的0x0001在引用被释放之前一直存在的,如果没有引用,则会被垃圾回收机制销毁

了解了上述过程,我们来看点好玩的

var obj = {
    num:10
};
var obj2 = {
    num:20
};
// ----------1
obj = obj2 // ----------2
obj2.num++ // ----------3
console.log(obj)

执行上述代码,对内存有何操作?

  1. 首先堆内存分配两个空间,假定内存地址是0001和0002,二者分别在堆中是
    0x0001:{ num:10 }和0x0002:{ num:20 }
  2. 在栈中新增两个变量:obj和obj2,程序将上述两个地址分配给这两个变量,来到代码中的
    第1步,如下图
  3. 第2步,修改栈中obj的值,将它改成obj2的地址,此时obj和obj2指向同一块堆内存地址
  4. 来到第3步,找到obj2的地址0002,使堆中这个值的num数值++,变成了21
  5. 最后在控制台输出{num: 21},程序执行完成,栈中的变量清空,堆中的变量等待JavaScript垃圾回收器进行销毁

进阶思考

提出猜想

让我们回到之前那道面试题,这里我们引入了一个新的概念:连续赋值运算

var obj = {
  num: 10,
};
obj.child = obj = { num: 20 };

为了更清晰看出整个流程,我使用一个代理来监听obj的变化
代理工厂函数(监听对象读写变化)

const proxyFactory = (target, opts) => {
  return new Proxy(target, {
    set: (target, propertyKey, value, _) => {
      console.log(target, propertyKey, value);
      console.log("执行了set");
      return Reflect.set(target, propertyKey, value);
    },
    get: (target, property, _) => {
      console.log(target, property);
      console.log("执行了get");
      return Reflect.get(target, property);
    },
    ...opts,
  });
};

我们将上面的连续赋值代码放在代理中,对属性的赋值以及取值进行监听

const __proxy = {
  obj: { num: 10 },
};
var proxy = proxyFactory(__proxy);
proxy.obj.child = proxy.obj = { num: 20 };

执行上述代码,结果如下

整句代码是先执行了读取,再进行写入

那么我们可以提出下面代码的猜想(注意:此处我们监听的是proxy.obj的读写,所以proxy.obj.child = __temp 这行代码实际上相对于 proxy.obj 是读取操作)

const __temp = { num: 20 }
proxy.obj.child = __temp;// 读取obj.child
proxy.obj = __temp;// 写入obj

证明猜想

如何证明上述猜想?

在这我们可以引入一个新的变量 obj2 用于记录 "前一个obj",于是我们得到了下面的代码

var obj = {
  num: 10,
};
var obj2 = obj;// -----1
obj.child = obj = { num: 20 };// -----2
obj.num++;// -----3
console.log(obj, obj2);

在控制台中打印如下

那么如果我们把代码换成以下代码效果是怎样的?

var obj = {
  num: 10,
};
var obj2 = obj; // -----1
var __temp = { num: 20 };
obj.child = __temp;
obj = __temp; // -----2
obj.num++; // -----3
console.log(obj, obj2);

效果和之前的一致

得出结论

我们针对上述代码对堆栈的操作可以做以下总结:

  1. 第1步,在堆栈中存放了两个变量,都指向0001内存
  2. 第2步,分为两个步骤:1.在堆内存0001数据中的新增child属性,将其指向堆内存0002数据;2.将obj重新指向(赋值)0002
  3. 第3步,修改obj.num,即修改了0002中的num值
  4. 最后输出结果,垃圾回收...

以上就是文章所有内容,以此记录一下这道面试题的详细分析过程

写在最后

对文章有任何问题欢迎在评论区或私信讨论

感谢你看到这里,如果你从文章中有收获,还请支持一下,谢谢!


相关文章
|
1月前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
1月前
|
JavaScript 前端开发 Java
避免 JavaScript 中的内存泄漏
【10月更文挑战第30天】避免JavaScript中的内存泄漏问题需要开发者对变量引用、事件监听器管理、DOM元素操作以及异步操作等方面有深入的理解和注意。通过遵循良好的编程实践和及时清理不再使用的资源,可以有效地减少内存泄漏的风险,提高JavaScript应用程序的性能和稳定性。
|
21天前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
171 77
|
21天前
|
监控 JavaScript
选择适合自己的Node.js内存监控工具
选择合适的内存监控工具是优化 Node.js 应用内存使用的重要一步,它可以帮助你更好地了解内存状况,及时发现问题并采取措施,提高应用的性能和稳定性。
114 76
|
21天前
|
监控 JavaScript 数据库连接
解读Node.js内存监控工具生成的报告
需要注意的是,不同的内存监控工具可能会有不同的报告格式和内容,具体的解读方法可能会有所差异。因此,在使用具体工具时,还需要参考其相关的文档和说明,以更好地理解和利用报告中的信息。通过深入解读内存监控报告,我们可以不断优化 Node.js 应用的内存使用,提高其性能和稳定性。
100 74
|
23天前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
112 62
|
23天前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
118 52
|
19天前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
63 31
|
1月前
|
JSON JavaScript 前端开发
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
本文介绍了JSONP的工作原理及其在解决跨域请求中的应用。首先解释了同源策略的概念,然后通过多个示例详细阐述了JSONP如何通过动态解释服务端返回的JavaScript脚本来实现跨域数据交互。文章还探讨了使用jQuery的`$.ajax`方法封装JSONP请求的方式,并提供了具体的代码示例。最后,通过一个更复杂的示例展示了如何处理JSON格式的响应数据。
35 2
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
|
23天前
|
缓存 监控 JavaScript
避免在Node.js中出现内存泄漏
总之,避免内存泄漏需要在开发过程中保持谨慎和细心,遵循最佳实践,不断优化和改进代码。同时,定期进行内存管理的检查和维护也是非常重要的。通过采取这些措施,可以有效地降低 Node.js 应用中出现内存泄漏的风险,确保应用的稳定和性能。