# Javascript 数据类型

# 数据类型的分类

  • 基本类型 string number null undefined Boolean symbol
  • 引用类型 Object Array Function

# 什么是基本类型与引用类型

有时我们也叫做简单类型和复杂类型

基本类型:基本类型的变量会保存在栈内存中,如果在一个函数中声明一个基本类型的变量,那么这个变量当函数执行结束之后会自动销毁

引用类型:引用类型的变量名会保存在栈内存中,但是变量值会存储在堆内存中,引用类型的变量不会自动销毁,当没有引用变量引用它时,系统的垃圾回收机制会回收它

作为前端开发工程师来说,可能对栈、堆、队列这些基本数据不太熟悉,下面我们具体说说

# 栈、堆、队列数据类型

#

栈 是一种遵循 后进先出(LIFO) 原则的有序集合。新添加和待删除的数据都保存在栈的同一端栈顶,另一端就是栈底。新元素靠近栈顶,旧元素靠近栈底。 栈由编译器自动分配释放。栈使用一级缓存。调用时处于存储空间,调用完毕自动释放。 原理就像我们使用乒乓球盒子一样 栈例子

#

堆数据结构是一种树状结构。它的存取数据的方式,则与书架与书非常相似。

书虽然也整齐的存放在书架上,但是我们只要知道书的名字,就可以很方便的取出我们想要的书,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。好比在JSON格式的数据中,我们存储的key-value是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字。

TIP

Object 类型都存储在堆内存中,是大小不定,复杂可变的。 Object 类型数据的 指针 存储在栈内存空间, 指针实际指向的值存储在堆内存空间。


为什么会有堆内存、栈内存之分
通常与垃圾回收机制有关。为了使程序运行时占用的内存最小。 当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的; 当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。


# 队列

在JavaScript中,理解队列数据结构的目的主要是为了清晰的明白事件循环(Event Loop)的机制到底是怎么回事。在后续的章节中我会详细分析事件循环机制。

队列是一种先进先出(FIFO)的数据结构。正如排队过安检一样,排在队伍前面的人一定是最先过检的人。用以下的图示可以清楚的理解队列的原理。 队列

WARNING

我们需要记住的是:
基本数据类型用栈存储,引用数据类型用堆存储
闭包变量是存在堆内存中的(后续我们会说)


# 基本类型与引用类型的区别

# 赋值的区别

基本类型的赋值可以理解为深拷贝,赋值后相当于重新开辟一个内存空间,如:

let a = 100;
let b = a;
b = 101
console.log(a.b) // 100, 101

引用类型的赋值是浅拷贝,引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的数据,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。

let a = {name:'码不停息',age:18};
let b = a;
b.age = 20;
console.log(a.age,b.age) // 20,20

TIP

可以思考下,怎么实现一个深拷贝?

# 类型判断的区别

基本类型判断直接可以用typeof进行判断

let a ='码不停息';
let b = 100;
let c = true;
let d = null;
let e = undefined;
console.log(typeof a); // string
console.log(typeof b); //number
console.log(typeof c); //Boolean
console.log(typeof d); // object 
console.log(typeof e); // undefined

WARNING

我们发现null的类型既然是object(对象),这是不是有点逆天了
其实这是浏览器的一个bug原因是:
不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回 object

引用类型用instanceof来检测

let a = {};
let b = [];
let c = function(){};
console.log(a instanceof Object ); //true
console.log(b instanceof Array ); //true
console.log(c instanceof Function); //true

由于 instanceof是判断某个对象是不是另一个对象的实例,所有用它判断也不是很准确

let b = [];
let c = function(){};
console.log(b instanceof Object ); //true
console.log(c instanceof Object); //true

我们发现用instanceof检测数组和函数的类型也是Object,那是应为它们也是Object创建出来的

let a = function(){};
let type = a.constructor.prototype.__proto__.constructor;
console.log(type) // Object

使用 Object.prototype.toString.call() 判断

function typeOf(val){
   return Object.prototype.toString.call(val).match(/\s+(\w+)/)[1]
}

终极判断 (基本类型 + 引用类型)

function typeOf(val){
  if(typeof val != 'object') {
    return typeof val
  }
  return Object.prototype.toString.call(val).match(/\s+(\w+)/)[1]
}