header-bg.jpg
JavaScript基础进阶 : 基本类型与引用类型及其储存方式
发表于 2017-09-16 17:42
|
分类于 JavaScript
|
评论次数 0
|
阅读次数 852

在学习JavaScript的基本类型与引用类型的相关知识之前 , 我们可以先了解几个关于内存的预备知识 , 以便更好地理解后面的内容。

由C/C++编译的程序的内存分配

堆区(heap)

一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式类似 于链表 , 堆区的数据是存放在二级缓存中的,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)

栈区(stack)

由编译器自动分配释放,存放函数的参数值,局部变量的值等 , 其操作方式类似于数据结构中的栈, 栈区的数据使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放.

全局区(静态区)(static)

全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域 , 程序结束后由系统释放.

文字常量区

常量字符串就是放在这里的 , 程序结束后由系统释放 。

程序代码区

存放函数体的二进制代码。

堆区与栈区的区别

栈区

先进后出 , 驻留于常规RAM(随机访问存储器)区域,这是一种特别快、特别有效的数据保存方式,仅次于寄存器。

堆区

顺序随意, 一种常规用途的内存池(也在RAM区域), 它最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必 知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。

以上是关于C/C++ 编译的程序的内存分配的相关知识, 那么同样在JS中也存在堆区(heap) 与 栈区(stack)这种概念, 大家都知道JS是一门解释型脚本语言, 在浏览器中需要由解释器(例如V8, Rhin, SpiderMonkey等等)一边编译一边执行, 那肯定就没有那么多种内存分区了, JavaScript中只有堆区与栈区, 理解起来也很简单, 下面就开始介绍JavaScript的相关知识

JavaScript中的基本类型与引用类型

JavaScript中的变量可以包含2种不同数据类型的值 : 基本类型值引用类型值

基本类型值

指的是5种简单的数据段: undefined、null、boolean、number、string, 它们被储存在栈区中, 操作变量, 就会影响实际的值。

引用类型值

引用类型是一种数据结构, 它的值包括Object、Array、Date、RegExp、Boolean、Number、String, 引用类型值是引用类型的一个实例, 例如 let a = new Object 或者 let b = new String, 那么a 和 b 就分别是 Object类型 和 String类型的一个实例

引用类型的值被保存在堆区中, 它不允许被直接操作, 因此需要通过保存在栈区中的相应的指针(Pointer)来操作。

可以用下面这张图来理解:

heap.gif

基本数据类型值占据空间小、大小固定,属于被频繁使用的数据,所以直接将值存储在栈区中

引用数据类型值占据空间大、大小不固定 , 如果也存储在栈区中,将会影响程序性能 , 所以将实际值储存在堆区中 , 将指针储存在栈区中

当解释器寻找引用数据类型值时,会首先检索其在栈区中的指针,取得指针后从堆区中获得相应实际值

以上是相关概念, 下面用实际例子来说明这2种类型的区别

基本类型的复制

var foo = 'Leo'; var bar = foo; foo = 'Bridy'; console.log(foo); // output: 'Bridy' console.log(bar); // output: 'Leo'

在复制基本类型的时, JS会直接在栈区中创建一个新的副本 , 因此当第一个变量的值发生改变, 并不会被影响第二个变量

下图很形象地展示了基本类型值的复制过程 :

stack.gif

引用类型的复制

var foo = new Object(); var bar = foo; foo.name = 'Leo'; console.log(bar.name); // output: 'Leo'

如上所示, 在声明引用类型值时, JS会在堆区创建对象的实际值 , 并在栈区创建该值对应的指针

而在复制对象时, JS则会在栈区中创建一个指针的副本 , 2个指针都指向堆区中的同一个对象 , 因此无论用哪个变量, 它们都操作的是堆区中的同一个对象, 所以当声明foo的name属性为Leo时, bar的name属性也是Leo

下图很形象地展示了引用类型值的复制过程 :

stack.gif

延伸拓展 , 函数的参数传递

function sum(e){ e += 10; return e; } var foo = 10; var bar = sum(foo); console.log(foo); // output: 10 console.log(bar); // output: 20

这个例子很好理解, JS中所有的函数都是按值传递的, 将变量foo传入函数sum中, 就相当于复制了一份变量foo给参数e, 所以参数e无论如何都不会影响到变量foo

函数中使用引用类型

function setName(e){ e.name = 'Leo'; e = new Object(); e.name = 'Birdy'; } var person = new Object(); setName(person); console.log(person.name); // output:

这个例子稍微复杂一点, 先猜猜看答案是谁?

如果你对JS非常熟悉, 那对此一眼就能看出答案, 首先声明变量person为一个对象, 将其传入函数setName中, 就相当于复制了一份栈内存中的指针给参数e, 所以参数e就可以修改堆内存中对象的name属性为’Leo’

之后再重新将参数e赋值为一个新对象, 此时堆内存中会加入一个新对象 , 这和之前的对象就是2个不同的对象了 , 所以修改其中一个对象的属性, 另一个对象不会被影响, 所以正确答案就是Leo, 嘻嘻, 猜对了没?

PS: 在函数内部声明的对象是局部对象, 在函数运行完毕后垃圾收集器会将其标记为可回收, 下次回收垃圾时会将其销毁并回收所占用的内存

确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

发布评论
还没有评论,快来抢沙发吧!