在之前总结 This 用法 的时候,遇到了传参问题,由此意识到了复制与引用的问题,说得高端些,引申出了深拷贝与浅拷贝的问题,后来在链家面试的时候,和一面也讨论到这个问题,当时的理解比较乱,涉及到深拷贝实现的方面都不熟悉,现在试着总结一下。

基本类型 & 引用类型

ECMAScript 中的变量类型分为两类

  • 基本类型:number、string、boolean、null、undefined
  • 引用类型:Object

两者的区别就是这篇文章要讨论的赋值问题,首先复习一下两种变量类型的存储方式。

基本类型

基本类型变量的存储方式如下图所示,栈内存中分别存储着变量的标识符以及变量的值。

所以 var a = 'leeon'; 这里的 a 就是以下面的的方式进行存储。

引用类型

至于引用类型变量,参看下图,可以对比上面基本类型变量的存储结构。

引用类型变量与基本类型变量的区别是,栈内存存储的是变量的标识符以及对象在堆中的存储地址。可能听起来拗口,例如 var obj1 = {name: 'leeon'}; 这样的对象定义之后,就会以下图的方式进行存储。

存储结构的差异只是前提,下面的代码才到重点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 情境一
// 基本类型变量
var a = 'leeon'; //leeon
var b = a;
console.log(b); //leeon
b = 'pine'; // pine
console.log(a); //leeon

// 这段代码很简单,改变 b 并不会影响到 a
// 下面这段就不同了

//情境二
//引用类型变量
var obj1 = {name: 'leeon'}
var obj2 = obj1;
console.log(obj2); // {name: 'leeon'}
obj2.name = 'pine';
console.log(obj1); // {name: 'pine'}

// obj2 是 obj1 的副本,但修改 obj2 却影响了 obj1

// 情境三

var obj1 = {name: 'leeon'}
var obj2 = obj1;
console.log(obj2); // {name: 'leeon'}
obj2 = {name: 'pine'};
console.log(obj1); // {name: 'leeon'}

// obj2 是 obj1 的副本,但修改 obj2 没有影响了

这样的问题最初遇到时觉得很费解,但是从内存角度进行理解之后,答案开始清晰,情境一中,基本类型变量的拷贝,复制了变量的标识符以及值,ab 的关系保持了独立。

而在情境二中,在引用类型变量这里,拷贝时复制的是变量的标识符与 对象存储地址,导致最后 obj1obj2 都指向同一个对象,obj2obj1 只是同一对象的两个面具,相互影响自然是必然的结果。

但是情境三中,对 obj2 的更改却没影响到 obj1,这又是为什么?难道上面的理论有问题?当然不是,给 obj2 赋值一个新的对象,背后做的其实是将 obj2 的对象地址指向 {name: 'pine'},并不会影响到原对象,原理如下 :

这样的拷贝方式就引发了一个问题,我本想拷贝一个新的完全独立的新对象,最后只得出来一个壳,这是无法接受的(感受到了敷衍……)。这里就引出了深拷贝与浅拷贝之说(人们总喜欢造一堆高大上的概念)。

深拷贝 & 浅拷贝

由此可以明白,所谓浅拷贝,指的是仅拷贝对象地址,拷贝后的副本与原对象仍共用一块内存,而深拷贝则是将对象进行内存级别的复制,拷贝的副本与原对象保持独立。

如何实现深拷贝

JSON.parse()

1
2
3
4
var oobj1 = {name: 'leeon',friens: {name:['john','kk','jd']}};
temp = JSON.stringify(obj1);
var obj2 = JSON.parse(temp);
// p 是 o 的深拷贝

slice()

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

concat()

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

jQuery 的 extend()

参考: