Step-By-Step
Step-By-Step copied to clipboard
深拷贝和浅拷贝的区别是什么?如何实现一个深拷贝?
深拷贝=>拷贝所有的属性,并且地址也与原来的不同,这样的话,你改变当前的属性也不会影响原来的 浅拷贝=>就是直接赋值的这种,地址相同,当你改变现在的值,原来的值也会跟着改变
深拷贝的实现: a:JSON.parse(JSON.stringify(obj)) => 这种方式只能对属性不是function的有用 b:Object.assgin({},target) => 这种方式第一层是深拷贝,第二层是浅拷贝 c:jquery的JQ.extend d:采用递归来进行拷贝 e:采用扩展运算符var a = {...obj};
浅拷贝与深拷贝
- 浅拷贝赋值引用地址,修改值会影响原始值(若属性的值未原始类型可能不会影响,依拷贝方法而定);
- 深拷贝引用类型会重新创建一个对象,不会影响原始值;
浅拷贝
- 直接赋值;
- 任何操作都会影响原数组;
let obj2 = obj1;
- Object.assign;
- 拷贝属性值,假如属性值是一个对象的引用,那么也会指向那个引用;
let obj2 = Object.assign({},obj1);
- Array.prototype.concat();
- 合并多个数组;
- 拷贝规则同Object.assign;
let arr2 = arr1.concat(...arr);
- Array.prototype.slice();
- 提取数组;
- 参数(可选):startIndex,endIndex(与索引值一致);
- 拷贝规则同Object.assign;
let arr2 = arr1.slice(start,end);
- 扩展运算符(...);
- 拷贝规则同Object.assign;
let obj2 = {obj1} or [...obj1];
- lodash(_.clone());
- 拷贝规则同Object.assign;
let obj2 = _.clone(obj1);
深拷贝
- JSON.parse(JSON.stringify());
- 不能拷贝函数;
let obj2=JSON.parse(JSON.stringify(obj1));
- 递归赋值;
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; }
- lodash(_.cloneDeep());
let obj2 = _.cloneDeep(obj1);
深拷贝就是完全的复制一个对象出来,而且不会影响到原来的对象;浅拷贝只是复制了原对象的引用地址,如果修改了浅拷贝的对象,原对象也会跟着改变...萌新理解就这么多 实现的话当然是最简单的JSON.parse( JSON.stringIfy(obj) ),工作中应该是够用了,递归又要百度,就这个好记一点,但是会有一点this的问题,看别人博客说的
如何区分深拷贝与浅拷贝:当b复制了a以后,修改a,观察b是否会发生变化
深拷贝:修改a以后,b不会发生变化
浅拷贝:修改a以后,b也会跟着变化
要实现深拷贝实际上就是实现一个方法使得改变a的时候不会影响到b的值,可以使用JSON.stringfy和JSON.parse来实现
浅拷贝与深拷贝主要是针对保存在堆内存里的复杂数据类型所给出的名词。 浅拷贝指的是将复杂数据类型在栈中保存的地址复制一份,所指向的数据是同一份, 深拷贝是指将复杂数据类型完整的复制一份,日常工作中JSON转换应该可以满足需求,或者用for in 遍历复制,注意,function,regexp,Date不可复制,如有错误,欢迎指正。
深拷贝:拷贝对象的属性并重新创建一个对象,不会影响原始值。可以通过JSON.stringfty()和JSON.parse()来实现 浅拷贝: 直接赋值,作用域相同,改变值会改变原先的值
浅拷贝:复制的是其引用的地址,当原始值改变时,浅拷贝的值也进行相应变化 深拷贝:复制的是值,当原始值改变时,深拷贝的值不会变化 JSON.parse(JSON.stringify(obj))
浅拷贝:原始类型为值传递;对象类型仍为引用传递只复制了对象的引用地址,修改其中任一的值,另一个值会随之变化。 深拷贝:将对象及值复制过来,两个对象修改其中任意一个的值,另一个不会随之改变。 JSON.parse(JSON.stringfy())即可实现简单的深拷贝,此方法不能复制函数类型。
所谓深浅拷贝,都是进行复制,那么区别主要在于复制出来的新对象和原来的对象是否会互相影响,改一个,另一个也会变。 浅拷贝: 对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝。
深拷贝:而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝。
深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例。
深拷贝
| 栈内存 | | 堆内存 |
|------------- | |------------- |
| 变量 | 值 | | 值 |
| ------------- | | ------------- |
| a | 内存1 | | {name: 'tian'} |
| b | 内存2 | | {name: 'tian'} |
深拷贝和浅拷贝的区别
深拷贝和浅拷贝主要针对对象和数组来说的。 浅拷贝,当复制了一个对象后,一个对象修改,会影响另一个对象。因为拷贝的是对象的引用地址。指向的还是同一片空间。 深拷贝,当复制了一个对象后,一个对象修改后,不会影响另一个对象。因为拷贝之后是一个新的对象,拷贝的是原对象的值。
深拷贝实现
1、JSON.strigify 和 JSON.parse
function deepClone(obj) {
let _obj = JSON.strigify(obj);
let newObj = JSON.parse(_obj);
return newObj;
}
2、递归
function deepClone(obj) {
let newObj = Array.isArray(obj) ? [] : {};
let key;
if(typeof obj !== 'object') {
return obj;
} else {
for(key in obj) {
if(obj.hasOwnProperty(key)) {
if(obj[key] && typeof obj[key] === 'object') {
newObj[key] = deepClone(obj[key])
} else {
newObj[key] = obj[key]
}
}
}
}
return newObj;
}
浅拷贝只拷贝了引用类型的地址,修改被拷贝对象或者拷贝对象的属性值,另外一方也会随之改变; 深拷贝拷贝了引用类型完整的属性值,不会产生影响; 深拷贝可以通过JSON互相转换来实现(该方法不适用于function、reg类型)、循环递归;
知识准备: JavaScript有五种基本数据类型,也就是简单数据类型,它们分别是Undefined,Null,Boolean,Number还有String。 Undefined就是已经声明但是没有赋值的变量。 Null其实就是不存在对象。 还有一种复杂的数据类型叫引用类型,就是对象。 1.基本数据类型 这种类型的值在内存中占据固定大小的空间,保存在栈内存中。
var x=1;
var y=x;
console.log(y);
y=2;
console.log(x);
console.log(y);
1 1 2
y是x的一个副本,他们占有不同的位置,只是值相等,改变其中一方的值,另一方不会改变。
2.引用类型
复杂数据类型也就是引用类型,它的值是对象的地址,这个地址保存在栈内存中,但是地址指向对象各属性值存在堆内存中,这个地址就指向这个堆内存
var obj = {
name:'Hanna Ding',
age: 22
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj); //Object {name: "Hanna Ding", age: 22, c: 5}
console.log(obj2); //Object {name: "Hanna Ding", age: 0, c: 5}
改变其中一个对象的属性值,两个对象的属性值都变了,因为obj和obj1都指向同一个地址引用。
浅拷贝 简单的赋值给一个变量,但是这个变量的值发生改变,另一个变量也发生改变。
var arr = [1, 2, 3, '4'];
var arr2 = arr;
arr2[1] = "test";
console.log(arr); // [1, "test", 3, "4"]
console.log(arr2); // [1, "test", 3, "4"]
深拷贝 数组提供了slice()和concat()方法
var arr=['a','b','c'];
var arr1=arr.slice(0);
arr1[0]='d';
console.log(arr);//a b c
console.log(arr1);//d b c
slice返回一个数组的浅拷贝,并且生成一个新的数组,改变新数组不会影响原数组。
var arr = ['a', 'b', 'c'];
var arr1 = arr.concat();
arr1[0] = 'test';
console.log(arr); // ["a", "b", "c"]
console.log(arr1); // ["test", "b", "c"]
concat()可以用来合并数组,并生成一个新的数组。
function deepCopy(arr1,arr2){ for(var i=0;i<arr1.length;++i){ arr2[i]=arr1[i] } }
对象 对象的深拷贝原理,定义一个新的对象,遍历源对象的属性,并赋值给新对象的属性。
var obj={
name:'Jodie',
age:18
}
var obj1=new Object();
obj1.name=obj.name;
obj1.age=obj.age;
obj.name='Jack';
console.log(obj); //{name:'Jodie',age:18}
console.log(obj1); //{name:'Jack',age:18}
封装一个deepCopy来实现对象的深拷贝
var obj={
name:'Jodie',
job:{
title:'pm',
level:'junior'
}
}
var deepCopy=function(source){
var result=new Object();
for(var item in source){
if (typeof source[item]==='object'){
result[item]=deepCopy(source[item])
} else{
result[item]=source[item]
}
}
return result;
}
var objCopy=deepCopy(obj);
obj.job.title='engineer';
console.log(obj); //{name:"Jodie",job:{level:"junior",title:"engineer"}}
console.log(objCopy);//{name:"Jodie",job:{level:"junior",title:"pm"}}
参考原址: https://www.cnblogs.com/dinghuihua/p/6674719.html
首先我们讨论一下深/浅拷贝出现的背景
- 对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。
- 而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈内存存对象的引用指针,一个在堆内存存放对象。这个时候会有一个问题,你拷贝的只是这个引用指针还是拷贝两块内存一起拷贝,这个时候就会有深浅拷贝一说。
综上:
- 浅拷贝:如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象
- 深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。
实现方式
- 浅拷贝:
Object.assign( {}, originObject )
、解构{ ...originObject }
- 深拷贝
- 第三方库jquery、underscore
- 递归实现
- JSON.parse(JSON.stringify(originObject ))
JSON.parse(JSON.stringify(originObject ))的弊端
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)
- 不可枚举的属性会被忽略
- 如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如
var obj = { foo: 'foo', toJSON: function () { return 'bar'; } }; JSON.stringify(obj); // '"bar"' JSON.stringify({x: obj}); // '{"x":"bar"}'
描述(来源,对比)
浅拷贝,深拷贝用于复杂数据类型(也就是引用类型,数组,对象等),两者都是为了防止直接赋值带来的弊端(也就是修改一个的值,另外一个也会随之变化),二两者的区别在于,浅拷贝只解决了对象的第一层问题,深度拷贝的话,无论多少层,修改一个,另外一个都不会有影响,例如代码所示
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = {...a}
let c = JSON.parse(JSON.stringify(a))
a.age = 2
console.log(b.age) // 1
a.jobs.first = 'native'
console.log(b.jobs.first) // native
console.log(c.jobs.first) // FE
浅拷贝的实现方法
- ... 扩展运算符
- Object.assign()
- 等等
深度拷贝的实现
- 一般使用JSON.stringify + JSON.parse --局限就是会忽视undefined,symbol,函数,对象间互相引用的情况
- 可以借助Jquery,lodash这些第三方js库
- 递归遍历实现
- 如果确定数据是数组类型的话,还可以使用数组的方法,slice,concat这些
- 使用MessageChannel 信道通信api (逼格高,兼容性还不错)
function structuralClone(obj) {
return new Promise(resolve => {
const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
}
var obj = {a: 1, b: {
c: b
}}
// 可以处理 undefined 和互相引用对象
(async () => {
const clone = await structuralClone(obj)
})()
@YvetteLau
群主你怎么看
本文基本参考 木易杨的Git上的理解,拜读过2遍
深拷贝与浅拷贝
我觉得深拷贝与浅拷贝的区分离不开赋值方式的理解
赋值的方式有两种
一种是基本数据赋值,如果修改a,或者b的值,相互不影响。类似于:
let b = 1
let a = b
a = 2
b = 3
console.log(a) // a=2
console.log(b) // b=3
一种是引用数据赋址,如果修改a或者b里面的值,两个会一起变。类似于
let b = {
name: 'test'
}
let a = b
b.name = 'test1'
a.name = 'test2'
console.log(a) // a={name: "test2"}
console.log(b) // b={name: "test2"}
使用赋值的方式来区分深拷贝,浅拷贝
浅拷贝
浅拷贝创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是饮用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。 可以理解浅拷贝只拷贝了基本类型值,和引用类型的地址。
我们使用浅拷贝的情况:
- Object.assign()
- Array.prototype.slice()
- 展开语法 Spread
深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所饮用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢且花销较大。拷贝前后两个对象互不影响。
使用深拷贝的场景 JSON.parse(JSON.stringify(object)) 但是该方法有以下几个问题:
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环饮用的对象
- 不能正确的处理 new Date()
- 不能处理正则
1,浅拷贝和深拷贝
何为拷贝:简易理解为重新复制一份数据。 浅拷贝和深拷贝主要针对引用类型数据而言。基本类型数据都是拷贝的栈内存中的值,改变拷贝后的值对原值不会产生影响。
浅拷贝: 产生新对象,如果被拷贝对象的属性中有引用类型的值,则拷贝的是数据在堆内存中的地址值,通过拷贝后得到的变量修改数据,源对象中的数据发生改变。 即浅拷贝只复制对象的第一层属性。
var obj2 = {
name: '小明',
age: 18,
eat: [1,[2,3],[4,5,6]]
}
var obj3 = clone(obj2)
obj3.name = '小明1'
obj3.eat[1] = [1,1]
console.log('obj2',obj2) //obj2 { name: '小明', age: 18, eat: [ 1, [ 1, 1 ], [ 4, 5, 6 ] ] }
console.log('obj3',obj3) //obj3 { name: '小明1', age: 18, eat: [ 1, [ 1, 1 ], [ 4, 5, 6 ] ] }
function clone(src){
const cloneObj = src instanceof Array ? [] : {}
for(var prop in src){
if(src.hasOwnProperty(prop)){
cloneObj[prop] = src[prop]
}
}
return cloneObj
}
深拷贝: 产生新对象, 如果被拷贝对象的属性中有引用类型的值,拷贝得是数据在堆内存中的值,通过拷贝后得到的变量修改数据,源对象中的数据不发生改变。 即深拷贝可以对对象的属性进行递归复制;
//拷贝对象
var obj4 = {
name: '小明',
age: 18,
eat: [1,[2,3],[4,5,6]]
}
var obj5 = deepClone(obj2)
obj5.name = '小明1'
obj5.eat[1] = [1,1]
console.log('obj4',obj4) //obj4 { name: '小明', age: 18, eat: [ 1, [ 2, 3 ], [ 4, 5, 6 ] ] }
console.log('obj5',obj5) //obj5 { name: '小明1', age: 18, eat: [ 1, [ 1, 1 ], [ 4, 5, 6 ] ] }
//拷贝数组
let arr = [1,2,3,4,[5,6]]
let arr1 = deepClone(arr)
arr1[4] = [1,1]
console.log('arr',arr) //arr [ 1, 2, 3, 4, [ 5, 6 ] ]
console.log('arr1',arr1) //arr1 [ 1, 2, 3, 4, [ 1, 1 ] ]
function deepClone(src){
const cloneObj = src instanceof Array ? [] : {}
for(var prop in src){
if(src.hasOwnProperty(prop)){
if(typeof prop == Object || typeof prop == Array){
cloneObj[prop] = deepClone(prop) //关键的一步。如果拷贝时,数据为引用类型,则对属性再进行一次拷贝取值
}else{
cloneObj[prop] = src[prop]
}
}
}
return cloneObj
}
本质区别:修改拷贝后的对象的值,是否影响源对象。
2,浅拷贝的实现方式
1,Object.assign()
const h = {
age: 18,
eat: {
food: 'apple'
}
}
const i = Object.assign({},h)
i.eat.food = 'noodle'
console.log(h) //{ age: 18, eat: { food: 'noodle' } }
console.log(i) //{ age: 18, eat: { food: 'noodle' } }
Array.prototype.concat()
const arr2 = [1,2,3,{name: '小明'},[2,2]]
const arr3 = arr2.concat()
arr3[3].name = '小虹'
arr3[4] = [1,1]
console.log(arr2) //[ 1, 2, 3, { name: '小虹' },[2,2] ]
console.log(arr3) //[ 1, 2, 3, { name: '小虹' },[1,1] ]
3,深拷贝的实现方式
JSON.parse(JSON.stringify())
const arr4 = [1,2,3,{name: '小明'}]
const arr5 = JSON.parse(JSON.stringify(arr4))
arr5[3].name = 'json'
console.log(arr4) //[ 1, 2, 3, { name: '小明' } ]
console.log(arr5) //[ 1, 2, 3, { name: 'json' } ]
封装递归的方法(见上)
函数库lodash
var _ = require('lodash')
const arr6 = [1,2,3,{name: '小明'}]
const arr7 = _.cloneDeep(arr6)
arr7[3].name = 'lodash'
console.log(arr6)
console.log(arr7)
浅拷贝 和 深拷贝
浅拷贝
只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,一般对象类型为引用类型
eg:
var a = {
name: 12,
age: 13
}
var b = a // a可为Object, arr等引用类型
类似于此
深拷贝
所有元素或属性均完全复制,与原对象完全脱离,也就是说所有对于新对象的修改都不会反映到原对象中, 是在内存中另辟一个新的地址。
eg.
function deepClone(obj) {
const newObj = obj instanceof Array? [] : {}
if ( typeof obj !== 'object') {
return obj
} else {
for(var i in obj) {
newobj[i] = typeof obj[i] === 'objcet' deepClone(obj[i]) : obj[i]
}
}
return newobj
}
arr.concat():数组的浅拷贝
arr.slice(): 数组的浅拷贝
JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据
注意点
- Object.assign 并不属于深拷贝
var obj1 = {
name: 'hello',
value: {
age: 12
}
}
var obj2 = Object.assign({}, obj1)
obj2.name = 'world'
obj1.name // 这边的值不会改变 还是为hello
obj2.value.age= 13
obj1.vakue.age // 13 这边为13
obj2.value = 12
obj1.value.age // 13
经查,Object.assign 只是对第一级属性进行深拷贝,以后每级都是浅拷贝
浅拷贝
浅拷贝的意思就是只复制引用,而未复制真正的值。
深拷贝
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
实现深拷贝
- 利用JSON.parse 和 JSON.stringify 方法
// 1.实现简单的深拷贝
const hanzo = [1,2,3,4];
const genji = JSON.parse(JSON.stringify(hanzo));
console.log(hanzo === genji); // false
hanzo.push(5);
genji.splice(0,1);
console.log(hanzo); // [1,2,3,4,5]
console.log(genji); // [2,3,4]
// 缺陷:只能实现一些简单的深拷贝。但是下面这种情况就不适合了。
// 2.
const obj = {
name: 'hanzo',
age: 38,
sayHello: function() {
console.log('半藏')
}
};
console.log(obj); // {name: "hanzo", age: 38, sayHello: ƒ}
const obj6 = JSON.parse(JSON.stringify(obj));
console.log(obj6); // {name: "hanzo", age: 38}
// sayHello 这个funciton 并没有没拷贝下来
// 原因:undefined、function、symbol 会在转换过程中被忽略,
// 所以如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。
2. 递归
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,
function deepCopy(source) {
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source) {
if(source[keys] && typeof source[keys] === 'object') { // 如果值是对象,就递归一下
targetObj[keys] = source.constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys];
}
}
return targetObj
}
const hanzo = {
name: 'hanzo',
age: 38,
sayHello: function() {
console.log('我是半藏')
}
}
const genji = deepCopy(hanzo);
genji.name = 'genji';
genji.age = 35;
genji.sayHello = function() {
console.log('我是源氏')
};
console.log(hanzo); // {name: "hanzo", age: 38, sayHello: ƒ}
console.log(genji); // {name: "genji", age: 35, sayHello: ƒ}
console.log(hanzo === genji); // false
hanzo.sayHello(); // 我是半藏
genji.sayHello(); // 我是源氏
ES6的Object.assign() 和 ...展开运算符,但它们只能拷贝首层;
数组中的 concat 和 slice 也可以实现,但它们也是拷贝首层
浅拷贝和深拷贝主要是用来区别拷贝引用类型的值的情况,浅拷贝直接拷贝引用类型的值的引用地址。深拷贝是拷贝引用类型的值对应的在堆内存中存储的值。
Object.assign(source, target), 以及对象展开操作符{...source}实现的是浅拷贝,JSON.parse(JSON.stringify(source))可以实现浅拷贝,但是对象必须是json安全的,比如无法识别undefined, 函数等
对象深拷贝的递归实现
function deepClone(obj) {
if (obj === null) return obj;
if (typeof obj !== 'object') return obj;
let copy = new obj.constructor();
for (let key in obj) {
copy[key] = deepClone(obj[key]);
}
return obj;
}
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
实现一个深拷贝
function clone(obj) {
var copy;
switch (typeof obj) {
case "undefined":
break;
case "number":
copy = obj - 0;
break;
case "string":
copy = obj + "";
break;
case "boolean":
copy = obj;
break;
case "object": //object分为两种情况 对象(Object)和数组(Array)
if (obj === null) {
copy = null;
} else {
if (object.prototype.toString.call(obj).slice(8, -1) === "Array") {
copy = [];
for (var i = 0; i < obj.length; i++) {
copy.push(clone(obj[i]));
}
} else {
copy = {};
for (var j in obj) {
copy[j] = clone(obj[j]);
}
}
}
break;
default:
copy = obj;
break;
}
return copy;
}
// 这段代码是去年刚入门的时候撸的 满满的槽点 哈哈哈哈
下面是浪里行舟大佬文章里的递归思路的函数,相较上面的那段考虑的更多更细致,感谢大佬的分享输出。
// 定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
// 实现深度克隆---对象/数组
function clone(target) {
// 判断拷贝的数据类型
// 初始化变量result 成为最终克隆的数据
let result, targetType = checkedType(target)
if (targetType === 'Object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
// 遍历目标数据
for (let i in target) {
// 获取遍历数据结构的每一项值。
let value = target[i]
// 判断目标结构里的每一值是否存在对象/数组
if (checkedType(value) === 'Object' ||
checkedType(value) === 'Array') { // 对象/数组里嵌套了对象/数组
// 继续遍历获取到value值
result[i] = clone(value)
} else { // 获取到value值是基本的数据类型或者是函数。
result[i] = value;
}
}
return result
}
参考文章: 浅拷贝与深拷贝 - 掘金
1、 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝 2、深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝
a、基本数据类型 let a = 1; //此时会开辟出来一个栈内存 b = a; //当b=a时,会在栈内存中开辟出一个新内存,其中包含了a,b这两个对应的name名和value值; //此时修改任一个value值对另一个是没有影响的,此时不算的上是深拷贝,深拷贝本身只是对复杂的object类型
b、复杂数据类型 //同上开辟出来的栈内存中对应的value值是一个地址,通过这个地址指向开辟出来的堆内存中的值, //浅拷贝的话只是对在栈内存中的value值,也就是这个存在栈内存中的地址进行了拷贝,它开辟出来的堆内存中的值是没有影响的; //如果我们同上操作把对应的这个堆内存中的值也给开辟出一个新内存,不就可以达到深拷贝了,也就是对应的value值的地址拷贝了,对应的堆内存中的两个内存值也进行了拷贝。
//实现深拷贝的方法
function deepClone(obj){
let _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone
}
let a=[0,1,[2,3],4],
b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
深拷贝和浅拷贝是针对复杂数据类型来说的。
深拷贝
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
浅拷贝
浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
可以使用 for in
、 Object.assign
、 扩展运算符 ...
、Array.prototype.slice()
、Array.prototype.concat()
等,如下:
let obj = {
name: 'Yvette',
age: 18,
hobbies: ['reading', 'photography']
}
let obj2 = Object.assign({}, obj);
let obj3 = {...obj};
obj.name = 'Jack';
obj.hobbies.push('coding');
console.log(obj);//{ name: 'Jack', age: 18,hobbies: [ 'reading', 'photography', 'coding' ] }
console.log(obj2);//{ name: 'Yvette', age: 18,hobbies: [ 'reading', 'photography', 'coding' ] }
console.log(obj3);//{ name: 'Yvette', age: 18,hobbies: [ 'reading', 'photography', 'coding' ] }
let arry = ['name', 'age', { info: 'female' }];
let arry2 = arry.slice(0);
let arry3 = arry.concat([]);
let arry4 = [...arry];
arry2[2].hi = 'hi';
console.log(arry2);//[ 'name', 'age', { info: 'female', hi: 'hi' } ]
console.log(arry3)//[ 'name', 'age', { info: 'female', hi: 'hi' } ]
console.log(arry4);//[ 'name', 'age', { info: 'female', hi: 'hi' } ]
可以看出浅拷贝只最第一层属性进行了拷贝,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存地址。来看一下使用 for in
实现浅拷贝。
let obj = {
name: 'Yvette',
age: 18,
hobbies: ['reading', 'photography']
}
let newObj = {};
for(let key in obj){
newObj[key] = obj[key];
//这一步不需要多说吧,复杂数据类型栈中存的是对应的地址,因此赋值操作,相当于两个属性值指向同一个内存空间
}
console.log(newObj);
//{ name: 'Yvette', age: 18, hobbies: [ 'reading', 'photography' ] }
obj.age = 20;
obj.hobbies.pop();
console.log(newObj);
//{ name: 'Yvette', age: 18, hobbies: [ 'reading' ] }
深拷贝实现
1.深拷贝最简单的实现是:
JSON.parse(JSON.stringify(obj))
let obj = {
name: 'Yvette',
age: 18,
hobbies: ['reading', 'photography']
}
let newObj = JSON.parse(JSON.stringify(obj));//newObj和obj互不影响
obj.hobbies.push('coding');
console.log(newObj);//{ name: 'Yvette', age: 18, hobbies: [ 'reading', 'photography' ] }
JSON.parse(JSON.stringify(obj))
是最简单的实现方式,但是有一点缺陷:
1.对象的属性值是函数时,无法拷贝。
let obj = {
name: 'Yvette',
age: 18,
hobbies: ['reading', 'photography'],
sayHi: function() {
console.log(sayHi);
}
}
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);//{ name: 'Yvette', age: 18, hobbies: [ 'reading', 'photography' ] }
2.原型链上的属性无法获取
function Super() {
}
Super.prototype.location = 'NanJing';
function Child(name, age, hobbies) {
this.name = name;
this.age = age;
}
Child.prototype = new Super();
let obj = new Child('Yvette', 18);
console.log(obj.location); //NanJing
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);//{ name: 'Yvette', age: 18}
console.log(newObj.location);//undefined;原型链上的属性无法获取
2.实现一个 deepClone 函数
1.如果是基本数据类型,直接返回
3.如果是 RegExp
或者 Date
类型,返回
4.如果是复杂数据类型,递归。
function deepClone(obj) { //递归拷贝
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(obj === null || typeof obj !== 'object') {
//如果不是复杂数据类型,直接返回
return obj;
}
/**
* 如果obj是数组,那么 obj.constructor 是 [Function: Array]
* 如果obj是对象,那么 obj.constructor 是 [Function: Object]
*/
let t = new obj.constructor();
for(let key in obj) {
//如果 obj[key] 是复杂数据类型,递归
if(obj.hasOwnProperty(key)){//是否是自身的属性
t[key] = deepClone(obj[key]);
}
}
return t;
}
测试:
function Super() {
}
Super.prototype.location = 'NanJing';
function Child(name, age, hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
Child.prototype = new Super();
let obj = new Parent('Yvette', 18, ['reading', 'photography']);
obj.sayHi = function () {
console.log('hi');
}
console.log(obj.location); //NanJing
let newObj = deepClone(obj);
console.log(newObj);//
console.log(newObj.location);//NanJing 可以获取到原型链上的属性
newObj.sayHi();//hi 函数属性拷贝正常
- 区别
- 深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,
- 浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象
- 实现
深拷贝和浅拷贝的区别
深拷贝拷贝的是目标所有 浅拷贝拷贝的是值 深拷贝拷贝前后两个值不会相互影响 浅拷贝 拷贝时
因为指向同一个指针 一旦该指针对应的值发生变化 那么统一指针的值一会发生变化
常见的浅拷贝的应用 array.slice array.concat array.from es6的扩展运算符 object.assign 当拷贝对象的属性值为对象时 也是浅拷贝 只有一层可称之为深拷贝
常见的深拷贝应用 jquery的$.extend JSON.parse(JSON.stringfy())
手写递归遍历 方法如下
function deepClone(obj){
// 判断是不是 正则 日期 是不是复杂类型 若以上条件成立 则直接返回
if (obj instanceof new RegExp) return new RegExp(obj);
if(obj instanceof new Date) return new Date(obj)
if(obj===null || typeof obj !=='object'){
return obj
}
let t =new obj.constructor();
for(let key in obj){
if(obj.hasOwnPropert(key)){
t[key] = deepClone(obj[key]) // 检测属性值是否是基本类型 若是则返回 否则继续执行此函数
}
}
return t
}
浅拷贝 概念: 对于字符串类型,浅拷贝是对值的复制,对于对象来说,浅拷贝是对对象地址的复制, 也就是拷贝的结果是两个对象指向同一个地址
深拷贝 概念: 深拷贝开辟一个新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
区别: 浅拷贝是复制,两个对象指向同一个地址 深拷贝是新开栈,两个对象指向不同的地址
深拷贝实现:
var obj = {
a:1,
arr: [1,2],
nation : '中国',
birthplaces:['北京','上海','广州']
};
var obj2 = {name:'杨'};
obj2 = deepCopy(obj,obj2);
console.log(obj2);
//深复制,要想达到深复制就需要用递归
function deepCopy(o, c){
var c = c || {};
for(var i in o){
if(typeof o[i] === 'object'){
if(o[i].constructor === Array){
//这是数组
c[i] = [];
}else{
//这是对象
c[i] = {};
}
deepCopy(o[i], c[i]);
}else{
c[i] = o[i];
}
}
return c;
}
(微信名:RUN)
浅拷贝 和 深拷贝
- 浅拷贝: 如果要拷贝复杂数据类型,只拷贝的是复杂数据类型的地址
- 浅拷贝的问题: 修改某一个对象的复杂数据类型,会导致其他的对象的数据也发生变化
- 深拷贝: 如果要拷贝复杂数据类型,不是拷贝的是复杂数据类型的地址,而是把里面的数据,一个一个的拷贝一份.
var obj1 =
name: '老王',
age: 60,
cars: ['奔驰', '宝马', '奥迪'],
houses: {
bj: '北京别墅',
sh: '上海别墅'
}
}
var obj2 = {
name: '小王',
age: 30
}
//浅拷贝
for(var k in obj1){
if(obj1[k] == undefined){
obj2[k] = obj1[k];
}
}
//深拷贝:
//递归实现
function deepCopy(son, far) {
for (var k in far) {
if (son[k] == undefined) {
if(far[k] instanceof Array){
son[k] = [];
deepCopy(son[k], far[k]);
}else if(far[k] instanceof Object){
son[k] = {};
deepCopy(son[k], far[k]);
}else{
son[k] = far[k];
}
}
}
}
deepCopy(obj1, obj2);
//修改某一个对象的复杂数据类型,不会导致其他的对象的数据也发生变化
console.log(obj2);
wsc.houses.sh = null;
console.log(obj1);
// 浅拷贝 和深拷贝 // 浅拷贝是拷贝一层 ,深层次的对象级别的就是拷贝引用 如果拷贝的是基本数据类型 那么就如同直接赋值 拷贝起本身 如果除了 基本类型外还有层是对象 那就是拷贝其引用 // 深拷贝是拷贝多层,每一层都要数据都要拷贝
// 对象的浅拷贝
function simpleClone(initObj){ var obj = {} for (var i in initObj){ obj[i] = initObj[i] } return obj } var obj = { a:"原来的值", b:{ a:"原来的值深层对象", b:"原来的值b-b" }, c:["1","2","3"], d:function(){ alert("原来的值fn") } } var CloneObj = simpleClone(obj) console.log(obj) CloneObj.a = "修改拷贝后的值-对象浅拷贝-层" // CloneObj.b.a = "修改拷贝后的值-深层对象" CloneObj.c = [4,5,6] CloneObj.d = function(){alert("修改fn")} console.log(CloneObj)
// console.log(CloneObj.a) //修改拷贝后的值-对象浅拷贝-层 // console.log(CloneObj.b) //{a: "修改拷贝后的值-深层对象", b: "原来的值b-b"} // console.log(CloneObj.c) // [4, 5, 6] // console.log(CloneObj.d) //{alert("修改fn")} // console.log("=====") // console.log(obj.a) //原来的值 ---没变 // console.log(obj.b) //{a: "修改拷贝后的值-深层对象", b: "原来的值b-b"} // console.log(obj.c) // ["1", "2", "3"] // console.log(obj.d) //ƒ (){ alert("原来的值fn")}
// es6的 Object.assign() 把多个目标拷贝到一个目标对象 拷贝的是对象的属性引用 而不是对象本身 也是只能拷贝一层 浅拷贝 var obj1 = { a:"原来的值assin拷贝", b:{ a:"原来的值", b:"b" } } var CloneObj1 = Object.assign({},obj1)
CloneObj1.a = "修改原来的值assin拷贝" CloneObj1.b.a = "修改原来的值" console.log(CloneObj1) console.log("====") console.log(obj1)
// 深拷贝 多层拷贝
// Object.assin() 如果只有一层数据 可以用上面的Object.assin()
// JSON.pares(JSON.string(obj)) 如果对象是能用json 直接表示的话 可以用这个方式实现深拷贝 比如 Number String Boolean Array ,扁平对象
// 但function 无法转换json
var obj2 = {
a:"原来的值a",
b:{
a:"原来的值b.a",
b:"b"
}
}
var cloneObj2 = JSON.parse(JSON.stringify(obj2))
console.log("====")
console.log(cloneObj2)
cloneObj2.a = "新修改的a"
cloneObj2.b.a = "新修改的b.a"
console.log("==修改前==")
console.log(obj2) //值没有发生变化 深拷贝成功
var obj3 = { fn:function(){ console.log("fn") } } var cloneObj3 = JSON.parse(JSON.stringify(obj3)) console.log(obj3.fn) //function(){ console.log("fn")} console.log(cloneObj3.fn) //undefined
// 循环引用 把自己赋值给自己的一个属性 // var a = {} // a.b = a
// 递归实现深拷贝 function deepClone(init,final){ var obj = final || {} for (var i in init){ var prop = init[i] if(prop === obj){ //init.a = init 循环引用 continue } if(typeof prop ==="object") { //判断是值类型还是引用类型 obj[i] = (prop.constructor === Array) ? [] :{} obj[i] = deepClone(prop) // 如果是引用 直接递归 }else{ obj[i] = prop } } return obj } var obj5 = {} var obj6 = { a:"原来的值a", b:{ a:"原来的值b.a", b:"原来的b" } } deepClone(obj6,obj5) console.log(obj5) obj5.a = "修改原来的值a" obj5.b.b = "修改的b" console.log(obj6)
// ***如果是对象是 日期 正则的话 就需要特使处理了
var obj6 = { a:"原来的值a", b:{ a:"原来的值b.a", b:"原来的b" } }
浅拷贝
- 浅拷贝只能拷贝第一层的元素,如果值中还有对象的话,二者享用相同地址
- 浅拷贝是会将对象的每个属性进行依次复制
- 但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
Object.assign
let a = {
name: 'rex',
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
使用展开运算符 ...
let a = {
age: 1 //对象的属性是基本类型,新对象和旧对象互不干扰
}
let b = {...a}
a.age = 18
console.log(b.age) // 1
如果对象的属性是引用类型,会相互干扰
let arry = ['name', 'age', { info: 'female' }];
let arry2 = [...arry];
arry2[2].hi = 'hi'
console.log(arry) //[ 'name', 'age', { info: 'female', hi: 'hi' } ]
for in
for in 会遍历原型继承的属性,但是不会遍历不可枚举属性
let obj = {
name: 'Yvette',
age: 18,
hobbies: ['reading', 'photography']
}
let newObj = {};
for(let key in obj){
newObj[key] = obj[key];
//这一步不需要多说吧,复杂数据类型栈中存的是对应的地址,因此赋值操作,相当于两个属性值指向同一个内存空间
}
console.log(newObj);
//{ name: 'Yvette', age: 18, hobbies: [ 'reading', 'photography' ] }
obj.age = 20;
obj.hobbies.pop();
console.log(newObj);
//{ name: 'Yvette', age: 18, hobbies: [ 'reading' ] }
Array.prototype.slice()
let arry = ['name', 'age', { info: 'female' }];
let arry2 = arry.slice(0);
Array.prototype.concat()
let arry = ['name', 'age', { info: 'female' }];
let arry2 = arry.concat([]);
深拷贝
- 深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。
- 深拷贝后的对象与原来的对象是完全隔离的,互不影响.
使用JSON.parse(JSON.stringify(obj))
let obj1 = {a: 0,b: {c: 0}};
console.log(JSON.stringify(obj1)) // {"a": 0,"b": {"c": 0}}
let obj2 = JSON.parse(JSON.stringify(obj1))
obj1.a =4;
obj1.b.c=5;
console.log(obj2) // { a: 0, b: { c: 0 } }
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环引用的对象
简单深度拷贝
function deepClone(obj) {
// 判断传进来的参数是否是一个对象
function isObject(o) {
return ((typeof o === "object" || typeof o === "function") && o !== null);
}
// 如果不是对象报错
if (!isObject(obj)) {
throw new Error("非对象");
}
// 判断是对象还是数组
let isArray = Array.isArray(obj);
// [...obj] 和 {...obj} 都是把一个对象给展开来
let newObj = isArray ? [...obj] : { ...obj };
// Reflect.ownkeys(target)是返回target对象自己的属性键的数组(包括Symbol类型)
// 相当于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Reflect.ownKeys(newObj).forEach(key => {
// newObj[key]是新数组的每一项
// 需要判断原来的旧数组的当前项是否是对象,如果是对象就递归调用,如果不是就直接赋值
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
});
return newObj; //最后返回遍历赋值后的新数组
}
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
};
let newObj = deepClone(obj);
newObj.b.c = 1;
console.log(obj.b.c); // 2
浅拷贝和深拷贝都只针对于引用数据类型。
- 浅拷贝只能拷贝第一层的元素,如果值中还有对象的话,二者享用相同地址,共享一个对象;
- 深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。不共享一个对象,两者互不影响
浅拷贝: for 循环 扩展运算符 let newObj = {...obj}; 合并对象 Object.assgin({},obj) 创建新对象 Object.creact({},obj) 数组的方法 slice() 深拷贝:
- 深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。
- 深拷贝后的对象与原来的对象是完全隔离的,互不影响.
1.深拷贝最简单的实现是: JSON.parse(JSON.stringify(obj)) 但是此方法有一定的局限性,当对象中的属性值是funtion时,是无法拷贝的 2.递归
function deepCloneX(obj) { //递归拷贝
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
// 如果是个基本数据类型 或者递归的时候如果是个 function的时候 也直接返回一个函数
if (obj === null || typeof obj !== 'object') {
return obj;
}
let newObj = new obj.constructor();
for (let key in obj) {
//如果 obj[key] 是复杂数据类型,递归
if (obj.hasOwnProperty(key)) {//是否是自身的属性
newObj[key] = deepCloneX(obj[key]);
}
}
return newObj;
}
看了几篇文章,还是感觉艳姐的递归方法,简单,完美实现各种情况,学到了,这个 new obj.constructor() 用的好巧妙, 厉害