JS堆栈和拷贝的理解

来源:互联网 发布:川一硅藻泥怎么样知乎 编辑:程序博客网 时间:2024/06/10 09:50

1.堆栈的概念

栈:
队列优先,先进先出,由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
堆:
先进后出,动态分配的空间一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(Operatig System,管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件)回收,分配方式类似于链表

2.基本类型和引用类型

基本类型:
存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配
5种基本数据类型:undefined,Null,Boolean,Number和String,它们是直接按值存放的,所以可以直接访问
引用类型:
存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。每个空间大小不一样,要根据情况进行特定的分配。
当我们需要访问引用类型(如对象,数组,函数等)的值,首先从栈中获得该对象的地址指针,然后从堆内存中取得所需要的数据

3.传值与传址的区别

实例一:js中创建两个对象p1,p2,使用赋值法:

  function Person(){//创建一个构造函数  this.name = 'zhangsan',  this.age = 30}var p1 = new Person();//初始化一个对象,并不需要返回这个对象,自动返回这个对象var p2 = p1;//此时复制堆上p1的地址址给p2,所以现在p1,p2指向堆上同一内存空间console.log(p2.name);//输出zhangsanp2.name = "apple";//此时修改的是p2地址指向的堆上对象的属性值console.log(p1.name);//输出appplep2 = null;//console.log(p2.name);报错,无法找到name这个属性console.log(p1.name);//输出apple

实例二:

var a = [1,2,3,4,5]; var b =a;//此时变量a,b指向同一个堆上的对象var c = a[0];console.log(b);//弹出1,2,3,4,5console.log(c);//弹出1b[4] = 6;//改变的栈上对象c = 7;console.log(a[4]);console.log(a[0]);

当改变b数据,a中数据对应也发生了变化,但是改变c中数据时,a的值并没有发生变化,这就是传值与传址的区别,因为a是数组型,属于引用型,所以它赋给b的时候传的是栈中的地址(相当于新建了一个不同名的“指针”),而不是堆内存中的对象。而c仅仅是从a堆内存中获取了一个数据值,并保存在栈中。所以当b修改时,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中
img

4.浅拷贝

在定义一个数组或者对象,变量存放的往往只是一个 地址。当我们使用对象拷贝时,如果属性是对象或者数组时,这时候我们传递的只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象的堆内存中,即意味着父子对象发生了关联,两者的属性值会指向同一个内存空间

var a = {   name : "JavaScript"}//使用对象直接量创建一个对象function Copy(p){ var c = {};//创建一个空对象 for(var i in p){//for...in遍历传入对象参数p中所有的属性值  c[i] = p[i];  }  return c;}a.author = {   firstname:"David",   surname:"Flanagan"}//给a新增了一个属性,此属性是一个对象var b = Copy(a);b.key3 = '33333';console.log(b.name);//输入JavaScriptconsole.log(b.key3);//输出33333console.log(a.key3);//输出undefined                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          4

a对象name是字符串,author是一个对象,当把a拷贝到b中,1,2属性顺利的拷贝到b对象中去,给b对象新增一个字符串类型的属性,b能正常修改,但是a中并无定义,说明了对象的key3(基本类型)并没有关联到父对象中去,所以是undefined
但是

b.author ={  name:dadiaoge;}console.log(a.author);console.log(b.author);//两者都输出改变后的值

所以,当修改的属性变为对象或者数组时,那么父子对象之间会发生关联,即对b对象属性进行修改时,a,b 的author属性值均发生了改变,其在内存中的状态可以用以下图片来表示:

name 属于基本类型,所以拷贝的时候传递的是该数据段。但是author属性是一个对象值,所以author在拷贝的时候传递的是指向该对象的地址,无论复制多少个此属性,其值始终指向父对象author在堆上的内存空间

5.深拷贝

在实际情况中,我们在编码的时候并不希望父子对象之间发生关联,这时候可以用到深度拷贝,既然属性值是对象或者数组的时候,只会传址。那么我们使用递归来解决问题,把所有父对象中所有属于对象的属性类型都遍历赋给子对象就好

        function Student(){            this.name = "zhangsan",            this.age = 12,            this.grade = {                English : "good",                math : "so"            }        }        var a = new Student;//实例化一个对象        function Copy(p,c){            var c = c || {};//创建一个空对象            for(i in p){//变量p中的属性                if(typeof p[i] === 'object'){//如果p中的这个属性的类型是对象                    c[i] = (p[i].constructor === Array)?[]:{};//判断该属性的类型是不是数组类型,判断完毕返回一个类型                    Copy(p[i],c[i]);//判断结束完毕之后,遍历p此属性中的属性值并将它赋给新创建的对象的这个属性                }                else{                    c[i] = p[i];                }            }            return c;        }        var b = {};        b = Copy(a,b);        b.aihao = {                    English:"great"        }        console.log(a);//不改变其原有属性的值(可以通过打断点来自己看一下整个流程)

内存过程如下图: