JS数据类型(1):数据类型与内存空间

507 阅读5分钟

数据类型是 JavaScript 中最基础的知识点,我们在日常的开发中,无时无刻不在用到数据类型相关的知识。在面试中,数据类型也是最基础的考点,本文将会以几道常见的面试题开头,依次解释其中需要我们掌握的知识点。

关于数据类型与数据存放相关的面试题:

  1. JavaScript 中变量类型分为几种,区别是什么?
  2. 基本数据类型和引用数据类型的区别?
  3. 不同数据类型分别存放在哪里?
  4. 栈和堆的区别是什么?分别怎么存储数据?
  5. 代码题。结果以及为什么?
var a = { age: 19 };
var b = a;
a = null;
console.log(b); // ?
  1. const 定义的值能修改么?
const obj = {
  name: 'zs',
  age: 20
};

obj.age = 30;
console.log(obj); // ?

这些问题都比较基础,但是如果你还有模棱两个的地方,不妨再仔细阅读一下下面的内容。

一. 数据类型

众所周知,JavaScript 中有 7 种数据类型,分别是:Undefined、Null、Boolean、Number、String、Symbol 以及 Object。

其中,前 6 种我们称之为基本数据类型,最后一种我们称之为引用数据类型

另一方面,在 JavaScript 中,一个变量可以随时持有任何类型的值。比如:

var a = '3';
a = 50;

这表明:JavaScript 中的变量是没有类型的,只有值才有。

二. 栈、堆以及数据存放

简单来说,在 JavaScript 中,每一个数据都需要有它所对应的内存空间。而内存空间主要分为两种:栈内存(Stack)堆内存(Heap)

栈内存一般用来存放基本数据类型的值(Undefined、Null、Boolean、Number、String、Symbol)。栈内存由系统自动分配内存空间,存储值的大小固定,空间小,运行效率更高。

堆内存用来存放引用数据类型的值(对象)。当我们声明了一个引用类型的值(对象)之后,这个引用类型的值(对象)保存在堆内存中。另一方面,这个值(对象)的地址会被保存在栈内存中,用来引用这个对象。同时,堆内存存储的值大小不定,可以动态调整,空间大,运行效率更低。

为了形象的说明,我们把下面的例子用图来表示一下:

var c = { name: 'zs' }; // 引用数据类型
var b = 'abc'; // 基本数据类型
var a = 10; // 基本数据类型

通过上面的解释以及图示,我们可以知道:

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

2. 引用数据类型是按引用访问的。

三. 赋值与传递

因为保存基本数据类型和引用数据类型的方式不同,所以从一个变量向另一个变量复制基本数据类型值和引用数据类型值时,也存在不同。

对于基本数据类型的值而言,我们总是通过值复制的方式来赋值和传递。

var num1 = 10;
var num2 = num1;
num1 = 30;
console.log(num2); // 10

在这个例子中,10 是一个 Number 类型的值,变量 num1 持有该值的一个副本。当我们将 num1 的值赋值给 num2 时,实际上是新开辟了一块内存空间,此时 num2 也持有该值的一个副本。这两个变量的任何操作,都不会互相影响。

而对于引用数据类型而言,我们总是通过引用复制的方式来赋值和传递。我们用文章开头的代码题来举例:

var a = { age: 19 };
var b = a;
a = null;
console.log(b);

因为变量 a 保存的是一个引用类型的值(对象),所以当把变量 a 的值赋值给变量 b 时,实际上是复制了 a 引用(也就是指向堆内存的地址),此时 ab 指向同一块内存空间。当代码执行 a = null 时,实际上是将清除了这个引用,变量 b 的指向不会发生变换。所以打印 b 的结果依旧是 { age: 19 }

这里我们需要明确以下两点:

1. ab 仅仅是指向 { age: 19 },而并非持有。

2. 引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

四. const 定义一个常量,可以修改么?

我们知道,const 声明一个只读常量。一旦声明,变量的值不能修改。但看到下面的例子,是否会有一丝疑惑?

const obj = {
  name: 'zs',
  age: 20
};

obj.age = 30;
console.log(obj); // { name: 'zs', age: 30 };

我们发现,代码并没有报错。其实这个原因也很简单,就像上面画的那张图一样,变量 obj 其实保存的是一个引用,也就是一个地址,当我们修改对象内部的值时,变量保存的地址并没有发生变化,所以是不会报错的。

如果我们修改代码为:

obj = null;

此时,const 声明的变量中保存的地址就会发生变化,所以才会报错。

五. 总结

本文主要介绍了 JavaScript 中最基础的概念数据类型,区分了基本数据类型与引用数据类型以及它们存放方式上的差异。正是因为这些差异,所以它们在赋值和传递上也有不同的特点。

最后,我们抛出两个问题:

  1. 为什么一个基本类型的值,可以像对象一样访问属性和方法?比如:
const a = '123';
a.length; // 3
  1. 既然基本数据类型可以访问属性和方法,为什么 1.toString() 会报错?不是应该变为 "1" 么?

下期,我们来聊聊数据类型中的封装对象。

以上内容,如有错误,欢迎指正。

文章会在 Github 首发,欢迎关注和 Star。