一.前言
每一个前端的 JavaScript 之路不一定是由《JavaScript 高级程序设计》开启的,但是每一位前端都一定被“按值传递”和“按引用传递”这两个概念坑过。现在你我应该都很清楚,在 JavaScript 中的object
类型是按引用传递的,但是在函数参数中,所有参数都是按值传递的。
我们今天要谈的东西,就起源于object
型数据的复制与再操作,简单来说,就是我们今天的主题:对象的浅拷贝和深拷贝。
二.按引用传递是什么含义?
首先,我们需要快速回想一下在 JavaScript 中基本数据类型有哪些,请看下面:
1. number
2. string
3. boolean
4. null
5. undefined
6. symbol
引用数据类型只有一种:
object
当然,由此衍生出来的变种也有很多,包括Array,function
等等,也可以看成是对象的一种。
接着我们得复习下 JavaScript 的堆栈中是怎么存储数据的。在 JS 中内存的使用和分配与其他语言也大同小异:
堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放。
对以上6种基本数据类型来说,
当我们在 JavaScript 中声明一个简单基础数据类型并初始化它的值时:
const NAME = 'waw';
此时这个值将以键值对的形式key:'NAME', value:'waw'
保存在栈中。
但是对引用类型的数据来说却并不是如此,对它来说,虽然也是以键值对形式存储于栈中,但是value
部分存储的是指向堆中的地址,如:key:'NAME', value: 指向堆的指针
,而value
真正的值则存储在堆中。而在 JavaScript 中是不允许直接操作堆中的内容的,所以我们日常操作对象时实际上操作的是对象的引用。
到这儿为止,咱们的提前知识储备已经差不多了,从上面的内容我们可以达成一个基础的共识,那就是基础数据类型是没有浅拷贝深拷贝之说的,大家都是存储在栈中的社会主义公民,都很平等。所以为了接下来讲解的层次性,我将从数组和对象,基本数据类型和引用数据类型,两种角度出发来给大家讲讲浅拷贝和深拷贝。
三.JavaScript 的浅拷贝
3.1 从基本数据类型的数组角度来说
形如var arr = [1, '1', true, null, undefined, Symbol()]
,这一类数组内元素的集合,我们都可以称它们为基本数据类型的数组。
浅拷贝最简单的就是var a = b
了。举个例子:
var arr = [1, '1', true, null, undefined, Symbol()];
var arrCopy = arr;
arrCopy[0] = 2;
console.log(arr)
console.log(arrCopy)
结果如下:
很明显,这就是个最简单的浅拷贝。当然我们也可以自己实现一个基础的浅拷贝函数:
function shallowClone(obj){
let cloneObj = {
};
for(let key in obj){
if(obj.hasOwnProperty(key)){
cloneObj[key] = obj[key];
}
}
return cloneObj;
}
3.2 从引用数据类型的数组角度来说
什么是引用数据类型的数组?举个简单例子,形如[[1, 2], {age: 1}, Number(1), String(2)]
的数组内元素的集合,都可以称为引用数据类型。当然,我们平常在业务中最常见的就是多维数组或者是对象数组。
对它来说,浅拷贝最直接的方式也是var a = b
;举个例子:
var arr = [[1, 2], {
age: 1}, Number(1), String(2)];
var arrCopy = arr;
arrCopy[0] = [3, 4];
console.log(arr)
console.log(arrCopy)
结果如下:
3.3 从对象角度来说
从对象角度来说,它就不像数组那样好区分基本数据类型和引用数据类型了,因为毕竟是key=> value
键值对形式来存储的结构。
我们一般也可以通过var a=b
来进行浅拷贝。举个例子:
var arr = {
name:'waw', age:1};
var arrCopy = arr;
arrCopy.name = 'gcc';
console.log(arr)
console.log(arrCopy)
结果如下:
这儿有个很有意思的事,ES6有一个Object.assgin()
方法,可以拷贝(合并)对象并返回新对象。我们试试用它来复制上面的对象看会输出什么: