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 的方法: ...
随机推荐
- XSSFWorkbook
支持2007以后的 此类与HSSFWorkbook(支持2007之前) 类似,读取文件时把全部的内容都存放到内存中,关闭输入流后. 内存与硬盘完全是毫无关系的两份数据,所有的操作都是对内存的操作,最后 ...
- 【CSP2019】括号树 题解(递推+链表)
前言:抽时间做了做这道题,把学长送退役的题. ----------------- 题目链接 题目大意:定义$()$是合法括号串.如果$A,B$是合法括号串,那么$(AB),AB$为合法括号串.现给定根 ...
- C++类、函数、指针
1.初始化所有指针. 2. (1)指向常量的指针: (2)常量指针:指针本身为常量: 3.若循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环. 4.字符数组要注意字符串字面值 ...
- JVM系列之:JIT中的Virtual Call接口
目录 简介 最常用的接口List 多个List的调用 不一样的List调用 总结 简介 上一篇文章我们讲解了Virtual Call的定义并举例分析了Virtual Call在父类和子类中的优化. J ...
- ios 生成字母加数字的随机数
文章来自:http://blog.csdn.net/baidu_25743639/article/details/73801700 近期项目第三方登录之后默认创建账号和密码,就用随机数生产,这里只需要 ...
- 为什么overflow:hidden能达到清除浮动的目的?
1. 什么是浮动 <精通CSS>(第3版)关于浮动的描述: 浮动盒子可以向左或向右移动,直到其外边沿接触包含块的外边沿,或接触另一个浮动盒子的外边沿. 浮动盒子也会脱离常规文档流,因此常规 ...
- cudnn加速计算
cudnn加速运算 torch.backends.cudnn.enabled = True torch.backends.cudnn.benchmark = True 第一句话是说,使用的是非确定性算 ...
- C#LeetCode刷题之#443-压缩字符串(String Compression)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3943 访问. 给定一组字符,使用原地算法将其压缩. 压缩后的长度 ...
- Web前端性能优化,应该怎么做?
摘要:本文将分享一些前端性能优化的常用手段,包括减少请求次数.减小资源大小.各种缓存.预处理和长连接机制,以及代码方面的性能优化等方面. base64:尤其是在移动端,小图标可以base64(webp ...
- jQuery的基础效果题
Jquery第二次考核 之真金不怕火炼 1. 名词解释 实例对象:var p1=new Person(); p1就是实例对象 构造:function Person(){} 原型对象:在 JavaS ...