yygmind / blog

我是木易杨,公众号「高级前端进阶」作者,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
https://muyiy.cn/blog/
10.51k stars 1.11k forks source link

【进阶1-3期】JavaScript深入之内存空间详细图解 #14

Open yygmind opened 5 years ago

yygmind commented 5 years ago

本期的主题是调用堆栈,本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,文末点击查看全部文章。

如果觉得本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。


堆栈的内容和执行顺序我就不说了,前面两篇已经介绍过了。

但是今天补充一个知识点:某些情况下,调用堆栈中函数调用的数量超出了调用堆栈的实际大小,浏览器会抛出一个错误终止运行。

对于下面的递归就会无限制的执行下去,直到超出调用堆栈的实际大小,这个是浏览器定义的。

function foo() {
    foo();
}
foo();

现在正式开始今天的主题,内存空间详解

栈数据结构

栈的结构就是后进先出(LIFO),如果读过前面两篇文章应该是相当熟悉了。文中使用乒乓球盒子的结构来解释。

处于盒子中最顶层的乒乓球5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球1,就必须将上面的4个乒乓球取出来,让乒乓球1处于盒子顶层。

堆数据结构

堆数据结构是一种树状结构。它的存取数据的方式与书架和书非常相似。我们只需要知道书的名字就可以直接取出书了,并不需要把上面的书取出来。JSON格式的数据中,我们存储的key-value可以是无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字。

队列

队列是一种先进先出(FIFO)的数据结构,这是事件循环(Event Loop)的基础结构,事件循环我们会在第8期详解介绍。

变量的存放

首先我们应该知道内存中有栈和堆,那么变量应该存放在哪里呢,

在计算机的数据结构中,栈比堆的运算速度快,Object是一个复杂的结构且可以扩展:数组可扩充,对象可添加属性,都可以增删改查。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。所以查找引用类型值的时候先去查找再去查找。

几个问题

问题1:

var a = 20;
var b = a;
b = 30;

// 这时a的值是多少?

问题2:

var a = { name: '前端开发' }
var b = a;
b.name = '进阶';

// 这时a.name的值是多少

问题3:

var a = { name: '前端开发' }
var b = a;
a = null;

// 这时b的值是多少

现在来解答一下,三个问题的答案分别是20‘进阶’{ name: '前端开发' }

内存空间管理

JavaScript的内存生命周期是

JavaScript有自动垃圾收集机制,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,使用a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。

在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在开发中,需要尽量避免使用全局变量。

思考题

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

a.x     // 这时 a.x 的值是多少
b.x     // 这时 b.x 的值是多少

参考

前端基础进阶(一):内存空间详细图解

解读 JavaScript 之引擎、运行时和堆栈调用

JavaScript变量——栈内存or堆内存

进阶系列目录

交流

进阶系列文章汇总:https://github.com/yygmind/blog,内有优质前端资料,觉得不错点个star。

我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!

huangd-d commented 5 years ago

a.x = a = {n: 2};
是a 还保存着对{n:1}的引用, 然后再改变引用到 {n:2}吗? 为什么啊?

huangd-d commented 5 years ago

理解了。 a.x 是已经先指向原对象了。 然后在赋值。

jjeejj commented 5 years ago

先提个错别字哦 image

zfamz commented 5 years ago

a.x = a = {n:2} 执行顺序 a.x -> a = {n:2} -> (a.x)= a (.运算符优先级大于=) a.x 在最开始就已经执行 类似 temp = a.x ; temp = a = {n:2} 注:javascript 执行顺序从右往左的

rocky-191 commented 5 years ago

从右往左

rocky-191 commented 5 years ago

最后a={n:2},b={n=1,x:{n:2}}

zhuoyu1994 commented 5 years ago

点运算优先级最高

Greency commented 5 years ago

关键点在于对象的引用和点运算符的优先级

MR-zhou-xx commented 5 years ago

a.x = undefined b.x = {n:2}

shirleyMHao commented 5 years ago

个人理解:取属性的运算符优先级最高,赋值运算符是右结合性的。 因此执行顺序是 1). 先取出a.x 2). a = {x: 2}, 3). a.x = a 运行第一步时,a还保留有b的引用,因此第三步就相当于b.x = {x: 2} 如有错误请指正

defypro commented 5 years ago

转一篇针对思考题的文章 http://www.cnblogs.com/vajoy/p/3703859.html

ghost commented 5 years ago

a.x = {n: 2}; b = {n: 1}; b.x = undefined;

luoheqp commented 5 years ago

原文 - “将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。” 前面的是 ‘是’ 还是 ‘不是’ ?

liam61 commented 5 years ago

https://www.zhihu.com/question/41220520 看了这个你还不懂的话,你来找我

Hanzvii commented 5 years ago

先执行‘.’,于是a.x = a = {n: 2}可以理解成({ n: 1 }).x = a = { n:2 }

YeaseonZhang commented 5 years ago

循环调用栈溢出,我的浏览器报错是Uncaught RangeError: Maximum call stack size exceeded

chechengpeng commented 5 years ago

这个网站可以查看执行过程 看了再结合

a.x = a = {n:2} 执行顺序 a.x -> a = {n:2} -> (a.x)= a (.运算符优先级大于=) a.x 在最开始就已经执行 类似 temp = a.x ; temp = a = {n:2} 注:javascript 执行顺序从右往左的

IWSR commented 5 years ago

.优先级大于赋值 a.x先声明 随后赋值右到左 a={n:2}返回{n:2}此时内存中b.x指向{n:2}

liangmuren commented 5 years ago

没必要在这里提堆数据结构,堆栈的堆和数据结构的堆是两回事

zhoubichuan commented 5 years ago

画了一个图,不知道是不是这样执行的

image

js100cc commented 4 years ago

原文中:基本类型 --> 保存在栈内存中。callstack frame 一旦 pop up 掉,请问闭包去哪里找数据? P.S. 本文的引子是 callstack, 后面是数据结构... 容易误解