由Object.is({}, {})返回false,来看看JavaScript的深拷贝与浅拷贝

var a = function() {console.log(11)};
var b = function() {console.log(11)};
console.log( a==b ); //false

console.log( {}=={} ); //false
console.log( []==[] ); //false

如你所见,上述三个例子结果都为false。

相信很多人都碰到过上述情况,像我这种基本功不太扎实的小白,估计都会看的一脸懵吧。别着急,本博客就是来解决问题的。

首先,先来了解两个概念:栈 stack 和 堆 heap。

堆、栈

栈(stack):先进后出;自动分配内存空间,由系统自动释放;使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完立即释放。

堆(heap):队列优先,先进先出;动态分配内存,大小不定也不会自动释放;存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定;一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。

js中基本数据类型和引用数据类型

基本类型:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配。

Undefined / Null / Boolean / Number / String,它们是直接按值存放的,可以直接访问。

引用类型:存放在堆内存中的对象;每个空间大小不一样,根据情况进行特定的分配。

当我们需要访问引用数据类型 (对象 / 数组 / 函数) 的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

所以,现在就可以解释 var a = function(){}; var b = function(){}; a==b 为false了。

变量a实际保存的是指向堆内存中对象的一个指针,而b保存的是指向堆内存中另一个对象的一个指针;虽然这两个对象的值是一样的,但它们是独立的2个对象,占了2份内存空间;所以 a==b 为 false。

如果 var a = {}; var b = a; 这时变量b复制了变量a保存的指针,它们都指向堆内存中同一个对象;所以 a==b 为 true。

传值与传址

基本类型与引用类型最大的区别实际就是 传值与传址的区别。

var a = [1,2,3,4,5];
var b = a;
var c = a[0];
console.log(b);  // [1,2,3,4,5]
console.log(c);  // 1
 
b[4] = 6;
c = 7;
console.log(a[4]);  //6
console.log(a[0]);  //1

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

这就是传值与传址的区别。因为a是数组,属于引用类型,所以a给b传的是栈中的地址,而不是堆内存中的对象。而c仅仅是从a堆内存中获取的一个数值,并保存在栈中。所以b修改的时候,会根据地址回到a堆内存中修改;c则直接在栈中修改,并且不能指向a堆内存中。

讲到栈和堆,不得不提浅拷贝和深拷贝。

浅拷贝

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

var a = {name: 'zyj'};
 
    function Copy(q) {
        var m = {};
        for(var i in q) {
            m[i] = q[i];
        }
        return m;
    }
 
    a.hobby = ['reading', 'coffee'];
    var b = Copy(a);
    b.age = 18;
    console.log(b.name);  //zyj
    console.log(b.hobby);  //['reading', 'coffee']
    console.log(b.age);  //18
    console.log(a.age);  //undefined

a对象中的name属性值为字符串,hobby为数组。a拷贝到b,两个属性均能顺利拷贝。 给b对象新增一个number类型的属性age时,b能够正常修改,而a中无定义。说明 子对象 b 的 age 并没有关联到 父对象 a 中,所以为undefined。

b.hobby.push('dancing');
console.log(b.hobby); //['reading', 'coffee', 'dancing']
console.log(a.hobby); //['reading', 'coffee', 'dancing']

但是,若修改的属性为对象或数组时,那么父子对象之间就会发生关联。其在内存的状态,见下图。
在这里插入图片描述
原因是 age的值属于基本类型,所以拷贝的时候传递的是该数据值;但hobby的值是堆内存中的对象,所以hobby在拷贝的时候传递的是指向hobby对象的地址,无论复制多少hobby,其值始终是指向父对象的hobby对象的内存空间。

深拷贝

在实际编码中,我们不希望父子对象之间产生关联,这时就用到深拷贝。既然属性值类型是数组或对象时只会传址,那么就可以用递归来解决这个问题,把父对象中所有属于对象的属性类型遍历赋给子对象即可。代码如下:

var a = {name: 'zyj'};
    function Copy(q, m) {
        var m = m || {};
        for(var i in q) {
            if(typeof q[i] === 'object') {
                m[i] = (q[i].constructor === Array)?[]:{};
                Copy(q[i], m[i]);
            }else {
                m[i] = q[i];
            }
        }
        return m;
    }
 
    a.hobby = ['reading', 'coffee'];
    var b = {};
    b = Copy(a, b);
    b.hobby.push('dancing');
    console.log(b.hobby); //['reading', 'coffee', 'dancing']
    console.log(a.hobby); //['reading', 'coffee']

修改b子对象的hobby数组时,没有使a父对象中的hobby数组新增一个值,即子对象没有影响到父对象a中的hobby,存储模式如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值