js中的深拷贝和浅拷贝

js的数据存储

程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:

  • 栈(stack)自动分配 的内存空间,大小和生存期是确定的,它由系统自动释放,寻址比堆快。栈中主要存放的是基本类型的值以及指向堆中的数组或者对象的地址
  • 堆(heap)动态分配 的内存,大小不定也不会自动释放。(因此需要浏览器提供的垃圾回收机制进行内存的回收,否则可能会导致内存泄漏

PS 1:栈的数据共享

栈有一个很重要的特殊性,就是存在栈中的数据可以共享

假设我们同时定义 int a = 3; int b = 3;,编译器先处理 int a = 3;

首先它会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为 3 的地址,没找到,就开辟一个存放 3 这个字面值的地址,然后将 a 指向 3 的地址

接着处理 int b = 3;,在创建完 b 的引用变量后,由于在栈中已经有 3 这个字面值,便将 b 直接指向 3 的地址。这样,就出现了 a 与 b 同时均指向 3 的情况。

特别注意的是,这种 字面值的引用类对象的引用 不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化

相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完 a 与 b 的值后,再令 a=4;,那么,b 还是等于 3

在编译器内部,遇到 a=4; 时,它就会重新搜索栈中是否有 4 的字面值,如果没有,重新开辟地址存放 4 的值;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值

PS 2:栈的溢出

栈创建的时候,大小是确定的,如果超出了浏览器规定的栈限制,就会报 stack overflow 错误,而堆的大小是不确定的,简单的测试:

测试环境是 16G 内存的电脑,需要注意的是:根据栈的定义可以知道如果 inc 函数里有变量声明的话也是会有内存占用的

1、谷歌浏览器chrome 55.0版本下限制是41909条

2、IE8浏览器下限制是3062条

PS 3:字符串的创建

// 创建'abc'字符串
var str=new String('abc');
var str='abc';
  • 第一种:用new关键字来新建String对象,对象会存放在 中,每调用一次就会创建一个新的对象

  • 第二种:在 中存放 值abc 和对 值的引用

第二种方式创建多个 abc 字符串,在内存中只存在一个值,有利于节省内存空间。并且由于栈访问更快,所以对于性能的提高大有裨益。

而第一种方式每次都在堆中创建一个新的String对象,加重了程序的负担。并且堆的访问速度慢,对程序性能的影响也大。

数据类型

  • 基本数据类型:StringNumberBooleanSymbolUndefinedNull
  • 引用数据类型:Object

基本数据类型(栈)

  • 存储在
  • 直接按 存放的,所以可以直接访问。

引用数据类型(堆)

  • 存储在

引用数据类型的堆存储

PS:js中基本数据类型的值不可变

如果一个变量是基本数据类型,那么在它被赋值之后,这个值就是不可变的

//示例1
var str = "hello";
str += "world";
console.log(str); //helloworld

//示例2
var myStrNew = "Bob";
myStrNew = "Job";
console.log(myStrNew); //Job

//示例3
var myStr = "Bob";
myStr[0] = "J";
console.log(myStr); //Bob
  • 字符串 的值是不可变的,这意味着一旦字符串被创建就不能被改变
  • 获取字符串中某个 index 的字符应该用 let str = myStr.charAt(2) 。因为当 index 的取值不在 str 的长度范围内时, str[index] 会返回 undefined,而 charAt(index) 则返回空字符串。并且str[index] 不兼容 ie6-ie8, charAt(index) 可以兼容。
  • 针对字符串变量,有很多方法都可以应用在其上面,这些所有的方法看上去返回了一个 修改后的字符串,实际上返回的是一个 新的字符串值

此外,用 const 定义的变量是不能改变的,这里其实分两种情况:如果定义的是 基本数据类型的变量,那么的确是不能对其做任何操作来改变其值的;但如果定义的是 引用类型的变量,由前面的分析可知,这个变量存储的其实只是一个地址,也就是说我们不能改变的是这个 地址,但是 地址中的内容 我们还是可以改变的

测试

浅拷贝和深拷贝

主要的区别就是,获得的是 还是 地址

浅拷贝的实现

for···in 只循环第一层

// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
  }
   return obj2;
}
var obj1 = {
   a: 1,
   b: 2,
   c: {
         d: 3
      }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4

深拷贝的实现

采用递归去拷贝所有层级属性

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){      //数组的typeof也是对象
        for(key in obj){
            if(obj.hasOwnProperty(key)){      
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4], b=deepClone(a);
a[0]=2;
console.log(a,b);

PS:更多方法请看参考 - js的深拷贝和浅拷贝

参考文章

posted @ 2020-07-24 13:02  _Sleeping  阅读(625)  评论(0)    收藏  举报