Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yygmind opened this issue Dec 4, 2018 · 21 comments
Open

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

yygmind opened this issue Dec 4, 2018 · 21 comments

Comments

@yygmind
Copy link
Owner

yygmind commented Dec 4, 2018

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

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


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

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

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

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

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

栈数据结构

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

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

堆数据结构

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

队列

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

变量的存放

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

  • 1、基本类型 --> 保存在内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:Undefined、Null、Boolean、Number 、String和Symbol
  • 2、引用类型 --> 保存在内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。

在计算机的数据结构中,栈比堆的运算速度快,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: '前端开发' }

  • 对于问题1,a、b都是基本类型,它们的值是存储在栈中的,a、b分别有各自独立的栈空间,所以修改了b的值以后,a的值并不会发生变化。
  • 对于问题2,a、b都是引用类型,栈内存中存放地址指向堆内存中的对象,引用类型的复制会为新的变量自动分配一个新的值保存在变量对象中,但只是引用类型的一个地址指针而已,实际指向的是同一个对象,所以修改b.name的值后,相应的a.name也就发生了改变。
  • 对于问题3,首先要说明的是null是基本类型,a = null之后只是把a存储在栈内存中地址改变成了基本类型null,并不会影响堆内存中的对象,所以b的值不受影响。

内存空间管理

JavaScript的内存生命周期是

  • 1、分配你所需要的内存
  • 2、使用分配到的内存(读、写)
  • 3、不需要时将其释放、归还

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堆内存

进阶系列目录

  • 【进阶1期】 调用堆栈
  • 【进阶2期】 作用域闭包
  • 【进阶3期】 this全面解析
  • 【进阶4期】 深浅拷贝原理
  • 【进阶5期】 原型Prototype
  • 【进阶6期】 高阶函数
  • 【进阶7期】 事件机制
  • 【进阶8期】 Event Loop原理
  • 【进阶9期】 Promise原理
  • 【进阶10期】Async/Await原理
  • 【进阶11期】防抖/节流原理
  • 【进阶12期】模块化详解
  • 【进阶13期】ES6重难点
  • 【进阶14期】计算机网络概述
  • 【进阶15期】浏览器渲染原理
  • 【进阶16期】webpack配置
  • 【进阶17期】webpack原理
  • 【进阶18期】前端监控
  • 【进阶19期】跨域和安全
  • 【进阶20期】性能优化
  • 【进阶21期】VirtualDom原理
  • 【进阶22期】Diff算法
  • 【进阶23期】MVVM双向绑定
  • 【进阶24期】Vuex原理
  • 【进阶25期】Redux原理
  • 【进阶26期】路由原理
  • 【进阶27期】VueRouter源码解析
  • 【进阶28期】ReactRouter源码解析

交流

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

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

@huangd-d
Copy link

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

@huangd-d
Copy link

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

@jjeejj
Copy link

jjeejj commented Dec 13, 2018

先提个错别字哦
image

@zfamz
Copy link

zfamz commented Dec 18, 2018

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
Copy link

从右往左

@rocky-191
Copy link

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

@zhuoyu1994
Copy link

点运算优先级最高

@Greency
Copy link

Greency commented Jan 19, 2019

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

@MR-zhou-xx
Copy link

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

@shirleyMHao
Copy link

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

@defypro
Copy link

defypro commented Feb 18, 2019

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

@ghost
Copy link

ghost commented Feb 19, 2019

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

@luoheqp
Copy link

luoheqp commented Feb 24, 2019

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

@liam61
Copy link

liam61 commented Mar 13, 2019

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

@Hanzvii
Copy link

Hanzvii commented Mar 17, 2019

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

@YeaseonZhang
Copy link

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

@chechengpeng
Copy link

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

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

@IWSR
Copy link

IWSR commented May 21, 2019

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

@liangmuren
Copy link

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

@zhoubichuan
Copy link

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

image

@js100cc
Copy link

js100cc commented Jun 20, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests