js数据存储问题简析(深浅拷贝)

   我们都知道js中的变量分为原始数据类型和引用数据类型,其中原始数据类型包括,字符串类型,数字类型,布尔类型,undefined,null,以及后来新添加的标志类型(symbol),引用数据类型就是对象数据类型,他们在计算机中所存储的方式也不近相同,原始数据类型的访问方式是按值访问,而引用数据类型的访问方式是按引用访问,一般来说,各类书籍介绍到这里就会停下来,可是我们仔细想想,什么是按值访问,什么是按引用访问吗?我们今天就来深入了解一下这个问题。

   原来,在js中存在着堆和栈,栈存放基础数据类型和指向堆的地址,堆中储存着引用数据类型,每当变量存储一个基本数据类型,便在栈里面建立一个新的空间,每当要引用这个数据时,js后台便会直接引用这个值类型,这也是按值引用的意思,而对象要复杂一些,每当建立一个新的对象实例,计算机便会自动在堆中建立一个引用数据类型空间,然后在栈里存放一个指向这个地址空间的地址,当调用对象数据时,首先是调用这个指针所在的地址,这就是按引用访问。

  这就是按值访问和按引用访问的真实意义,可是这又会带来新的问题,也就是俗称的深拷贝和浅拷贝的问题,所谓深浅拷贝的区分,也就是,在复制一个变量的值后,修改复制后的值,原先的值是否会被改变,这样说有点抽象我们通过一个图表来看看:

浅拷贝:

      栈内存                                             堆内存
name           val                                       val

    a            堆地址1 ============>    [0,1,2,3,4]

b = a 进行拷贝时,其实复制的是a的应用地址,并非堆里面的值。


      栈内存                                                堆内存
name           val                                           val

    a            堆地址1 ============>    [0,1,2,3,4]
    b            堆地址1 ============>    

当我们a[0] = 1进行数组修改时,a和b由于指向同一个地址,所以b自然也收到了影响,这就是浅拷贝

  栈内存                                                     堆内存
name           val                                             val

    a            堆地址1 ============>    [1,1,2,3,4]
    b            堆地址1 ============>    

如果在堆内存中也开辟一个新的内存专门为b存放值,就达到了深拷贝的效果

  栈内存                                                      堆内存
name           val                                              val

    a            堆地址1 ============>    [0,1,2,3,4]
    b            堆地址1 ============>    [0,1,2,3,4]
 

从这几个表格就可以清楚的看出两者的区别,因为这个特性,可能会对我们写代码造成影响,这点需要牢记,如何解决呢,要想实现深拷贝有以下几种方法可以解决:

1.采用递归拷贝所有的层级属性

2.JSON对象来实现

3.jQuery的extend方法实现深拷贝

4.手动实现(较为笨拙)

5.for in遍历实现

如果感兴趣可以自己去查询相关资料,以后有时间我也会详细的为大家解释一番。

注意:深拷贝只针对较为复杂的对象类型数据,一般的数据复制并不需要考虑这些问题。

数据类型传输中还有一点是极易混淆人的,就是函数参数的传递方式,js中所有的参数都是按值传递的,并不像上面不同数据类型有着不同的访问方式,

有些迷糊?没关系,我们看看几个红宝书上给的例子:

(原文)在按值传递参数时,值会被复制到一个局部变量(arguments对象中的一个槽位),在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地的修改会反映到函数外部

    <script>
        function addTen(num) {
            num +=10;
            return num;
        }

        let count = 20;
        let result = addTen(count);
        console.log(count);
        console.log(result);
    </script>

打印结果:

 这里显示如果输入的参数数据类型是原始数据类型,也即按值传递参数时对本地的修改不会传递到函数外部(也既count的值并没有修改,如果修改了,说明参数传递是按照值传递的)这里还好理解一些,下面则是容易混淆的部分,如果输入的是对象这个复杂的数据类型呢?我们来看看例子:

    <script>
         function setName(obj) {
            obj.name = "Nicholas";
         }
         let person = new Object();
         setName(person);
         console.log(person.name);
    </script>

打印结果:

 可以清楚的看到在外部定义的person对象因为函数内部的参数传递发生了变化,这表明参数是按照引用传递的,可是我们前面才刚刚讲到函数的参数是按值传递的,这不是前后矛盾吗?我们再看看下面这个例子:

    <script>
         function setName(obj) {
            obj.name = "Nicholas";
            obj = new Object();
            obj.name = "greg"
         }
         let person = new Object();
         setName(person);
         console.log(person.name);
    </script>

我们在函数内部添加了两端代码再来看看打印结果:

我们看到结果并未改变,如果是按照引用传输,在函数内部的属性值修改会修改指针导致输出的值应该是greg才对,这表明函数的参数传输方式依然是按值传输,只不过当obj在函数内部被重写时,它变成了一个指向本地对象的指针,而那个本地对象在函数执行结束时就被销毁了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值