js 浅拷贝 和 深拷贝

目录

前言

一、基本类型

二、引用数据类型

1、引用类型的浅拷贝和深拷贝分析

(1)、引用类型的浅拷贝分析

(2)、引用类型的深拷贝分析

三、引用类型的浅拷贝和深拷贝的方法

1、浅拷贝的方法

(1)、直接赋值法

(2)、局部作用域直接使用全局作用域变量

2、深拷贝的方法

(1)、JSON.parse(JSON.stringify(obj))(🌟🌟🌟🌟🌟)

(2)、完美的深拷贝方法——递归(🌟🌟🌟🌟🌟)

(3)、JQuery 的 extend() 方法

3、ES6 之深拷贝与浅拷贝的实现

(1)、ES6 的 Object.assign() 方法

(2)、ES6 的扩展运算符(...)(🌟🌟🌟🌟🌟)

四、考考你

五、应用

1、Object.assign 和 扩展运算符(...)深拷贝踩坑


前言

若想拷贝一个数据,既要看是什么数据类型,也要看用的是什么方法。

VN图表示引用数据类型的浅拷贝与深拷贝的关系:

一、基本类型

基本类型包括:字符串、布尔值、数字、undefined、null、symbol。

基本类型的存储方式:基本类型以“-”的方式,直接存储在 栈 中。

基本类型的拷贝都是深拷贝:

let a = 1;
    b = a;

b = 2;

b; // 2
a; // 1

当 b=a 时,栈内存会新开辟一个内存:

当你此时修改 a=2,对 b 并不会造成影响。

二、引用数据类型

引用数据类型包括:数组、对象、Date、RegExp、函数、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。

引用数据类型的存储方式:引用类型以"-引用地址-"的方式,存在“”内存中,存在“”内存中,但是 “栈” 内存会提供一个 “引用地址 指向 “堆” 内存中的值。

引用类型:将该对象引用地址存储在中,然后对象里面的数据存放在中。(数组、对象、Date、RegExp、函数、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math))。

引用类型的拷贝:引用类型的拷贝有浅拷贝和深拷贝之分。

1、引用类型的浅拷贝和深拷贝分析

(1)、引用类型的浅拷贝分析

对象的浅拷贝,拷贝的仅仅是“引用地址”,不是值。

let a = [0,1,2,3,4], 
    b = a; 
console.log(a === b);       // true 
a[0]=1; 
console.log(a, b);           // a: [1,1,2,3,4] b:[1,1,2,3,4]

我们以上面的例子画个图,初始:

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

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

 

【结论】

引用类型的浅拷贝,拷贝的仅仅是“引用地址”,两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。

(2)、引用类型的深拷贝分析

对象的深拷贝,把引用地址和值一起拷贝过来。

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj === "object"){
        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 obj = {
    a: "hello",
    b: {
        a: "hello",
        b: 21
    }
};

// 拷贝
let cloneObj = deepClone(obj);
console.log('-----原a', cloneObj.a);               // hello
console.log('-----原b', cloneObj.b);               // {a: "hello", b: 21}

更改原对象,看看拷贝过来的对象是否变化 :

obj.a = "changed";
obj.b = {a: "world", b: 25}
console.log('-----新a', cloneObj.a);                // hello
console.log('-----新b', cloneObj.b);                // {a: "hello", b: 21}

console.log(cloneObj.a === obj.a);                  // false

【结论】

引用类型的深拷贝,把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。

三、引用类型的浅拷贝和深拷贝的方法

只有引用数据类型才有深拷贝与浅拷贝之说。

  • 引用类型的浅拷贝:拷贝的仅仅是“引用地址”,两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。
  • 引用类型的深拷贝:把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。

1、浅拷贝的方法

(1)、直接赋值法

var obj = {
    a: 1,
    b: {
        c: 2
    }
}

var cloneObj = obj;// 直接赋值是浅拷贝

cloneObj.a = 3;
cloneObj.b.c = 4;

cloneObj;
// {
//     a: 3,
//     b: {
//         c: 4
//     }
// }

obj;
// {
//     a: 3,
//     b: {
//         c: 4
//     }
// }

(2)、局部作用域直接使用全局作用域变量

局部作用域内直接使用全局作用域变量(使用前不做处理:比如使用 ES6 的拓展运算符等,关于 ES6 新语法对引用类型拷贝的影响下面会讲到)。

var obj = {
    a: 1,
    b: { c: 2 }
};

function test (x) {
    x.a = 3;
    x.b.c = 4
    console.log('---函数作用域 x', x);
}

test(obj);
console.log('---全局作用域 obj', obj);

// ---函数作用域 x 
// {
//     a: 3,
//     b: {
//         c: 4
//     }
// }

// ---全局作用域 obj
// {
//     a: 3,
//     b: {
//         c: 4
//     }
// }

2、深拷贝的方法

(1)、JSON.parse(JSON.stringify(obj))(🌟🌟🌟🌟🌟)

let obj = {
    a: "hello",
    b: {
        c: 21
    },
    d: ["Bob", "Tom", "Jenny"],
    e: function() {
        alert("hello world");
    }
};

let cloneObj = JSON.parse(JSON.stringify(obj));

obj.a = "changed";
obj.b.c = 25;
obj.d = [1, 2, 3];
obj.e = () => { alert("changed") };

// {
//     a: "hello",
//     b: {
//         c: 21,
//     },
//     d: ["Bob", "Tom", "Jenny"],
//     e: undefined
// }

 【拓展】

在 JavaScript 中,当对象中的属性值为 undefined 时,在执行 JSON.stringify(obj) 时,这些属性会被忽略,因此最终的 JSON 字符串中不会包含这些属性。对象中的属性值为 null 没问题。例如:

const obj = { a: undefined, b: null };

console.log(obj); // 输出 { a: undefined, b: null }
console.log(JSON.stringify(obj)); // 输出 '{ b: null }'

(2)、完美的深拷贝方法——递归(🌟🌟🌟🌟🌟)

// 封装一个深拷贝的函数
const deepClone = (obj) => {
  let cloneObj = Array.isArray(obj) ? [] : {};
  for (let k in obj) {
    if (obj.hasOwnProperty(k)) { // 判定 obj 里是否有 k 这个属性。
      if(typeof obj[k] === "object"){ // 判定 k 是不是对象(广义)
        cloneObj[k] = deepClone(obj[k]);
      } else {
        cloneObj[k] = obj[k];
      }
    }
  }
  return cloneObj;
}

// 测试
let obj = {
  a: "hello",
  b: {
      c: 21
  },
  d: ["Bob", "Tom", "Jenny"],
  e: function() {
      alert("hello world");
  }
};
const clone = deepClone(obj);
console.log(clone);

obj.a = "changed";
obj.b.c = 25;
obj.d = [1, 2, 3];
obj.e = () => { alert("changed") };
console.log(clone);
// 可见,改变原对象并不影响深拷贝的对象:
// {
//     a: "hello",
//     b: {
//         c: 21,
//     },
//     d: ["Bob", "Tom", "Jenny"],
//     e: function(){
//         alert("hello world");
//     }
// }

(3)、JQuery 的 extend() 方法

$.extend( [deep ], target, object1 [, objectN ] )

deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 

let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

可以看到,效果与上面方法一样,只是需要依赖JQ库。

3、ES6 之深拷贝与浅拷贝的实现

(1)、ES6 的 Object.assign() 方法

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

Object.assign(target, ...sources)

参数:

  • target:目标对象。
  • sources:任意多个源对象。

返回值:

  • 目标对象会被返回。

一层为深拷贝,多层为浅拷贝。

一层为深拷贝:

let obj = {
    a: "hello",
    b: 21
};
 
let cloneObj= Object.assign({}, obj);

cloneObj.a = "changed";
cloneObj.b = 25;

cloneObj;
// {
//     a: "changed",
//     b: 25
// }

obj;
// {
//     a: "hello",
//     b: 21
// }

多层且为浅拷贝:

let obj = {
    a: "hello",
    b:{
        c: 21
    }
};
 
let cloneObj= Object.assign({}, obj);

cloneObj.a = "changed";
cloneObj.b.c = 25;

cloneObj;
// {
//     a: "changed",
//     b: {
//         c: 25
//     }
// }

obj;
// {
//     a: "hello",
//     b: {
//         c: 25
//     }
// }

(2)、ES6 的扩展运算符(...)(🌟🌟🌟🌟🌟)

一层为深拷贝,多层为浅拷贝。

一层为深拷贝:

let obj = { 
    a: 'hello',
    b: 21
}

let cloneObj = {...obj};

cloneObj.a = 'boy';
cloneObj.b = 25;

cloneObj;
// {
//     a: "boy", 
//     b: 25
// }


obj;
// {
//     a: "hello", 
//     b: 21
// }

多层为浅拷贝:

let obj = { 
    a: 'hello',
    b: {
        c: 21
    }
}

let cloneObj = {...obj};

cloneObj.a = 'boy';
cloneObj.b.c = 25;

cloneObj;
// {
//     a: "boy", 
//     b: {
//         c: 25
//     }
// }

obj;
// {
//     a: "hello", 
//     b: {
//         c: 25
//     }
// }

四、考考你

阿里面试题:请给出最终输出的值?

var a = 1;

var obj = {
    b: 2
};

var fn = function () {};
fn.c = 3;

function test (x, y, z) {
    x = 4;
    y.b = 5;
    z.c = 6;
    console.log('---函数作用域', [x, y.b, z.c]);
}

test(a, obj, fn);
console.log('---全局作用域', [a, obj.b, fn.c]);

复制上述代码,在浏览器执行一下发现:

当全局作用域和函数作用域同时拥有同名的变量,更改函数里的变量:

  • 如果该变量是基本类型则不会改变全局作用域里的同名变量的值;
  • 如果该变量是引用类型则会改变全局作用域里的同名变量的值,因为这两个变量的引用地址是相同的,同一个引用地址指向的是同一个值。

拓展:

var a = 1;

var obj = {
    b: 2,
    c: { d: 3 }
};

var fn = function () {};
fn.e = 4;

function test (x, y, z) {
    x = 5;
    y.b = 6;
    y.c.d = 7
    z.e = 8;
    console.log('---函数作用域', [x, y.b, y.c.d, z.e]);
}

test(a, obj, fn);
console.log('---全局作用域', [a, obj.b, obj.c.d, fn.e]);

// ---函数作用域 (4) [5, 6, 7, 8]
// ---全局作用域 (4) [1, 6, 7, 8]

从拷贝的角度分析:

函数作用域里的形参 可以看作是从 全局作用域里的变量 拷贝而来,基本类型值是深拷贝,所以拷贝的变量改变后不影响原来的变量。引用类型,局部作用域直接使用全局作用域变量,是浅拷贝,所以拷贝的变量改变后会影响原来的变量。但是,如果局部作用域不直接使用全局作用域变量,而是用 ES6 语法(比如扩展运算符)处理一下,就是深拷贝了:

var a = 1;

var obj = {
    b: 2,
    c: { d: 3 }
};

var fn = function () {};
fn.e = 4;

function test (x, y, z) {
    x = 5;
    y.b = 6;
    y.c.d = 7
    z.e = 8;
    console.log('---函数作用域', [x, y.b, y.c.d, z.e]);
}

test(a, { ...obj }, fn);
console.log('---全局作用域', [a, obj.b, obj.c.d, fn.e]);

// ---函数作用域 (4) [5, 6, 7, 8]
// ---全局作用域 (4) [1, 2, 7, 8]

五、应用

1、Object.assign 和 扩展运算符(...)深拷贝踩坑

const arr = [{
    type: 1,
    flag: false
}, {
    type: 2,
    flag: true
}, {
    type: 3,
    flag: true
}];

const obj = {
    title: "",
    imgUrl: "",
    content: "",
}

const newList = arr.map(item => {
    const { type, flag } = item;
    Object.assign(obj, { type, flag });
    return obj;
})

console.log(newList);
// [{
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 3,
//    "flag": true
// },
// {
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 3,
//    "flag": true
// },
// {
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 3,
//    "flag": true
// }]

上述代码,结果显然是错的。因为你拷贝的是变的部分,而不是不变的部分。由于 Object.assign() 以及 扩展运算符(...)在只有一层时是深拷贝——数据改变就会影响最终的结果,所以尽量拷贝不变的一方。解决方案如下:

方案一(推荐):

const arr = [{
    type: 1,
    flag: false
}, {
    type: 2,
    flag: true
}, {
    type: 3,
    flag: true
}];

const obj = {
    title: "",
    imgUrl: "",
    content: "",
}

const newList = arr.map(item => {
    const { type, flag } = item;
    return {...obj, type, flag};
})

console.log(newList);
// [{
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 1,
//    "flag": false
// },
// {
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 2,
//    "flag": true
// },
// {
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 3,
//    "flag": true
// }]

还有一种办法供参考:

方案二:

const arr = [{
    type: 1,
    flag: false
}, {
    type: 2,
    flag: true
}, {
    type: 3,
    flag: true
}];

const newList = arr.map(item => {
    const { type, flag } = item;
    return {
        title: "",
        imgUrl: "",
        content: "",
        type,
        flag
    };
})

console.log(newList);
// [{
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 1,
//    "flag": false
// },
// {
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 2,
//    "flag": true
// },
// {
//    "title": "",
//    "imgUrl": "",
//    "content": "",
//    "type": 3,
//    "flag": true
// }]

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
JavaScript中,浅拷贝是指只拷贝了对象的一层内容,而引用类型的深层次数据仍然共享内存地址。简单来说,浅拷贝只是拷贝了对象的外层,而没有完全拷贝整个对象的内容。 举个例子,如果有一个数组里面包含了另一个数组或对象,浅拷贝只会拷贝外层的数组,而深层次的数组或对象仍然是共享内存地址的。 在JavaScript中,有一些常见的浅拷贝方法,比如使用Object.assign()方法可以将一个对象的属性拷贝到另一个对象中。通过Object.assign()方法,我们可以将源对象的属性浅拷贝到目标对象中。对于目标对象的已有属性,如果与源对象中的属性重复,那么源对象的属性会覆盖目标对象的属性值。 综上所述,浅拷贝只是拷贝了对象的一层内容,并不能改变对象的地址,所以深层次的引用类型仍然是共享内存地址的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JS深拷贝浅拷贝](https://blog.csdn.net/baidu_33438652/article/details/124219924)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [js浅拷贝深拷贝](https://blog.csdn.net/weixin_57660141/article/details/126060089)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值