网站首页 > 技术文章 正文
在 JavaScript 中,因为对象和数组是引用类型,所以当我们直接将它们赋值给另一个变量时,实际上是将它们的引用地址复制了一份。这样,当我们对其中一个变量进行修改时,另一个变量也会受到影响。因此,为了避免这种情况,我们通常需要使用拷贝方法来复制一个对象或数组的值并创建一个新的副本。本文将来介绍浅拷贝和深拷贝的概念以及它们的应用场景。
浅拷贝
浅拷贝是指将原对象或数组的值复制到一个新的对象或数组中,但是新的对象或数组的属性或元素依然是原对象或数组的引用,这意味着当我们修改其中一个对象或数组时,另一个对象或数组也会受到影响。因此,浅拷贝通常只针对引用类型。下面是常见的浅拷贝方法:
1. Object.create(obj)
Object.create() 方法可以用于创建一个新对象,并将原对象作为新对象的原型。这样,新对象就可以访问原对象的所有属性和方法。
示例代码:
const obj1 = { name: '张三', age: 18 };
const obj2 = Object.create(obj1);
console.log(obj2.name); // 张三
obj1.name = '李四'
console.log(obj2.name); //李四
我们可以看到,obj2具有obj1的属性,且当obj1的属性修改时,obj2的这个属性也变了。
2. Object.assign({}, obj)
Object.assign() 方法是用于将一个或多个源对象的属性复制到目标对象中。它的语法如下:
Object.assign(target, ...sources)
其中,target 是目标对象,sources 是一个或多个源对象。
该方法会遍历源对象的所有可枚举属性,并将其复制到目标对象中。如果目标对象中有相同的属性,则后面的属性会覆盖前面的属性。
需要注意的是,该方法只会复制对象自身的属性,不会复制继承自原型链上的属性。而且,如果源对象中有值为 null 或 undefined 的属性,则该属性不会被复制。
浅拷贝示例代码:
let a = {
name:'A',
like:{n: 'coding'}
}
let b = Object.assign({},a)
a.name = 'B'
a.like.n = 'running'
console.log(b.name); // 输出A
console.log(b.like.n); // 输出running
当a中的原始类型属性被修改时,b中的这个属性是不会跟着修改的,但是引用类型被修改则不然,这是因为原始类型与引用类型的存放位置是不同的,在代码执行的时候,原始类型的值存放在调用栈中,而引用类型的值则会被存放在堆中。
新对象b中从a中复制过来的所有原始类型的属性都是新的属性,在b的执行上下文中具有自己的空间地址,而复制过来的引用类型like的值并不是它真正的值,而是它在堆中的引用地址,所以在上述代码中b.like和a.like仍然是同一个对象,它们在调用栈中的值都指向同一个堆中的地址,所以当a.like中的属性被修改时,b.like的属性也变了。
3. [].concat(arr)
[].concat() 方法可以用于将一个或多个数组合并成一个新数组。
示例代码:
let arr = [1,2,3]
let newArr = [].concat(arr)
arr.push(4)
arr[0] = 10
console.log(newArr);//输出[1,2,3]
这个时候修改原数组arr,新数组newArr是不变的,那是不是说明这是深拷贝呢?不,如果数组中的值为对象呢?
let arr = [1,2,3,{n:10}]
let newArr = [].concat(arr)
arr[3].n = 100
console.log(newArr); //输出[1,2,3,{n:100}]
当对象中的值修改时,新数组中的对象中这个值也变了,原理同上一个方法相似。
4. 数组解构 [...arr]
数组解构是一种将数组或类数组对象中的值提取出来,赋值给变量的方法。它可以让我们更方便地访问数组的元素。
[...arr]使用示例代码:
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // [1, 2, 3, 4, 5, 6]
为什么它也是浅拷贝方法呢?
let arr = [1,2,3,{n:10}]
let newArr = [...arr]
arr[3].n = 100
console.log(newArr);
同上面讲的方法一样,它也是原数组有些值修改后会跟着变。
5. arr.slice()
slice() 方法是 JavaScript 数组对象的一个方法,用于从数组中提取指定区间的元素创建一个新的数组,并不会对原数组产生影响。它接受两个参数:开始索引和结束索引,当参数为空时,则会复制整个原数组 arr。这意味着返回的新数组与原数组具有相同的元素、长度和顺序。
示例代码:
const arr1 = [1, 2, 3];
const arr2 = arr1.slice();
console.log(arr2); // [1, 2, 3]
若原数组中值存在对象,则结果也同上面的方法,原数组中的对象中的属性值改变新数组也跟着变。
深拷贝
深拷贝是指将原对象或数组的值复制到一个新的对象或数组中,并且新的对象或数组的属性或元素完全独立于原对象或数组,即它们不共享引用地址。因此,当我们修改其中一个对象或数组时,另一个对象或数组不会受到任何影响。
常见的深拷贝方法是使用 JSON.parse(JSON.stringify(obj)),它的语法如下:
let newObj = JSON.parse(JSON.stringify(obj));
JSON.stringify() 方法是 JavaScript 的一个内置方法,用于将一个 JavaScript 对象或值转换为 JSON 字符串,而JSON.parse()用于将 JSON 字符串解析为对应的 JavaScript 对象或值。
这个方法可以将一个对象序列化为 JSON 字符串,然后再将 JSON 字符串解析为一个新的对象,因为它曾转化为字符串,所以不会出现像浅拷贝中那种引用类型指向同一地址的情况,从而实现深拷贝。
let obj = {
name: 'A',
age:18,
a:{
n:1
}
}
let obj2 = JSON.parse(JSON.stringify(obj))
obj.a.n = 2
console.log(obj2.a.n); //输出1
但是,这种方法也存在一些缺陷:
- 不能处理 undefined、function 和 Symbol 等特殊数据类型,因为它们在 JSON 中没有对应的表示方式。
- 无法处理循环引用,即当一个对象的属性指向自身或形成循环引用关系时,深拷贝会陷入无限递归。
手写实现深拷贝
function deepCopy(obj){
//不是引用类型就不拷贝
if(!(obj instanceof Object)) return obj
//如果形参obj是数组,就创建数组,如果是对象就创建对象
let objCopy = obj instanceof Array ? [] : {}
for(let key in obj){
if(obj instanceof Object){
objCopy[key] = deepCopy(obj[key])
} else{
if(obj.hasOwnProperty(key)){
objCopy[key] = obj[key]
}
}
}
return objCopy
}
- 上一篇: 前端面试模拟:常见的3个JavaScript经典考题
- 下一篇: JS设计模式之访问者模式
猜你喜欢
- 2025-01-02 JavaScript字符串toString()方法教程
- 2025-01-02 vue3 - 内置组件Teleport的使用
- 2025-01-02 网页三维CAD中加载和保存STEP模型
- 2025-01-02 在.NET Web API中设置响应输出Json数据格式的两种常用方式
- 2025-01-02 剖析Selenium代码执行时元素查找失败的缘由
- 2025-01-02 H5的Canvas绘图——使用fabricjs绘制一个可多选的随机9宫格
- 2025-01-02 jscanify:支持 Node.js/浏览器/React 移动文档扫描仪
- 2025-01-02 js事件机制详解
- 2025-01-02 Java ArrayList用法详解附代码示例
- 2025-01-02 如何用枚举快速提高编程效率,数据元素快速对应,小枚举大作用
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)