JavaScript数据类型–基本类型和引用类型

ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型。也有其他的叫法,比如原始类型和对象类型,拥有方法的类型和不能拥有方法的类型,还可以分为可变类型和不可变类型,都是依据这两种的类型特点来命名的。下面是他们的区别和联系。首先必须要知道一些基础知识。堆和栈的区别在哪里呢?我面阿里的时候就被问到了。


栈和堆详解

基本类型的栈:栈是内存中一种特殊的数据结构,也称为线性表,栈按照后进先出 的原则存储数据,先进入的数据被压入栈底,最后插入(push)的数据放在栈顶,需要读取数据时从栈顶开始弹出(pop)数据,即最后一个数据被第一个读出来。因此说, 值类型都是简单的数据段。变量的位置和变量值的位置是重叠的,也就是说值类型的数据被存储在变量被访问的位置。

引用类型的堆:这类值存储在堆(heap)中,堆是内存中的动态区域,相当于自留空间,在程序运行期间会动态分配给代码和堆栈。

堆中存储的一般都是对象,然后通过一个编号传递给栈内变量,这个编号就是所谓的引用指针(point),这样变量和变量值之间是分离的,它们通过指针相联系。当读写数据时,计算机通过变量的指针找到堆中的数据块,并进行操作。

number、string、boolean、null和undefined型数据都是值类型。 由于值类型数据占据的空间都是固定的,所以可以把它们存储在狭窄的内存栈区。这种存储方式更方便计算机进行查找和操作,所以执行速度会非常快。

而对于object型数据(包括function和array)来说,由于它们的大小是不固定的,所以不能存储在栈区,只能被分配到堆区,如果存储在栈区,则会降低计算机寻址的速度。而堆的空间是不固定的,所以很适合存储大小不固定的对象数据,然后在栈区存储对象在堆区的地址即可,而地址的大小是固定的,所以这种分离存储的方法不会影响计算机的寻址速度,对于变量的性能也没有任何负面影响(如图)。

在JavaScript语言中,object、function和array等对象都是引用型数据。 很多语言都把字符串视为引用型数据,而不是值类型,因为字符串的长度是可变的。但是JavaScript比较特殊,它把字符串作为值类型进行处理。不过,字符串在复制和传递运算中,是以引用型数据的方法来处理的。

有了这些基本知识,再去看下面的区别就很好理解了。


基本类型和引用类型包含的数据类型不同

基本类型包含的数据类型
undefined,null,boolean,number,string,基本类型的访问是按值访问的,就是说你可以操作保存在变量中的实际的值。

引用类型包含的数据类型
对象、数组、函数。也可以说是就是对象了。对象是属性和方法的集合。也就是说引用类型可以拥有属性和方法,属性又可以包含基本类型和引用类型。


基本类型的值是不可变,引用类型的值是可变

基本类型的值不可变
任何方法都无法改变一个基本类型的值,比如一个字符串:

1
2
3
var name = 'seven';
name.toUpperCase(); // 输出 'Seven'
console.log(name); // 输出 'seven'

会发现原始的name并未发生改变,而是调用了toUpperCase()方法后返回的是一个新的字符串。

1
2
3
4
5
6
var person = 'seven';
person.age = 21;
person.method = function(){//...};

console.log(person.age); // undefined
console.log(person.method); // undefined

通过上面代码可知,我们不能给基本类型添加属性和方法,再次说明基本类型时不可变;

引用类型的值可变
我们可为为引用类型添加属性和方法,也可以删除其属性和方法

1
2
3
4
5
6
7
8
var person = {};//创建个控对象 --引用类型
person.name = 'seven';
person.age = 21;
person.sayName = function(){console.log(person.name);}
person.sayName();// 'seven'

delete person.name; //删除person对象的name属性
person.sayName(); // undefined

上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的。


基本类型和引用类型的比较不同

基本类型相等时值相等
基本类型只有在它们的值相等的时候它们才相等。但有时候会遇到这种情况:

1
2
3
var a = 1;
var b = true;
console.log(a == b);//true

它们不是相等吗?其实这是类型转换和 == 运算符的知识了,也就是说在用==比较两个不同类型的变量时会进行一些类型转换。像上面的比较先会把true转换为数字1再和数字1进行比较,结果就是true了。 这是当比较的两个值的类型不同的时候==运算符会进行类型转换,但是当两个值的类型相同的时候,即使是==也相当于是===。

1
2
3
var a = 'seven';
var b = 'seven';
console.log(a === b);//true

引用类型的比较是引用的比较

1
2
3
var person1 = '{}';
var person2 = '{}';
console.log(person1 == person2); // true

上面讲基本类型的比较的时候提到了当两个比较值的类型相同的时候,相当于是用 === ,所以输出是true了。上面的两个person都是字符串。

1
2
3
var person1 = {};
var person2 = {};
console.log(person1 == person2); // false

这两个person是字符串。但此时的结果是false。因为引用类型时按引用访问的,换句话说就是比较两个对象的堆内存中的地址是否相同,那很明显,person1和person2在堆内存中地址是不同的。
这个会在下面详细的讲。


基本类型和引用类型的存放地址不一样

这一点是我认为理解他们不同的最重要的一点。
基本类型的变量是存放在栈区的(栈区指内存里的栈内存)

1
2
3
var name = 'seven';
var city = 'wuhan';
var age = 1;

栈区包括了 变量的标识符和变量的值。
引用类型的值是同时保存在栈内存和堆内存中的对象
javascript和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作啥呢? 实际上,是操作对象的引用,所以引用类型的值是按引用访问的。

准确地说,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。

1
2
3
var person1 = {name:'seven'};
var person2 = {name:'cld'};
var person3 = {name:'christine'};


基本类型和引用类型的赋值

基本类型是简单赋值
在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上:

1
2
3
4
5
var a = 10;
var b = a;
a ++ ;
console.log(a); // 11
console.log(b); // 10

此时,a中保存的值为 10 ,当使用 a 来初始化 b 时,b 中保存的值也为10,但b中的10与a中的是完全独立的,该值只是a中的值的一个副本,此后,这两个变量可以参加任何操作而相互不受影响。
也就是说基本类型在赋值操作后,两个变量是相互不受影响的。

引用类型的赋值是对象引用
当从一个变量向另一个变量赋值引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。前面讲引用类型的时候提到,保存在变量中的是对象在堆内存中的地址,所以,与简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象。那么赋值操作后,两个变量都保存了同一个对象地址,则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响:

1
2
3
4
5
6
7
8
9
10
11
12
var a = {}; // a保存了一个空对象的实例
var b = a; // a和b都指向了这个空对象

a.name = 'seven';
console.log(a.name); // 'seven'
console.log(b.name); // 'seven'

b.age = 1;
console.log(b.age);// 1
console.log(a.age);// 1

console.log(a == b);// true

因此,引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何的操作都会相互影响。


参考文章:

  1. 推酷
  2. segmentfault