JS基本类型、引用类型梳理
原始值类型与引用值类型
ECMAScript规范中定义了变量的两种类型:原始值类型和引用值类型。区别两种类型的直接特征是:存储位置。如果某种变量是直接存储在栈(stack)中的简单数据段,即为原始值类型,如果是存储在堆(heap)中的对象,则为引用值类型。
一般而言,栈中存放的变量(原始值类型)都具有占据空间小、大小固定的特点。只有String是个特例,虽然它不具备大小固定的要求,但JS中明确规定了 String 是不可变的,鉴于如此稳定而又会被频繁使用,所以放入栈中存储。在其他语言中,String 是可以在适当条件下修改的,因此被放入堆中存储。
堆中存放的变量(引用值类型)都具有占据空间大、大小不固定的特点,因此如果也存储在栈中,将会影响程序运行的性能。引用值类型还在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
原始值类型
原始值的数据类型有:undefined
、boolean
、number
、string
、null
,原始值类型的访问是按值访问的,就是说你可以操作保存在变量中的实际的值。原始值类型有以下几个特点:
1. 原始值类型的值不可变
举个栗子:
var a = 'hello';
a.toUpperCase(''); // 实际上返回一个新的字符串,存在另外一个地址
console.log(a); // hello
typeof('hello') // string
假设保存第一行字符串的地址是A,第二行的地址是B;字符串不可改变的意思就是:执行第二条语句的时候,返回一个新建字符串 HELLO
,然后将原来指向A的a
改为指向新的地址,即B,若字符串可以修改,那么此时应该是修改原来A地址中的值为 HELLO
,但是这样在js中是禁止的,所以说字符串是不可修改的。
这里说的原始值类型是指 hello
是string类型, 也就是说无论对变量 a
做任何方法都不能改变 hello
的值,改变的只是变量a
所指向的地址。
再举个栗子:
var person = 'Jhon';
person.age = 22;
person.method = function(){//...};
console.log(person.age); // undefined 原始值类型没有属性
console.log(person.method); // undefined 原始值类型没有属性
javascript中明确规定了原始值类型 undefined
、boolean
、number
、string
、null
的值是不可改变的,这里的不可改变是指改原始值类型的值本身在js中是禁止操作的。也就是说每新建一个原始值,都会开辟一块新的内存。
这两个栗子可以看出原始值类型的值是无法改变的。
2. 原始值类型值比较
- 原始值是value的比较,字符串的比较是,长度相等并且每一个索引的字符都相等。
- 原始值类型的变量是存放在栈区的(栈区指内存里的栈内存)
- 因此比较时只关注栈内存,不涉及到堆内存地址的比较
var name = 'jozo';
var city = 'guangzhou';
var age = 22;

引用类型
javascript中除了上面的基本类型 undefined
、boolean
、number
、string
、null
之外就是引用类型了,也可以说是就是对象了。对象是属性和方法的集合,也就是说引用类型可以拥有属性和方法,属性又可以包含基本类型和引用类型。来看看引用类型的一些特性:
1. 引用类型的值是可变的
我们可为为引用类型添加属性和方法,也可以删除其属性和方法,如:
var person = {};//创建个控对象 --引用类型
person.name = 'jozo';
person.age = 22;
person.sayName = function(){console.log(person.name);}
person.sayName();// 'jozo'
delete person.name; //删除person对象的name属性
person.sayName(); // undefined
上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的。
2. 引用类型的值是同时保存在栈内存和堆内存中的对象
javascript和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作什么呢? 实际上,是操作对象的引用,所以引用类型的值是按引用访问的。
准确地说,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。
假如有以下几个对象:
var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
var person3 = {name:'xiaoq'};
则这三个对象的在内存中保存的情况如下图:

3. 引用类型的比较是引用的比较
var person1 = '{}';
var person2 = '{}';
console.log(person1 == person2); // true
上面讲基本类型的比较的时候提到了当两个比较值的类型相同的时候,相当于是用 === ,所以输出是true了。再看看:
var person1 = {};
var person2 = {};
console.log(person1 == person2); // false
可能你已经看出破绽了,上面比较的是两个字符串,而下面比较的是两个对象,为什么长的一模一样的对象就不相等了呢?
别忘了,引用类型时按引用访问的,换句话说就是比较两个对象的堆内存中的地址是否相同,那很明显,person1
和person2
在堆内存中地址是不同的:

所以这两个是完全不同的对象,所以返回false。
简单赋值
在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上:
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中的值的一个副本,此后,这两个变量可以参加任何操作而相互不受影响。
也就是说基本类型在赋值操作后,两个变量是相互不受影响的。在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上:

也就是说基本类型在赋值操作后,两个变量是相互不受影响的。
对象引用
当从一个变量向另一个变量赋值引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。前面讲引用类型的时候提到,保存在变量中的是对象在堆内存中的地址,所以,与简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象。那么赋值操作后,两个变量都保存了同一个对象地址,则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响:
var a = {}; // a保存了一个空对象的实例
var b = a; // a和b都指向了这个空对象
a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'
b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22
console.log(a == b);// true
它们的关系如下图:

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