数据类型分类与存储

986 阅读7分钟

美好的十月开始了!为了巩固自己的知识体系并更好的将自己的所学分享给大家。于此,开启了一系列全新的分享计划。本计划将以一道经典的面试题 —— 从打开浏览器到输入 URL 到页面展示,这个过程中发生了什么 —— 为大纲从浏览器和 JavaScript 两个切入点由浅入深的整理出一套完整的前端知识体系的相关文章。

为了方便更好的总结和查看,文章中对知识体系将会以思维导图或脑图的形式进行展示。对于其中关键的点,也会拿出来单独讲解。另外,在梳理的过程中难免会遇到各式各样的困难和阻碍,希望大家多多鼓励我前行。当然我也会不负众望,定时更新出干货文章,供大家享用!

---- 今日导学 ----

今日你将学到 JavaScript 中涉及到的数据类型相关的知识!

---- 以下是正文 ----

几乎所有的编程语言都具有存储变量当中的值和对存储在变量中的值进行访问和修改的能力。正因为这种对变量值进行存储和访问的能力将状态带给了程序,使程序可以做很多复杂的、有趣的事情。

下面我们就来详细说说关于变量存储的这些值代表的含义和分类情况。

数据类型分类

分类是指按照种类、等级或性质将抽象或者具体的事物进行分别归类。为此,我们可以将 JavaScript 中涉及到 的数据类型分为三大类:基础数据类型、引用数据类型和语言级数据类型,而这三大数据类型中的每一个类别又可以继续进行细化分类。

基本数据类型

基础数据类型代表了 JavaScript 中最简单的数据类型,也是 JavaScript 数据类型的基本组成,主要就是用来存储各种简单数据值的。其包括以下七类:

image

引用数据类型

image

语言级数据类型

image

以上关于 JavaScript 中数据类型的讲解,都是通过思维导图的方式进行展示的,我相信这比用文字表述出来要简明清晰的多。当然图中只是列出了关于数据类型的一些基本信息和它们的描述、作用等。除了预言级层面的数据类型外,开发者熟悉的就数基本数据类型和引用数据类型了,后续我们的内容也会围绕这两块进行展开。针对于每种数据类型还可以说的有很多,后续也会拎到单独的文章中讲解!关于语言级别的数据类型在后续的文章中也有可能会提及,大家如果想要了解的话可以自行查看规范

类型值特点

数据类型是描述用来存储什么样的数据的,比如说 string 就是标识这块空间存储的是 UTF16 的字符编码串,而 number 就是标识这块空间存储的是符合 IEEE754 规范的双精度浮点数等等,每个数据类型都有自己特定的数据存储格式。下面的内容会将基础数据类型和引用数据类型从四个维度进行比较说明他们的值存储、赋值、传递的特性。

值存储

说到变量存储,就不得不说栈内存和堆内存了!

在 JavaScript 中,不!基本上是在所有的编程语言中:

  • 栈(stack)是由编译器自动分配和释放的一块内存区域,其主要用来存放一些基础类型的变量值、指令代码、常量以及对象句柄(也就是对象的引用地址)。

    • 栈内存的操作方式类似于数据结构中的栈(仅在表头进行插入或删除操作的线性表),其优势在于存取速度快,仅次于寄存器,而且栈中的数据还可以共享;
    • 而其缺点在于,存在栈中的数据大小与生存期必须是确定的,缺乏一定的灵活性。
  • 堆(heap)是程序运行时动态分配的内存区域,在构建对象时所需要的内存从堆中分配。这些对象就是我们常提起的引用数据类型值。

    • 这种分配方式类似于数据结构中的链表。堆内存在使用完毕后,由垃圾回收(Garbage Collection, GC)器 “隐式” 回收。
    • 其优势在于动态地分配内存大小,可以 “按需分配”,其生存期也不必事先告诉编译器。在使用完毕后,垃圾收集器会自动回收这些不再使用的内存块
    • 其缺点在于要在运行时才动态分配内存,而且相比于栈内存,它的存取速度较慢。

从上面对栈和堆的描述和对比中,我们可以知道基础类型值是按值存储在栈空间的。而引用类型值的存储跨越栈、堆两个空间 —— 变量和值引用指针是存在栈空间的,因为存取方便,而具体的值则是存在堆空间,会按需分配内存空间。如下图所示:

image

图中有个点要特别注意,那就是变量 c 存储的 null 值,这个 null 表示的是一个空指针对象,即不指向任何的空间或者值,这也就是 typeof null === 'object' 的原因所在。

值复制

复制值在日常开发中是常用的操作,我们需要从一个变量向另外一个变量赋值基本类型值和引用类型值。针对这两种类型值的复制在底层也因它们的存储形式不同而不同。

  • 基本类型值复制

用例子说话:

var num1 = 5;
var num2 = num1;

image

从图可以看出,基本类型值的赋值是值复制的,也就是复制的两个变量值处于栈内存中的两个不同的区域,两者互不影响。

  • 引用类型值复制

用例子说话:

var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'lane';
obj2.name;      // 'lane'

image

对于引用类型的值是单独存储在堆空间的,而栈空间只有相应的指针指向堆空间的值,所以对于 obj1 和 obj2 来说,他们虽然在栈空间申请了不同的区块用于存储指针对象,但是他们的指针指向的堆空间是相同的,所以最后对 obj1 和 obj2 的值访问会访问到同一个堆区块,也就有了示例代码的效果。

值的动态属性

引用类型值是一组属性和方法的集合。既然拥有属性和方法,那我们就可以进行增删改查操作,例子如下:

var person = new Object();
person.name = 'lane';
person.name;    // 'lane'

而对于基本类型值却有所不同:

var name = 'lane';
name.age = 18;
name.age;    // undefined

我们可以为基本类型值设置属性,而且这样也不会报错,但是当我们访问这个属性时,发现该属性不存在。这已经很好的说明了我们只能给引用类型值动态的设置属性。

函数传参

函数传参是最容易迷糊的地方!在说函数传参之前,先说明一点:定义函数时,函数接受的参数为形参(形式参数);调用函数时,传递的具体值是实参(实际参数)。

image

话再说回来,其实函数传参形式和我们上面讲到的值复制是一样一样的。形式参数就是声明的局部变量会按值接受传递的实际参数:针对基本类型值就是直接复制值,而引用类型值就是复制引用指针。

// 基本类型值传参
function sum(a, b) {
    a += 1;
    return a + b;
}

var a1 = 1;
var b1 = 2;

sum(a1, b1);    // 4
a1;             // 1
// 引用类型值传参
function setName(obj) {
    obj.name = 'lane';
}

var person1 = { name: 'jack' };
setName(person1);

person1.name;       // 'lane'
// 引用类型值传参
function setName(obj) {
    obj.name = 'lane';
    
    // obj 指向了另外一个对象
    obj = { name: 'Tom' }
}

var person1 = { name: 'jack' };
setName(person1);

person1.name;       // 'lane'

上面的三个例子很好的说明了基本类型值和引用类型值传参的方式了。

总结

系列的第一篇博文,首先说到了 JavaScript 涉及到的数据类型分类,为了方便描述和良好的阅读效果,于是采用了思维导图的方式进行展示。然后从存储方式、值的复制、函数参数的传递和动态属性四个方面描述了基本类型值和引用类型值的不同。涉及到的知识量适中,希望大家喜欢!