深入了解JS深复制与浅复制

面试常问到什么是深复制和浅复制(深拷贝和浅拷贝),怎么实现深复制,那么我们一起来探讨学习下。

下面三句话可以初步理解深浅复制的区别:

  1. 首先深复制和浅复制只针对像 Object, Array 这样的复杂对象的。
  2. 深复制:复制的是引用(地址),浅复制:复制的是实例。
  3. 简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

首先先了解ECMAScript 中的变量类型,分为两类:

  • 基本类型:number、string、boolean、null、undefined
  • 引用类型:Object(Object,Array,Date,RegExp,Function)

关于null和undefined的区别

  • null表示"没有对象",即该处不应该有值。(转为数值为‘O’,例 :5 + null  // 5)
  • undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。(转为数值为‘NaN’,例:5 + undefined // NaN)

关于两种数据类型的存储区别

基本类型的数据是存放在栈内存中的,而引用类型的数据是存放在堆内存中的。

下面列举两个例子来理解:

1.基本类型的数据存储:(实质上存储形式只是变量的标识符以及变量的值)

var a = 'A';

 zhan

2.引用类型的数据存储:(实质上存储形式只是变量的标识符以及对象在堆内存中的存储地址)

var a = {name:“jack”};

堆

关于两种数据类型的复制区别

1. 基本类型 的复制:当你在复制基本类型的时候,相当于把值也一并复制给了新的变量。看如下例子:

var a = 1;
var b = a;
console.log(a === b); // true
var a = 2;
console.log(a); // 2
console.log(b); // 1

上述代码执行结果用图表方式理解如下:

基础类型存储到栈

2. 引用类型 的复制:当你在复制引用类型的时候,实际上只是复制了指向堆内存的地址,即原来的变量与复制的新变量指向了同一个东西。 看如下例子:

var a = {name:"jack",age:20};
var b = a;
console.log(a === b); // true
a.age = 30;
console.log(a); // {name:"jack",age:30};
console.log(b); // {name:"jack",age:30};

上述代码执行结果用图表方式理解如下:

引用类型数据的存储

 通过上述对引用类型数据“浅复制”的例子,我们将下图“a复制给b”实现效果称为‘深复制’:

深复制

 那么如何实现深复制呢?我的总结如下:

1 》》利用 递归 来实现,对属性中所有引用类型的值,遍历到是基本类型的值为止。

function deepClone(source){    
  if(!source && typeof source !== 'object'){      
    throw new Error('error arguments', 'shallowClone');    
  }    
  var targetObj = Array.isArray(source) ? [] : {};    
  for(var keys in source){       
    if(source.hasOwnProperty(keys)){          
      if(source[keys] && typeof source[keys] === 'object'){  
        targetObj[keys] = deepClone(source[keys]);    //递归      
      }else{            
        targetObj[keys] = source[keys];         
      }       
    }    
  }    
  return targetObj; 
}

//检测深复制代码
var a = {name:"jack",age:20};
var b = deepClone(a);
console.log(a === b); // false
a.age = 30;
console.log(a); // {name:"jack",age:30};
console.log(b); // {name:"jack",age:20};

2 》》通过JSON 对象中的 stringify 把一个 js 对象序列化为一个 JSON 字符串,然后用parse 可以把 JSON 字符串反序列化为一个 js 对象也可实现深复制。举个例子:

var a = {name: 'hjs',friends: {name:['ali','qq','baidu']}};
var b = JSON.parse(JSON.stringify(a);
// b 是 a 的深拷贝

3 》》Array 的 slice 和 concat 方法

var arr1 = ["1","2","3"];
var arr2 = arr1.slice(0);
或 var arr2 = arr1.concact()
// arr2 是 arr1 的深拷贝

其实这里有个误区,slice和concat方法并不是严格意义上的深复制,举个例子你就理解了:

var a = [[1,2,3],4,5];
var b = a.slice();
或 var b = a.concat();
console.log(a === b); // false
a[0][0] = 6;
console.log(a); // [[6,2,3],4,5]
console.log(b); // [[6,2,3],4,5]

细心的你会发现,a中数组内的值发生改变,b也随之改变。其实对此我的总结如下:

通过Array的slice和concat方法进行复制,只会对第一层级的属性进行所谓深复制,并不是真正意义上的深复制。

4 》》通过第三方库的实现

jQuery ——  $.extend(true, {}, ...)

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var y = $.extend({}, x),          //浅复制
    z = $.extend(true, {}, x);    //深复制

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false

lodash —— _.clone() / _.cloneDeep()

_.clone(obj, true)等价于_.cloneDeep(obj)

var $ = require("jquery"),
    _ = require("lodash");

var arr = new Int16Array(5),
    obj = { a: arr },
    obj2;
arr[0] = 5;
arr[1] = 6;

// 1. jQuery
obj2 = $.extend(true, {}, obj);
console.log(obj2.a);                            // [5, 6, 0, 0, 0]
Object.prototype.toString.call(obj2);           // [object Int16Array]
obj2.a[0] = 100;
console.log(obj);                               // [100, 6, 0, 0, 0]

//此处jQuery不能正确处理Int16Array的深复制!!!

// 2. lodash
obj2 = _.cloneDeep(obj);                       
console.log(obj2.a);                            // [5, 6, 0, 0, 0]
Object.prototype.toString.call(arr2);           // [object Int16Array]
obj2.a[0] = 100;
console.log(obj);                               // [5, 6, 0, 0, 0]

通过上面代码很好理解lodash库是相较于jQuery更好的更全面的处理深复制的第三方库。

文章参考:深入剖析 JavaScript 的深复制【 js 基础 】 深浅拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值