值类型与引用类型及在内存中的存储

Part1 值类型与引用类型

值类型与引用类型的定义

  • 值类型(基本类型)

    定义:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配。
    5种基本数据类型:stringnumberbooleannullundefined

  • 引用类型

    定义:存放在堆内存中的对象,变量保存的实际是一个指针,当我们需要访问引用类型时,存放在栈内存中的指针会将我们指向堆内存的数据。每块空间大小不一样,根据情况进行特定的分配。
    常见的引用类型有:ArrayObjectFunction

值类型与引用类型的存储(传值与传址)

  • 值类型的赋值:把右边存储的信息(具体数据)复制一份给左边的变量。

    特点:值类型赋值只是简单的数据复制,互相不影响,是独立的。

    1
    2
    3
    4
    5
    var a = 10;
    var b = a; //具体数据的复制,复制之后a,b相互独立互不影响
    b = 1;
    console.log(a); //10
    console.log(b); //1
  • 引用类型的赋值:把右边存储的信息(指向具体数据的地址)复制一份给左边的变量。

    特点:共享同一份数据,修改其中一个对象属性的值也会影响另一个。

    1
    2
    3
    4
    5
    6
    7
    var obj1 = {
    num : 10
    };
    var obj2 = obj1; //数据地址的复制,两个对象指向同一份数据,共享同一份数据。
    obj2.num = 1;
    console.log(obj1.num); //1 修改obj2的属性也会影响obj1
    console.log(obj2.num); //1

值类型与引用类型在函数中的应用

在函数中的参数有两种,一种是实参,就是实际的参数。还有一种是形参,形式参数,占位用的,函数调用之前是没有值的。

函数的调用:默认会把实参赋值给形参。

  • 值类型作为函数的参数(值传递)
    形参与实参相互独立,没有影响

    1
    2
    3
    4
    5
    6
    7
    8
    var num = 10;
    function fn(n) {
    //默认会执行一步 n = num;
    n = 10;
    console.log(n); //10
    }
    fn(num); //函数的调用
    console.log(num); //20 值传递时,形参实参互不影响
  • 引用类型做为函数的参数(指针传递)
    实参形参共享同一份数据,修改一个对象的值也会对另外一个对象产生影响

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var obj = {num : 10}; //定义一个对象
    function fn(object) {
    //默认进行 object = obj; 此时进行了地址的传递
    object.num = 1//修改了一个源数据,所有指向改数据的对象的属性都进行了更改
    object = {other : others}; //object新建一块内存空间,指针指向新建的空间
    console.log(object.num); //undefined 新建的空间内部已经没有num属性
    }
    fn(obj); //调用函数
    console.log(obj.num); //1 被object更改了属性

Part2 堆栈的概念

堆栈的定义

  • 堆(heap)
    动态分配内存,大小不定,不会自动释放存储空间,堆中存储复杂类型数据

  • 栈(stack)
    自动分配内存空间,系统自动释放,栈中存储简单类型数据

    栈是一中特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。

    线性表

    后进先出(Last in First out),简称LIFO线性表

数据在堆栈中的表示

JavaScript在内存中是如何表示的呢?我们可以通过以下例子来了解值类型和引用类型在内存中是如何存储的。

1
2
3
4
5
6
7
8
9
10
11
12
var zs = {
name : 'zs',
age : 10,
sex : '男',
dog : dog, //指向dog对象
run : function () {} //指向run函数
};
var dog = {
name : '旺财',
age : 10,
run : function () {} //指向run函数
};

以上代码可以描述为图示的关系:
数据在堆栈中的表示


Part3 深拷贝与浅拷贝

浅拷贝

浅拷贝
在定义一个对象或数组的时候,变量在栈中只是存储了一个地址(指针),当我们复制该对象或数组的时候,复制的值也是该地址。在访问属性的时候,还是会通过复制的地址回溯到原来对象或数组指向的内存中。即两者共享了同一内存空间。修改一个对象的属性,另一个对象也会受到影响。

1
2
3
4
5
6
7
function Copy(p) {
var c = {};
for (var i in p) {   
c[i] = p[i];
}
return c; 
}

深拷贝

深拷贝
当我们不希望复制后的对象还与原对象共享同一数据,两者独立时,我们可以使用深拷贝来解决,通过递归的方法,把原对象中的属性方法都遍历给新的对象。这样,两个对象的属性会存储在堆中的不同内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function deepClone(obj, copy) {
//被拷贝的对象必须是一个对象
copy = copy || {};
//遍历在目标对象中的所有属性
for (var k in obj) {
//判断是否是自身的属性,防止遍历到原型属性
if (obj.hasOwnProperty(k)) {
//如果是引用类型的属性(function除外)就要进行深拷贝
if (typeof obj[k] === 'object') {
//判断属性是数组还是对象
copy[k] = obj[k].constructor === Array ? [] : {};
//递归进更深一层
deepClone(obj[k], copy[k]);
} else {
//值类型或者是函数直接复制
copy[k] = obj[k];
}
}
}
return copy;
}