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

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

Tags
前端
Published
November 4, 2018
Author
LIAOKUN
METADATA
title: 《JavaScript高级程序设计 第四章》通过内存空间探究ES中基本类型和引用类型的异同d ate: 2018-11-04 22:30 tags: [前端,高程学习笔记] categories: 技术
 
在开发过程中我们对基本类型和引用类型的一些特性有所了解:
基本类型是按值访问的,所以我们可以尽情地赋值修改不用担心有副作用;
而引用类型是按引用访问的,所以这会导致当我们操作一个复制自另一个引用类型的值时,使得另一个值也发生变化。
说起来可能有点拗口,又是复制又是赋值的,还有什么按值访问按引用访问,看一下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 基本类型和引用类型的值
参考文章:
国内外已经有很多大神写了相关的文章或回答,本文基于这些文章学习理解而得。简书上关于 内存空间详细图解 的这篇文章已经写得非常详尽,因此本文仅对那篇文章中没有提及或不够完善的部分做补充。
通过这一小段简单的说明,其实对ES中的数据类型可以有一些合理的猜想:
因为栈内存空间小存取速度快,多用来存储一些简单的数据,那么ES中基本类型值很可能就存储栈内存中;
而因为堆内存的一些特性,使得它非常适合拿来存储引用类型值。
在接下来章节中我们会去探究到底是不是这种分配方式。
MDN 内存管理中也有提到:
像C语言这样的高级语言一般都有底层的内存管理接口,比如 malloc()和free()。另一方面,JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者感觉他们可以不关心内存管理。 这是错误的。
JavaScript的垃圾回收机制意味着它会去不断回收杂乱无序的不再使用的变量,而堆内存严格的先进后出的存储模式无法满足垃圾回收机制的运行要求--通过这点可以看出JavaScript中的变量都是存储在堆内存中的。
// 声明一个基本类型的变量 const simpleVariable = 'simpleVariable'; // 由simpleVariable复制一个copySimpleVariable变量 let copySimpleVariable = simpleVariable; // 因为基本类型的变量是按值访问的,所以修改copySimpleVariable的值会改了copySimpleVariable的值不会对simpleVariable产生影响 copySimpleVariable = 123;
当发生复制操作时,内存空间如上节中提到的所示,当我们声明一个复制自 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引擎做了哪些动作,从而更好地把握并使用各个变量。