JavaScript中 浅拷贝与深拷贝的理解及实现

简介: JavaScript中 浅拷贝与深拷贝的理解及实现

1. 相关知识点

1.1 基本类型与引用类型分类

基本类型:number,string,boolean,null,undefined,symbol及BigInt(任意精度整数)七类;

引用类型:对象、数组、函数等;

1.2 JS中变量的存储方式

栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的名值和引用类型的名(地址)

堆:动态分配的内存,大小不定,也不会自动释放,里面存放引用类型的值

1.2.1 基本类型

let a = 1;

20210104203500268.jpg

当你b = a复制时,栈内存会新开辟一个内存

20210104203500268.jpg

当你此时修改a=2,对b并不会造成影响;

let a=1,b=a;

虽然b不受a影响,但这也算不上深拷贝因为深拷贝本身只针对较为复杂的object类型数据。

1.2.2 引用数据类型

let a = [0,1,2,3,4];

20210104203500268.jpg

当 b = a 进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值;

20210104203500268.jpg

当a[0] = 1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,

这就是浅拷贝。

20210104203500268.jpg

要是在堆内存中也开辟一个新的内存专门为b存放值(就像基本类型那样),就达到深拷贝的效果了。

20210104203500268.jpg

2. 实现浅拷贝的方法

2.1 for···in 只循环第一层

// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
  var obj2 = Array.isArray(obj1) ? [] : {};
  for (let i in obj1) {
   obj2[i] = obj1[i];
  }
  return obj2;
}
var obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3
  }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.c1 = 4;
console.log(obj1.a,obj2.a); // 1  3
console.log(obj1.c.c1,obj2.c.c1); // 4  4

2.2 Object.assign方法

var obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3
  }
}
var obj2 = Object.assign({},obj1);
obj2.a = 3;
obj2.c.c1 = 4
console.log(obj1.a,obj2.a) // 1  3
console.log(obj1.c.c1,obj2.c.c1); // 4  4

2.3 直接用 = 赋值

let a = [0,1,2,3,4],
    b = a;
console.log(a===b); // true
a[0]=1;
console.log(a,b); // [1,1,2,3,4]   [1,1,2,3,4]

3. 实现深拷贝的方法

3.1 采用递归去拷贝所有层级属性

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 a = [1,2,3,4]
let b = deepClone(a);
a[0]=2;
console.log(a,b); // [2,2,3,4]  [1,2,3,4]
let obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3
  }
}
let obj2 = deepClone(obj1)
obj2.c.c1 = 4
console.log(obj1,obj2); // {a:1,b:2,c:{c1:3}}   {a:1,b:2,c:{c1:4}}

3.2 通过JSON对象来实现深拷贝

注意: 无法实现对象中方法的深拷贝

function deepClone(obj) {
  var _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
  return objClone;
}  
let a = [1,2,3,4]
let b = deepClone(a);
a[0]=2;
console.log(a,b); // [2,2,3,4]  [1,2,3,4]
let obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3,
    c2:function(){ // 无法实现对对象中方法的深拷贝
      console.log('深拷贝') 
    }
  }
}
let obj2 = deepClone(obj1)
obj2.c.c1 = 4
console.log(obj1,obj2); // {a:1,b:2,c:{c1:3,c2:f()}}   {a:1,b:2,c:{c1:4}}

3.3 通过 jQuery 中的 extend 方法实现深拷贝

$.extend( [deep ], target, object1 [, objectN ] )

deep表示是否深拷贝,true为深拷贝,false为浅拷贝

target Object类型 目标对象,其他对象的成员属性将被附加到该对象上;

object1 objectN(可选), Object类型 第一个以及第N个被合并的对象;

var array1 = [1,2,3,4];
var newArray = $.extend(true,[],array1); // true为深拷贝,false为浅拷贝

3.4 lodash函数库 实现深拷贝

lodash中文网

let result = _.cloneDeep(test)

3.5 Reflect法

reflect简介

function isObject(val) {
  return val != null && typeof val === 'object' && Array.isArray(val) === false;
}
function deepClone(obj) {
  let isArray = Array.isArray(obj)
  let cloneObj = isArray ? [...obj] : { ...obj }
  Reflect.ownKeys(cloneObj).forEach(key => {
    cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })
  return cloneObj
}
let a = [1,2,3,4]
let b = deepClone(a);
a[0]=2;
console.log(a,b); // [2,2,3,4]  [1,2,3,4]
let obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3,
    c2:function(){
      console.log('深拷贝')
    }
  }
}
let obj2 = deepClone(obj1)
obj2.c.c1 = 4
console.log(obj1,obj2); // {a:1,b:2,c:{c1:3,c2:f}}   {a:1,b:2,c:{c1:4,c2:f}}

3.6 手动实现深拷贝

let obj1 = {
   a: 1,
   b: 2
}
let obj2 = {
   a: obj1.a,
   b: obj1.b
}
obj2.a = 3;
alert(obj1.a); // 1
alert(obj2.a); // 3

3.7 Object.assign 方法

注意:

如果对象的 value 是基本类型,可用 Object.assign 来实现深拷贝

// 对象的 value 是基本类型
var obj = {
  a: 1,
  b: 2,
}
var obj1 = Object.assign({}, obj); // obj赋值给一个空{}
obj1.a = 3;
console.log(obj.a) // 1

3.8 slice 实现对数组的深拷贝

注意:

当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝;

当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝;

// 当数组里面的值是基本数据类型,比如 String,Number,Boolean 时,属于深拷贝
// 当数组里面的值是引用数据类型,比如 Object,Array 时,属于浅拷贝
var arr1 = ["1","2","3"]; 
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log(arr1);  // ["1","2","3"]
console.log(arr2);  // ["1","9","3"]

3.9 concat 实现对数组的深拷贝

注意:

当数组里面的值是基本数据类型,比如 String,Number,Boolean 时,属于深拷贝;

当数组里面的值是引用数据类型,比如 Object,Array 时,属于浅拷贝;

// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝;
// 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝;
var arr1 = ["1","2","3"];
var arr2 = arr1.concat();
arr2[1] = "9";
console.log(arr1); // ["1","2","3"]
console.log(arr2); // ["1","9","3"]
var arr1 = [{a:1},{b:2},{c:3}];
var arr2 = arr1.concat();
arr2[0].a = "9";
console.log(arr1[0].a); // 9
console.log(arr2[0].a); // 9

3.10 通过 var newObj = Object.create(oldObj)

注意: 无法实现对对象中方法的深拷贝;

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];  // 避免相互引用对象导致死循环,如 initalObj.a = initalObj 的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
let obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3,
    c2:function(){
      console.log('深拷贝')
    }
  }
}
let obj2 = deepClone(obj1)
obj2.c.c1 = 4
console.log(obj1,obj2); // {a:1,b:2,c:{c1:3,c2:f}}   {a:1,b:2,c:{c1:4}}

3.11 使用 扩展运算符 实现深拷贝

注意:

当value是基本数据类型,比如 String,Number,Boolean 时,属于深拷贝;

当value是引用类型的值,比如 Object,Array,属于浅拷贝;

let obj1 = {
  a: 1,
  b: 2,
  c: {
    c1: 3,
    c2:function(){
      console.log('深拷贝')
    }
  }
}
let obj2 = {...obj1}  // 此时拷贝了 {c1:3} 的引用地址
obj2.c.c1 = 4        // 改变对象里面的值
console.log(obj1,obj2);  // {a:1,b:2,c:{c1:4,c2:f}}   {a:1,b:2,c:{c1:4,c2:f}}
let obj3 = {...obj1}  
obj3.c = {c1:5}     // 改变引用的对象,实际改变了引用对象的地址
console.log(obj1,obj3);   // {a:1,b:2,c:{c1:4,c2:f}}   {a:1,b:2,c:{c1:5}}

参考文章(侵删)

相关文章
|
2月前
|
JSON 前端开发 JavaScript
JavaScript拷贝大作战:浅拷贝vs深拷贝
JavaScript拷贝大作战:浅拷贝vs深拷贝
51 0
|
3月前
|
JSON 前端开发 JavaScript
【面试题】JavaScript 深拷贝和浅拷贝 高级
【面试题】JavaScript 深拷贝和浅拷贝 高级
|
14天前
|
JavaScript 前端开发
js中浅拷贝和深拷贝的区别
js中浅拷贝和深拷贝的区别
21 1
|
1月前
|
JavaScript 前端开发 API
javascript中的浅拷贝和深拷贝
javascript中的浅拷贝和深拷贝
|
3月前
|
JavaScript 前端开发
Js中浅拷贝和深拷贝有什么区别,如何实现?
在 JavaScript 中,浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两种常见的数据拷贝方式,它们的区别在于拷贝的程度
22 0
|
3月前
|
JavaScript 前端开发
JavaScript浅拷贝和深拷贝
JavaScript浅拷贝和深拷贝
|
3月前
|
JavaScript 前端开发
js中浅拷贝和深拷贝的区别
js中浅拷贝和深拷贝的区别
27 0
|
5月前
|
存储 JSON JavaScript
JavaScript深拷贝与浅拷贝
JavaScript深拷贝与浅拷贝
22 0
|
6月前
|
存储 JavaScript 前端开发
带你读《现代Javascript高级教程》十七、JavaScript深拷贝与浅拷贝(1)
带你读《现代Javascript高级教程》十七、JavaScript深拷贝与浅拷贝(1)
|
6月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十七、JavaScript深拷贝与浅拷贝(2)
带你读《现代Javascript高级教程》十七、JavaScript深拷贝与浅拷贝(2)