JS深浅拷贝及其实现

基本数据类型和引用数据类型
JS数据分为基本数据类型和引用数据类型。基本数据类型的变量存储在栈中,引用数据类型则存储在堆中,引用数据类型的存储地址则保存在栈中。

下面来看一个小例子
// 基本数据类型
let intType = 1;
console.log('初始intType:' + intType);
let copyIntType = intType;
copyIntType = 2;
console.log('更改后intType:' + intType);
console.log('更改后copyIntType:' + intType);
let object = {
a: 1
};
// 引用数据类型
let copyObject = object
console.log('初始object:');
console.log(object);
copyObject.a = 2;
console.log('更改后的object:');
console.log(object);
console.log('更改后的copyObject:');
console.log(copyObject);
结果:

基本数据类型在复制的时候会创建一个值的副本,并将该副本赋值给新变量。引用类型在复制的时候其实复制的是指针。

深浅拷贝
- 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
- 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。实现方式如下
实现
- 遍历赋值实现
- ES6扩展运算符
- ES6方法Object.assign()
- 数组方法(只适用于类数组对象)
Array.from(arrayLike)
Array.prototype.concat()
Array.prototype.slice()
遍历赋值实现
var obj = { a:1, arr: [2,3] };
//浅拷贝实现
for (var prop in obj){
if(obj.hasOwnProperty(prop)){
shallowObj[prop] = obj[prop];
}
}
ES6扩展运算符
var obj = { a:1, arr: [2,3] };
var obj1 = {...obj}
ES6方法Object.assign()
var obj = { a:1, arr: [2,3] };
var obj1 = Object.assign({}, obj);
数组方法(仅适用于类数组对象)
Array.from(arrayLike)
var array1 = ['a', ['b', 'c'], 'd'];
var array2 = Array.from(array1);
Array.prototype.concat()
var array1 = ['a', ['b', 'c'], 'd'];
var array2 = array1.concat([1,2]);
Array.prototype.slice()
var array1 = ['a', ['b', 'c'], 'd'];
var array2 = array1.slice(0,2);
引用赋值:地址的赋值,将对象指针赋值给一个变量,让此变量指向对象
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
实现方式如下:
- JSON.parse()和JSON.stringify()
- 递归
JSON.parse()和JSON.stringify()
let parseObject = {
a: {
b: 1
}
}
let cloneParseObject = JSON.parse(JSON.stringify(parseObject));
parseObject.a.b = 2;
console.log('cloneParseObject');
console.log(cloneParseObject);

缺陷:
- 会忽略 undefined
- 会忽略 symbol
- 无法对function进行处理 需要确认.
- 不能解决循环引用的对象
递归(简单版)
// 深拷贝
function cloneDeep(obj) {
if (!obj && typeof obj !== 'object') {
throw new Error('错误参数');
}
const targetObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
//只对对象自有属性进行拷贝
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
targetObj[key] = cloneDeep(obj[key]);
} else {
targetObj[key] = obj[key];
}
}
}
return targetObj;
}
关键点
- 判断参数类型
- 判断是否是数组
- for in遍历
- 判断是否是自有对象
- 判断子属性是否是对象,是对象则递归
递归(复杂版)
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]'; const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]'; const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]; function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
} function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
} function getType(target) {
return Object.prototype.toString.call(target);
} function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
} function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
} function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
} function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
} function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
function clone(target, map = new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
module.exports = {
clone
};
示例代码
参考
JS深浅拷贝及其实现的更多相关文章
- js 深浅拷贝 笔记总结
一.js 数据类型 javaScritp的数据类型有:数值类型.字符串类型.布尔类型.null.undefined.对象(数组.正则表达式.日期.函数),大致分成两种:基本数据类型和引用数据类型, 其 ...
- jQuery开发插件的两个方法 js 深浅拷贝
1.jQuery.extend(object);为扩展jQuery类本身.为类添加新的方法.由全局函数来调用, 主要是用来拓展个全局函数 2.jQuery.fn.extend(object);为jQu ...
- js深浅拷贝
作为一枚前段,我们知道对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况.通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个情况. 浅拷贝 首先可以通过O ...
- 最简js深浅拷贝说明
1.浅拷贝 浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响. 浅拷贝分两种情况: 1.直接拷贝源对象的引用 2. 源对象拷贝实例,但其属性对象(类型为Object, ...
- JS 深浅拷贝
首先理解概念 浅拷贝: 只复制对象的基本类型, 对象类型, 仍属于原来的引用. 深拷贝: 不紧复制对象的基本类, 同时也复制原对象中的对象.就是说完全是新对象产生的. 首先看浅拷贝 //浅拷贝 var ...
- JS中深浅拷贝 函数封装代码
一.了解 基本数据类型保存在栈内存中,按值访问,引用数据类型保存在堆内存中,按址访问. 二.浅拷贝 浅拷贝只是复制了指向某个对象的指针,而不是复制对象本身,新旧对象其实是同一内存地址的数据,修改其中一 ...
- js 基础数据类型和引用类型 ,深浅拷贝问题,以及内存分配问题
js 深浅拷贝问题 浅拷贝一般指的是基本类型的复制 深拷贝一般指引用类型的拷贝,把引用类型的值也拷贝出来 举例 h5的sessionStorage只能存放字符串,所以要存储json时就要把json使用 ...
- JS--变量及深浅拷贝
JS变量分为基本类型和引用类型 基本类型数据包括Number, String, Boolean, Null, Undefined五种类型: 引用数据类型包括Array, Date, RegExp, F ...
- 【 js 基础 】 深浅拷贝
underscore的源码中,有很多地方用到了 Array.prototype.slice() 方法,但是并没有传参,实际上只是为了返回数组的副本,例如 underscore 中 clone 的方法: ...
随机推荐
- CentOS部署RabbitMQ
CentOS版本:CentOS-7-x86_64-DVD-1804 RabbitMQ版本:3.7.24 1. 下载安装包 因为RabbitMQ是erlang语言开发的,所以需要提前安装erlang环境 ...
- Gradient Centralization: 简单的梯度中心化,一行代码加速训练并提升泛化能力 | ECCV 2020 Oral
梯度中心化GC对权值梯度进行零均值化,能够使得网络的训练更加稳定,并且能提高网络的泛化能力,算法思路简单,论文的理论分析十分充分,能够很好地解释GC的作用原理 来源:晓飞的算法工程笔记 公众号 论 ...
- Linux命令持续学习
1 基础命令 1 jps 查看后台运行的java相关的程序 jvm调优所有 2 ps -ef | grep java 查询后台运行的程序(通过关键字) 3 cd - 回到上一级目录 4 vim之后输 ...
- JS 下拉菜单案例
css代码 .nav { width: 300px; height: 400px; list-style: none; padding:; margin: 0 auto; } .nav>li { ...
- 重写ThreadPoolTaskExecutor
目录 主类开启异步注解 创建线程池配置类 创建线程池实现类 创建一个测试类Controller 创建异步Service方法 定义异步的实现类 ThreadPoolExecutor:JDK内置线程池实现 ...
- 使用cors完成跨域请求处理
跨域的含义 同源策略以及其限制内容 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS.CSFR等攻击.所谓同源是指"协议+域名+端口&quo ...
- java 基本类型包装类
一 基本类型包装类 1.包装类概述 Java中提供了相应的对象来解决实现字符串与基本数据之间转换问题,基本数据类 型对象包装类:java将基本数据类型值封装成了对象. 8种基本类型对应的包装类如下: ...
- 谈谈代码评审(code review)
什么是代码评审(code review)? 根据维基百科的定义,代码评审是一种通过若干人员检阅源代码方式来进行的软件质量保证活动.根据软件工程的经典理论,代码评审应该是收益很高的活动,因其产生在Cod ...
- 洛谷P1308.统计单词数(字符串匹配)
题目描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给 ...
- SSRF 跨站请求伪造学习笔记
参考文章: 了解SSRF,这一篇就足够了 SSRF 学习之路 SSRF绕过方法总结 Weblogic SSRF漏洞 What-是什么 SSRF(Server-Side Request Forgery) ...