js值类型引用类型和深浅拷贝

622 阅读5分钟

JS中的堆栈

堆和栈都是运行时内存分配的一个数据区

堆(heap)
堆(heap)用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象;
它是运行时动态分配内存的,因此存取速度较慢
栈(stack)
栈(stack)中主要存放一些基本类型的变量和对象的引用,(包含池,池存放常量),
其优势是存取速度比堆要快,并且栈内的数据可以共享,但缺点是存在栈中的数据大小
与生存期必须是确定的,缺乏灵活性

JavaScript的数据类型有哪些?

1.值类型(基本类型):string,number,boolean,undefined,null(这5种基本类型是按值访问的)es6新增了symbol

2.引用类型:Object(JS中除了基本类型以为都是对象,数组,函数,正则表达式)

基本数据类型存放在栈中

基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问

let a = 1;
let b = a;
b = 2;
console.log(a,b) //1,2

引用数据类型

存放在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置

引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。
通过这个引用地址可以快速查找到保存中堆内存中的对象
  let obj1 = {
      name:'lyl'
   }
  let obj2 = obj1;
  obj2.name = "yr";
  console.log(obj1.name); // yr

这说明obj1和obj2指向了用一个堆内存,obj1赋值给obj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了obj2,所以obj1和obj2指针都指向堆内存中同一个。

var a = [1,2,3,4];
var b = a;//传址 ,对象中传给变量的数据是引用类型的,会存储在堆中;
var c = a[0];
//传值,把对象中的属性/数组中的数组项赋值给变量,
这时变量C是基本数据类型,存储在栈内存中;改变栈中的数据不会影响堆中的数据
alert(b);//1,2,3,4
alert(c);//1
//改变数值 
b[2] = 6;
c = 7;
alert(a[2]);//6
alert(a[0]);//1

从上面我们可以得知,当我改变b中的数据时,a中数据也发生了变化;但是当我改变c的数据值时,a却没有发生改变。

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

浅拷贝

在定义一个对象或数组时,变量存放的只是一个地址,当使用对象拷贝时,如果属性是对象或者数组,我们传递的也只是一个地址,因此子对象在访问该属性时,会根据地址找到父对象指向的堆内存,父子对象是共享的。

var a={key1:"1"}
function Copy(p){
   var c ={};
   for (var i in p){
      c[i]=p[i]
   }    
   return c;
}
a.key2 = ["yr","lyl"]
var b = Copy(a);
b.key3 = "3"
alert(b.key1)//1
alert(b.key3)//3
alert(a.key3);//undefined

b.key2.push("xn")
alert(a.key2);//yr lyl xn

修改的属性变为对象或数组时,那么父子对象之间就发生关联

es6实现浅拷贝
      var a = {name:"yr",eating:[
            {
                name:'螺蛳粉'
            }, {
                name:'酸辣粉'
            }
        ]}
        var b = Object.assign({},a);
        //var b = {...a}
        b.age = 18;
        b.eating.push({
            name:'过桥米线'
        })
        console.log(a.age,a.eating);//undefined   螺蛳粉 酸辣粉 过桥米线

以上Object.assign和... 进行拷贝时,对象中属性的值是数组或对象,都是拷贝地址,如果对象中属性对应的是基本数据类型,就是拷贝值

深拷贝
JSON.parse(JSON.stringify(obj))

JSON.stringify 序列化(对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据) 利用对象序列化可以进行对象的"深克隆",即复制对象本身及引用的对象本身。但是无法复制不可枚举的属性 JSON.parse 反序列化

存在问题:如Date类型会被转为字符串类型,Undefined和RegExp类型丢失等问题。 无法拷贝存在循环引用的对象。 拷贝自身可枚举字符串属性,原型链丢失。

let obj = {
    name:'yr',
    age:undefined,
    like:[1,2,3,4],
    reg: new RegExp(),
    function(){
​
    }
}
Object.defineProperty(obj, "sex", {
    value: "female",
    enumerable: false //设置为不可枚举
});
let obj1 = JSON.parse(JSON.stringify(obj))

不希望父子对象之间产生关联,那么这时候可以用到深拷贝,属性值类型是数组和或象时只会传址,就用递归来解决

   function deepCopy(obj) {
      let cloneObj = Array.isArray(obj) ? [] : {}
      for (let key in obj) {
        if(typeof obj[key] === 'object') {
          cloneObj[key] = deepCopy(obj[key]);
        }else {
          cloneObj[key] = obj[key];
        }
      }
      return cloneObj
    }
    let a = {
      age:18,
      b: {name:'yr'}
    }
    let obj = deepCopy(a)
    obj.age = 16;
    console.log(a,obj)

闭环问题

环:子节点属性赋值了父节点

let a = {
    b:{}
}
a.b.c = a;

当要深克隆的对象是这种闭环对象时,就会发生无限递归 上面代码添加

a.b.c = a;

这时候使用weakMap协助return WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

   function deepClone(obj,hash = new WeakMap()){
        if(hash.has(obj)) return hash.get(obj)
        let cloneObj = Array.isArray(obj) ? [] : {}
        hash.set(obj,cloneObj)
        for(let key in obj){
            if(typeof obj[key] === 'object'){
                if(obj[key] === null){
                    cloneObj[key] = obj[key]
                }else if(obj[key].constructor === Date){
                    cloneObj[key] = obj[key]
                }else if(obj[key].constructor === RegExp){
                    cloneObj[key] = obj[key]
                }else{
                    cloneObj[key] = deepClone(obj[key],hash)
                }
            }else{
                cloneObj[key] = obj[key]
            }
        }
        return cloneObj
     }

    let a = {
      age:18,
      b: {name:'yr'},
      fn1:function(){
          console.log(1)
      },
      c: /a/,
      w:null,
      date:new Date(),
      q:undefined

    }
    a.b.c = a;
    let obj = deepClone(a)
    obj.age = 16;
    console.log(a,obj)

image.png

深克隆推荐工具lodash

常用工具lodash
npm i --save lodash

组件中引入
import lodash from 'lodash'
loodash.cloneDeep(obj)深拷贝