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

https://img.lcgod.com/2019/03/23/1553337230.gif

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

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

堆区(heap)

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

栈区(stack)

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

全局区(静态区)(static)

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

文字常量区

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

程序代码区

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

堆区与栈区的区别

栈区

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

堆区

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

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

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

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

基本类型值

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

引用类型值

引用类型是一种数据结构,它的值包括 ObjectArrayDateRegExpBooleanNumberString,引用类型值是引用类型的一个实例。

例如:

let a = new Object
let b = new String

那么 ab 就分别是 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 则会在栈区中创建一个指针的副本,两个指针都指向堆区中的同一个对象,因此无论用哪个变量,它们都操作的是堆区中的同一个对象,所以当声明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,嘻嘻,猜对了没?

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

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

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