深入分析 JavaScript 的数据类型和拷贝

引用

学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。

最近在一个老项目上进行修改,遇见了自己的心里障碍,看别人的代码真的很难受,并且在不懂业务逻辑的情况下,所以在空闲时刻开始了学习,记录。

栈(stack)和堆(heap)

stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放

数据存储以及类型

JS分两种数据类型:

  1. 基本数据类型:Number、String、Boolean、Null、Undefined、Symbol(ES6),这些类型可以直接操作保存在变量中的实际值。

  2. 引用数据类型:Object(在JS中除了基本数据类型以外的都是对象,引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。)

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

var a = 10;
var b = a;
b = 20;
console.log(a); // 10值
console.log(b); // 20值

在这里插入图片描述

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

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

浅拷贝的实现方式

let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = Object.assign({}, demo)
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price);  
        // dayday 100

修改上面代码demo变量之后,对象clone_demo基本属性没有改变,但是修改demo对象中book引用属性时,对象clone_demo相应位置属性值也发生改变,同样的接下来展开运算符也是一样效果

let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = {...demo}
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price);  
        // dayday 100

可以看到展开运算… 效果跟Object.assign() 效果是一样的。

let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]

a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]

console.log(b);
//  ["1", [4, 3]]

slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的「浅拷贝」。原始数组不会被改变。可以看出,改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝,相应的还有concat等,在工作中面对复杂数组结构要额外注意。

深拷贝实现方式

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。

 let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = JSON.parse(JSON.stringify(demo))
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price);  
        // dayday 45

完全改变变量 demo 之后对 clone_demo 没有任何影响,这就是深拷贝的魔力。
同样的对于数组使用该方法也是可以达到深拷贝的。
注意的就是:

  1. 会忽略undefined Symbol
  2. 不能序列化函数
  3. 不能解决循环引用的对象
  4. 不能正确处理 new Date()
  5. 不能处理正则

对于undefined symbol 函数三种情况会直接忽略

 let demo = {
            name : 'dayday',
            h1 : undefined,
            h2 : Symbol('dayday'),
            h3 : function () {},
        }
        let clone_demo = JSON.parse(JSON.stringify(demo))
        console.dir(clone_demo)
        // { name : 'dayday' }

循环引用情况下,会报错

let obj = {
    a: 1,
    b: {
        c: 2,
     d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

new Date 情况下,转换结果不正确。

new Date();
// Wed Jul 01 2020 16:19:07 GMT+0800 (中国标准时间) {}

JSON.stringify(new Date());
// ""2020-07-01T08:19:19.860Z""

JSON.parse(JSON.stringify(new Date()));
// "2020-07-01T08:19:35.569Z"

正则情况下

let demo = {
    name: "daydaylee",
    a: /'123'/
}
console.log(demo);
// {name: "daydaylee", a: /'123'/}

let clone_demo = JSON.parse(JSON.stringify(obj));
console.log(clone_demo);
// {name: "daydaylee", a: {}}

除了上面介绍的深拷贝方法,常用的还有jQuery.extend() 和 lodash.cloneDeep(),由于文章篇幅的问题,这里就不多介绍了,有兴趣的可以自己去了解了解

其实写到这里还没很完善,后续在慢慢补充

愿我们越来越好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值