Open YvetteLau opened 5 years ago
let obj2 = obj1;
let obj2 = Object.assign({},obj1);
let arr2 = arr1.concat(...arr);
let arr2 = arr1.slice(start,end);
let obj2 = {obj1} or [...obj1];
let obj2 = _.clone(obj1);
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;
}
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"}}
Object.assign( {}, originObject )
、解构{ ...originObject }
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
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"}
浅拷贝创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是饮用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。 可以理解浅拷贝只拷贝了基本类型值,和引用类型的地址。
我们使用浅拷贝的情况:
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所饮用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢且花销较大。拷贝前后两个对象互不影响。
使用深拷贝的场景 JSON.parse(JSON.stringify(object)) 但是该方法有以下几个问题:
何为拷贝:简易理解为重新复制一份数据。 浅拷贝和深拷贝主要针对引用类型数据而言。基本类型数据都是拷贝的栈内存中的值,改变拷贝后的值对原值不会产生影响。
浅拷贝: 产生新对象,如果被拷贝对象的属性中有引用类型的值,则拷贝的是数据在堆内存中的地址值,通过拷贝后得到的变量修改数据,源对象中的数据发生改变。 即浅拷贝只复制对象的第一层属性。
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()
```javascript
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] ]
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 只是对第一级属性进行深拷贝,以后每级都是浅拷贝
浅拷贝的意思就是只复制引用,而未复制真正的值。
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
// 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 会在转换过程中被忽略,
// 所以如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,
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(); // 我是源氏
浅拷贝和深拷贝主要是用来区别拷贝引用类型的值的情况,浅拷贝直接拷贝引用类型的值的引用地址。深拷贝是拷贝引用类型的值对应的在堆内存中存储的值。
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 函数属性拷贝正常
深拷贝和浅拷贝的区别
深拷贝拷贝的是目标所有 浅拷贝拷贝的是值 深拷贝拷贝前后两个值不会相互影响 浅拷贝 拷贝时
因为指向同一个指针 一旦该指针对应的值发生变化 那么统一指针的值一会发生变化
常见的浅拷贝的应用 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" } }
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 会遍历原型继承的属性,但是不会遍历不可枚举属性
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' ] }
let arry = ['name', 'age', { info: 'female' }];
let arry2 = arry.slice(0);
let arry = ['name', 'age', { info: 'female' }];
let arry2 = arry.concat([]);
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 } }
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() 用的好巧妙, 厉害
function
, undefined
, 'symboL',会忽略该值, 数组内部出现则会将其转化为null,
function DeepCopy(obj) {
if(typeof obj !== 'object') return;
let newObj = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if(typeof obj[key] === 'Object') {
DeepCopy(obj[key]);
}else{
newObj[key] = obj[key];
}
}
return newObj;
}
深拷贝和浅拷贝都是针对引用类型来说的。JS的类型可以分为基本类型(值类型)和引用类型,对基本类型的复制操作实际上是在栈内存重新开辟一个空间,用来存放基本类型的值,两个变量指向的是两个不同的内存空间,对引用类型的复制操作实际上也是在栈内存重新开辟一个空间,用来存放值,但是这个值是一个地址,导致两个变量最后指向的是还是同一个地址。
// 基本类型
var a = 2;
var b = a;
a = 4;
console.log(a, b); // 4,2 a,b分别指向的是栈内不同的内存空间
// 引用类型
var obj1 = {name: 'plane'};
var obj2 = obj1;
obj1.name = 'hjh';
console.log(obj1, obj2); //{name: 'hjh'},{name: 'hjh'} obj1和obj2之间的复制操作,复制的只是地址。最终地址指向的还是堆内的同一份内存空间,所以obj1的属性变了,obj2也会跟着变。
针对以上的引用类型的之间的复制问题,有时候不是我们想要的效果。
那么我们应该怎么使引用类型之间的复制不是复制地址呢?这就涉及到浅拷贝和深拷贝话题了。根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝
即只对第一层的对象进行拷贝。第二层或者往下的仍然还是拷贝地址
var obj1 = {name: 'plane'};
var obj2 = Object.assign({}, obj1);
obj1.name = 'hjh';
console.log(obj1, obj2); //{name: 'hjh'},{name: 'plane'}
var obj1 = {name: 'plane'};
var obj2 = {...obj1};
obj1.name = 'hjh';
console.log(obj1, obj2); //{name: 'hjh'},{name: 'plane'}
for..in
这种用法也就是遍历对象属性,把对象属性重新设置到新的对象上
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
var obj1 = {name: 'plane'};
var obj2 = shallowClone(obj1);
obj1.name = 'hjh';
console.log(obj1, obj2); //{name: 'hjh'},{name: 'plane'}
如果对象是数组,可以使用数组的方法。
var obj1 = [1,2,3];
var obj2 = obj1.slice();
obj1[0]= 'plane';
console.log(obj1, obj2); //['plane',2,3],[1,2,3]
var obj1 = [1,2,3];
var obj2 = obj1.concat([]);
obj1[0]= 'plane';
console.log(obj1, obj2); //['plane',2,3],[1,2,3]
即无限层级拷贝。
最简单的深拷贝就是浅拷贝+递归。递归条件就是判断一下是不是对象就可以了。
function clone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = clone(source[i]); // 注意这里
} else {
target[i] = source[i];
}
}
}
return target;
}
但是上述实现例子会存在几个问题
JS中的黑魔法,使用一行代码实现深拷贝
function deepClone(target) {
return JSON.parse(JSON.stringify(target));
}
但是也会存在以下几个问题
最后附上颜海镜大大的文章。深拷贝的终极探索
深拷贝和浅拷贝都是相对于引用类型而言。 JS中的数据类型分为基本类型和复杂类型;对基本类型复制时会对数据进行一份拷贝,而对复杂类型赋值,则会进行地址的拷贝,使两份数据在内存里指向同一个地址。 所以进行浅拷贝的时候,事实上无论修改哪一份变量,最终所有进行指向同一个地址的变量,都会一同发生变化。 代码如下:
var a = {
b: 1
}
var b = { ...a }
b.b = 2
a // {b: 2}
深拷贝理解起来也很简单,就是执行之后,新的变量无论怎么改动都不会影响原来的变量就对了
以下代码来自优秀JS库 lodash
function baseClone(value, bitmask, customizer, key, object, stack) {
let result
const isDeep = bitmask & CLONE_DEEP_FLAG
const isFlat = bitmask & CLONE_FLAT_FLAG
const isFull = bitmask & CLONE_SYMBOLS_FLAG
if (customizer) {
result = object ? customizer(value, key, object, stack) : customizer(value)
}
if (result !== undefined) {
return result
}
if (!isObject(value)) {
return value
}
const isArr = Array.isArray(value)
const tag = getTag(value)
if (isArr) {
result = initCloneArray(value)
if (!isDeep) {
return copyArray(value, result)
}
} else {
const isFunc = typeof value == 'function'
if (isBuffer(value)) {
return cloneBuffer(value, isDeep)
}
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
result = (isFlat || isFunc) ? {} : initCloneObject(value)
if (!isDeep) {
return isFlat
? copySymbolsIn(value, copyObject(value, keysIn(value), result))
: copySymbols(value, Object.assign(result, value))
}
} else {
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
result = initCloneByTag(value, tag, isDeep)
}
}
// Check for circular references and return its corresponding clone.
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
return stacked
}
stack.set(value, result)
if (tag == mapTag) {
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
if (tag == setTag) {
value.forEach((subValue) => {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
})
return result
}
if (isTypedArray(value)) {
return result
}
const keysFunc = isFull
? (isFlat ? getAllKeysIn : getAllKeys)
: (isFlat ? keysIn : keys)
const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
if (props) {
key = subValue
subValue = value[key]
}
// Recursively populate clone (susceptible to call stack limits).
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
}
js中的数据类型分为基本类型和引用类型。
针对引用类型我们就有了浅拷贝和深拷贝两种方法,它们区别在于对象的嵌套层级,浅拷贝就只能对第一层进行拷贝,深拷贝则可以通过JSON.parse(JSON.stringify(object)),递归(对多个层级进行类型判断依次执行对应类型的浅拷贝。
数组对象的深拷贝与浅拷贝 前提:原始数据类型和对象类型赋值时的差异 JavaScript的数据类型分为原始数据类型和对象类型。二者在内存中存放的方式不同,导致了其赋值时差异。分别举个栗子
var x = 1;
var y = x; //y获得了和x同样的值
y = 2;
console.log(x); // 1
var m = [1,2]; //m存放的是指向[1,2]这个数组对象的引用地址
var n = m; //n也获得 [1,2]数组对象的引用地址
n[0] = 3;
console.log(m); //[3,2]
由上栗子可以看出 :原始数据类型赋值时,给的是实实在在的数据值 ,赋值后二者只是值一样而已,不会相互影响; 而对象类型,给的是 原数据的引用地址,所以新旧数据会互相影响,因为本质上还是同一个数据对象,如上栗中的数组
浅拷贝就是流于表面的拷贝方式;当属性值为对象类型时,只拷贝了对象数据的引用,导致新旧数据没有完全分离,还会互相影响
//测试数据
var array1 = ['a',1,true,{name:'lei',age:18}];
//concat() slice() 实现浅拷贝
var array2 = array1.concat()
//修改拷贝后的数据
array2[0] = 'b'; //array1[0]是原始数据类型 所以是直接赋值的
array2[3].name = 'zhang'; //array1[3]是对象数据类型 所以拷贝的是对象的引用,其实还是和原数组使用同一对象
console.log(array1); // ['a',1,true,{name:'zhang',ag
栗子中 array2是array1的浅拷贝对象,数组元素是原始数据类型的不会相互影响了(array1[0]),但是array1[3]是对象类型,还是会互相影响。
从浅拷贝解释基本可以明白,深拷贝就是 ‘完全’拷贝,拷贝之后新旧数据完全分离,不再共用对象类型的属性值,不会互相影响。
var test = [1,'a',{name:'lei',age:18}];
var copy1 = JSON.parse(JSON.stringify(test)); //特殊方式
console.log(copy1);
copy1[2].name = 'zhang' console.log(test); //[1,'a',{name:'lei',age:18}] 未受到影响
- 2. 实现深拷贝
已经实现了浅拷贝,思考下应该是对 对象类型属性值赋值时,导致的没有完全分离,所以要修改下 拷贝对象类型属性值的方式,对它再调用一次深拷贝,这样就实现了深拷贝,如下:
//实现深拷贝 function deepCopy( target ){ if(typeof target !== 'object') return ; //判断目标类型,来创建返回值 var newObj = target instanceof Array ? [] : {};
for(var item in target){ //只复制元素自身的属性,不复制原型链上的 if(target.hasOwnProperty(item)){ newObj[item] = typeof target[item] == 'object' ? deepCopy(target[item]) : target[item] //判断属性值类型 } }
return newObj }
//测试 var test = [1,'a',{name:'lei',age:18}];
var copy2 = deepCopy(test); copy2[2].name = 'zhang'
console.log(test); ////[1,'a',{name:'lei',age:18}] 未受到影响
# 总结
一定要理解造成浅拷贝的原因:对象类型数据复制时,复制了引用地址,用的还是同一个数据对象;所以实现深拷贝的方式就是要对 对象类型属性值递归进行深拷贝,避免直接赋值。
首先要明白,深拷贝
和浅拷贝
只针对复杂类型(Object
、Array
)来说的。
对于复杂类型而言,一个对象创建的时候会在内存中分配两块空间,一个在栈内存中存储对象的引用指针,一个在堆内存中存储对象真实的数据。这时候会有一个问题,你拷贝的只是这个引用指针,还是连同它的真实数据一起拷贝,所以才会有深浅拷贝一说。
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象在栈中的引用地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
let obj = {
age: 28
}
let obj2 = obj;
obj2.age = 30;
console.log(obj.age); // 30
console.log(obj2.age); // 30
上面代码说明了obj
和obj2
是指向的同一个对象,一个对象作出改变,另一个也会跟着改。
在了解到浅拷贝
和深拷贝
之前, 我以为=
赋值就是拷贝了(捂脸)。。。,其实不然。
浅拷贝
是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(复杂类型),拷贝的就是内存地址,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
要实现浅拷贝
也比较简单:
可以使用Object.assign()
、扩展运算符...
、Array.prototype.slice()
、Array.prototype.concat()
等,这里我们拿其中一个来举例:
let obj = {
age: 28
}
let obj2 = Object.assign({}, obj);
obj2.age = 30;
console.log(obj.age); // 28
console.log(obj2.age); // 30
通过这个例子,可以很清楚的知道浅拷贝
跟=赋值
的区别了:浅拷贝
可以实现基本类型的拷贝且不会影响到原对象。
我们在上面代码的基础上再做一下修改:
let obj = {
age: 28,
child: {
age: 8
}
}
let obj2 = Object.assign({}, obj);
obj2.child.age = 10;
console.log(obj.child.age); // 10
console.log(obj2.child.age); // 10
上面的代码说明:浅拷贝
只能实现第一层基本类型的拷贝,当对象包含子对象时,实际上拷贝的只是引用地址,无论哪个对象作出改变,另一个都会跟着改变。
这时候就需要使用深拷贝
了。
简单理解就是:可以实现多层数据的拷贝,拷贝后的对象是一个全新独立的对象,重新为其分配内存空间,跟原对象不会相互影响。
实现深拷贝
最简单的应该是使用JSON.parse(JSON.stringify(obj))
这个方法。
let obj = {
age: 28,
child: {
age: 10
}
}
let obj2 = JSON.parse(JSON.stringify(obj));
obj2.age = 30;
obj2.child.age = 12;
console.log(obj.age); // 28
console.log(obj2.age); // 30
console.log(obj.child.age); // 10
console.log(obj2.child.age); // 12
但是该方法也是有局限性的(来自某大佬的小册内容):
undefined
symbol
let obj = {
age: undefined,
sex: Symbol('man'),
jobs: function () {},
name: 'dazhi'
}
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj); // {name: "dazhi"}
可以看到,上述情况中,undefined
、symbol
、函数
都被忽略掉了。
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.c = obj.b;
obj.b.c = obj.c;
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
如果你有这么一个循环引用对象,你会发现并不能通过该方法实现深拷贝
,会报错。
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;原型链上的属性无法获取
上面代码说明:JSON.parse(JSON.stringify(obj))
无法拷贝原型链上的属性。
实际开发中,可以直接使用Lodash
库的深拷贝方法。
Lodash
深拷贝方法源码地址:https://github.com/lodash/lodash/blob/master/.internal/baseClone.js
--- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含对象 |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改 |
深拷贝 | 否 | 改变不会会使原数据一同改变 | 改变不会使原数据一同改 |
参考:
深拷贝: 深拷贝是复制变量的值,对于非基本类型,递归直到基本类型值后再进行复制,深拷贝生成的变量与原来的变量互不影响。 浅拷贝: 浅拷贝是复制变量的属性,但是当变量是引用类型的时候,复制的是引用,所以改变一个值,另一个也会受到影响。
let deepclone = function(obj) {
let res
if(typeof obj == "object") {
res = Array.isArray(obj) ? [] : {}
for(let i in obj) {
res[i] = typeof obj[i] === "object" ? deepclone(obj[i]) : obj[i]
}
} else {
res = obj
}
return res
}
深拷贝与浅拷贝的慨念划分主要是复杂类型的赋值引发的。
由于复杂类型(array/object)存储于内存中。而JavaScript不允许直接从内存中操作数据。所以js贴心的给了你一张房卡。 赋值只是复制了一张房卡,而不是赋值了一件房子。多张房卡对应一间房子。
所以,复制房卡的是浅拷贝。 想要多一栋房子,就叫深拷贝。 深拷贝有几种方式; 例如:JSON.parse(JSON.stringify(obj)); 这种方式有缺陷,无法对function进行拷贝;
深拷贝与浅拷贝的区别?如何实现一个深拷贝
在回答这个问题前,我们先来回顾一下JS中两大数据类型
基本类型 Undefined、Null、Boolean、Number、String 引用类型 Object Array 基本类型 基本类型就是值类型, 存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配
引用类型 引用类型, 存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置 如何实现深拷贝 JSON.stringify() 首先安利一个无脑黑科技骚操作 * 缺点: JSON.stringify()无法正确处理函数
let obj = { name: '每日一题', value: 'JS' } console.log(JSON.parse(JSON.stringify(obj))) // 深拷贝了一份obj
let obj = { name: '每日一题', value: 'JS', fn: function(){} } console.log(JSON.parse(JSON.stringify(obj))) // obj.fn 丢失 总结 浅拷贝是复制,两个对象指向同一个地址 深拷贝是新开栈,两个对象指向不同的地址
深拷贝是对对象以及对象的所有子对象进行拷贝,和原数据没有指向同一个对象,如果第一层为基本数据类型,改变不会使原数据一同改变,如果原数据中包含子对象,改变不会使原数据一同改变。
浅拷贝和原数据没有指向同一个对象,如果第一层为基本数据类型,改变不会使原始数据一同改变,如果原数据中包含子对象,改变会使原数据一同改变。
/**
* target 目标对象 对象都合并到target里
* source 合并对象
*/
function deepClone(target, source) {
for (key in source)
if ((isPlainObject(source[key]) || isArray(source[key]))) {
// source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
// source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
// 执行递归
extend(target[key], source[key])
}
// 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
else if (source[key] !== undefined) target[key] = source[key]
}
针对引用类型(对象、数组)
浅拷贝一个对象,如果属性是基本数据类型,直接拷贝值,若新的对象属性值改变,不会改变原来的属性值。但是对于引用类型的数据,只是拷贝了该对象的地址,与原对象指向同一个地址,若新的对象改变,会影响原来的对象。 如:object.assign()、Array.prototype.slice()、Array.prototype.contact()、扩展运算符...
深拷贝一个对象,除了拷贝基本数据类型的值,会递归拷贝所有引用类型,所有引用类型都会指向一个新的内存地址。与原来的对象完全隔离,新的对象的改变不会影响原来的对象。 实现:
let obj = {
name: 'yeAlice',
info: {
age: 18,
sex: 'female'
},
hello: function() {
console.log('hello, ' + this.name)
},
hobby: undefined
}
let obj1 = JSON.parse(JSON.stringify(obj)) console.log(obj1) // { // name: 'yeAlice', // info: { // age: 18, // sex: 'female' // } // }
obj.info.age = 20 console.log(obj1.info.age) // 18
缺点:
不能正确处理函数、undefined、RegExp、new Date()等
2. 写一个deepClone函数
看了题主写的,按照思路自己实现了一遍。
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 }
let newObj = new obj.constructor() for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = deepClone(obj[key]) } } return newObj }
let a = { name: 'Alice', info: { age: null, sex: 'female' }, day: new Date(), reg: /\w/, test: undefined }
let b = deepClone(a) // { // name: 'Alice', // info: { // age: null, // sex: 'female' // }, // day: new Date(), // reg: /\w/, // test: undefined // }
深拷贝=>拷贝所有的属性,并且地址也与原来的不同,这样的话,你改变当前的属性也不会影响原来的 浅拷贝=>就是直接赋值的这种,地址相同,当你改变现在的值,原来的值也会跟着改变
深拷贝的实现: a:JSON.parse(JSON.stringify(obj)) => 这种方式只能对属性不是function的有用 b:Object.assgin({},target) => 这种方式第一层是深拷贝,第二层是浅拷贝 c:jquery的JQ.extend d:采用递归来进行拷贝 e:采用扩展运算符var a = {...obj};