《JavaScript高级程序设计 第四章》通过内存空间探究ES中基本类型和引用类型的异同

在开发过程中我们对基本类型和引用类型的一些特性有所了解:

基本类型是按值访问的,所以我们可以尽情地赋值修改不用担心有副作用;

而引用类型是按引用访问的,所以这会导致当我们操作一个复制自另一个引用类型的值时,使得另一个值也发生变化。

说起来可能有点拗口,又是复制又是赋值的,还有什么按值访问按引用访问,看一下demo:


// 声明两个基本类型的变量,simpleVariable2复制自simpleVariable1

let simpleVariable1 = 'simpleVariable1';

let simpleVariable2 = simpleVariable1;

// 打印出两个变量的值,没有什么问题

console.log(simpleVariable1);  // simpleVariable1

console.log(simpleVariable2);  // simpleVariable1

// 修改 simpleVariable2 的值,一切如预期

simpleVariable2 = 'simpleVariable2';

console.log(simpleVariable1);  // simpleVariable1

console.log(simpleVariable2);  // simpleVariable2

// 声明两个复杂类型的变量,complexVariable2复制自complexVariable1

let complexVariable1 = {

name: 'complexVariable1',

};

let complexVariable2 = complexVariable1;

// 打印出两个变量的值,没有什么问题

console.log(complexVariable1);  // name:complexVariable1

console.log(complexVariable2);  // name:complexVariable1

// 修改complexVariable2中的值,一切就不如预期了:complexVariable1中的值也发生了变化

complexVariable2.name = 'complexVariable2';

console.log(complexVariable1);  // name:complexVariable2

console.log(complexVariable2);  // name:complexVariable2

本文基于以上代码反映的问题对基本类型和引用类型从内存空间的角度做深入学习,从而对变量的声明、引用过程有更深的理解,间接达到提升代码质量的作用。

《JavaScript高级程序设计》中包含的这些内容的章节:4.1基本类型和引用类型的值

参考文章:

MDN-内存管理

Does JavaScript use stack or heap for memory allocation or both?

stack and heap in V8

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

国内外已经有很多大神写了相关的文章或回答,本文基于这些文章学习理解而得。简书上关于 内存空间详细图解 的这篇文章已经写得非常详尽,因此本文仅对那篇文章中没有提及或不够完善的部分做补充。

栈内存与堆内存

栈内存作为内存中的一块区域,遵循先进后出First-In-Last-Out的存储模式。一般栈内存比较小,但是存取数据的效率远高于堆内存,因此多用来存储一些简单的数据。

堆内存和栈内存正好相反,内存大但是存取数据的效率低,数据存储方式无序且随意,因此适合存放一些复杂的数据结构。

通过这一小段简单的说明,其实对ES中的数据类型可以有一些合理的猜想:

因为栈内存空间小存取速度快,多用来存储一些简单的数据,那么ES中基本类型值很可能就存储栈内存中;

而因为堆内存的一些特性,使得它非常适合拿来存储引用类型值。

在接下来章节中我们会去探究到底是不是这种分配方式。

ES中的内存空间

ECMA-262标准中没有定义内存布局,因此本文讨论的内存空间是由JavaScript引擎实现的。

这里以V8引擎的内存布局实现举例。

从js垃圾回收机制来认识它的内存空间

参考文章Does JavaScript use stack or heap for memory allocation or both?中提到:

The easiest way is to think for a second. Javascript is a garbage-collected language. GC means that there is data scattered around which has to be cleaned up. Does that sound like the data is stored on the stack? Rather... no, because the stack has strictly ordered data. So, variables are allocated on the heap.

MDN 内存管理中也有提到:

像C语言这样的高级语言一般都有底层的内存管理接口,比如 malloc()和free()。另一方面,JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者感觉他们可以不关心内存管理。 这是错误的。

JavaScript的垃圾回收机制意味着它会去不断回收杂乱无序的不再使用的变量,而堆内存严格的先进后出的存储模式无法满足垃圾回收机制的运行要求--通过这点可以看出JavaScript中的变量都是存储在堆内存中的。

结论

既然已经得出了结论那么为什么还有之后那么多章节,而且之后的章节中还提到了栈内存,这和得出的结论是否矛盾呢?在翻了一些文章结合理解之后得出了如下较为系统的结论:

  • ES的标准中没有明确定义内存布局,内存布局由具体的JavaScript引擎实现。

  • 通过内存回收机制可以看出JavaScript中的变量都是存储在堆内存中的

  • 在之后章节中提到的栈内存,是由js引擎在堆内存中模拟的一个类似于栈内存的对象。

内存空间总览

image

↑↑↑图片来自内存空间详细图解

基本类型的内存空间

如上一节中猜测的一般:对于基本类型,其值直接存放在栈内存中,我们可以直接通过变量名访问到它们的值。因此书本中说

基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。


// 声明一个基本类型的变量

const simpleVariable = 'simpleVariable';

// 由simpleVariable复制一个copySimpleVariable变量

let copySimpleVariable = simpleVariable;

// 因为基本类型的变量是按值访问的,所以修改copySimpleVariable的值会改了copySimpleVariable的值不会对simpleVariable产生影响

copySimpleVariable = 123;

引用类型的内存空间

而对于引用类型来说,它们的值存放在堆内存中,变量名保存的是一个内存地址,当我们访问这个变量的时候实际访问的是一个引用地址,通过这个引用地址访问到存在在堆内存中的值。


//声明一个引用类型

const m = {

name: 'complexVariable',

};

//声明一个新的引用类型,复制自complexVariable

const n = complexVariable;

如这段代码所示,当我们声明一个复制自m的引用类型n时,并不是像基本类型一般会直接修改这个变量的值,而是给这个变量一个指向堆内存的引用地址的值,如图所示:

image

↑↑↑图片来自内存空间详细图解

当操作引用类型值时内存空间发生了什么变化

对引用类型的操作大体有如下几种:

  • 复制

  • 增加、删除、修改属性

  • 赋值


当发生复制操作时,内存空间如上节中提到的所示,当我们声明一个复制自 m的引用类型 n时,并不是像基本类型一般会直接修改这个变量的值,而是给这个变量一个指向堆内存的引用地址的值。此时发生的情况是书本中说的

引用类型的值是按引用访问的。


当发生增加、删除、修改属性时,会通过变量的内存地址的值访问到在堆内存中实际的对象,并对这个实际的对象进行相应的增加、删除、修改属性操作。此时的情况便是在书本下方加的注解

但在为对象添加属性时,操作的是实际的对象。

正因为是操作了实际的对象,所以会导致所有指向这个堆内存中对象的变量都发生变化,这就是文章开头demo中说到的情况:

声明两个复杂类型的变量,complexVariable2复制自complexVariable1。

修改complexVariable2中的值,一切就不如预期了:complexVariable1中的值也发生了变化


当发生赋值操作时,操作对象的值会赋值为堆内存中将要赋值的对象的内存地址。而此时就算操作的变量是复制自别的引用类型变量,也会接触两个变量之间的关系,无论操作哪个对象都不会对另一个产生影响:


// 声明一个引用类型变量

let complexVariable1 = {

name: 'complexVariable1',

code: '01',

};

// 声明一个复制自complexVariable1的变量

let complexVariable2 = complexVariable1;

// 修改complexVariable2的name属性,会操作在堆内存中的实际对象导致complexVariable1也发生变化

complexVariable2.name = 'complexVariable2';

console.log(complexVariable1.name);  // complexVariable2

// 而当我们做赋值操作的时候,会将一个新的内存地址赋值给complexVariable2

complexVariable2 = {

name: 'new complexVariable2',

code: '007',

}

// 现在complexVariable2和complexVariable1已经没有半毛钱关系了

complexVariable2.name = 'change back to complexVariable1';

console.log(complexVariable1.name);  // complexVariable2


通过对内存空间的学习,可以清楚地知道变量发生创建赋值修改操作时js引擎做了哪些动作,从而更好地把握并使用各个变量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349