JS的深拷贝和浅拷贝对比

649 阅读4分钟

JS的深拷贝和浅拷贝对比

引言: 最近复习一些JS的基础知识点,看到JS深拷贝、浅拷贝发现只有一些模糊的印象。所以决定认真的探讨一下其中的道理。

先说堆和栈

  • 有人就会纳闷,为啥扯到了堆和栈?
  • 其实深拷贝和浅拷贝最主要的区别就是其在内存中的存储类型。
  • 堆和栈都是内存中划分出来用于存储的区域。
栈(stack)为自动分配的内存空间,它由系统自动释放;
堆(heap)则是动态分配的内存,大小不定也不会自动释放。

JS基本数据类型

五个基本数据类型:String、Number、Boolean、Undefined、Null

基础的数据类型是存放在栈里的

存放在栈中的简单数据类型,数据大小确定,是直接按值存放的,所以可以直接访问。

JS基本数据类型的值不可改变

对这个JS概念不太了解的同学可能会感到诧异。稍安勿躁,我慢慢道来。
基础数据类型和对象、数组、函数有着根本上的区别。
任何函数都无法更改一个原始值。
我们平时认为的修改一个参数的值,其实是定义并返回了一个新的值。
Talk is cheap show me the code.
var str = 'abc'
str[1] = 'd'
str = 'abc'
相信了吧,
所以,记住这一点:基本数据类型值不可变!

引用类型(Object)是存放于堆中的

变量其实是存放在栈中的一个指针,指向堆中存放的数据。
和基本数据类型不同,引用类型是可变的。
Talk is cheap show me the code.
var arr = ['a', 'b', 'c']
arr[0] = 'c'
arr = ['c', 'b', 'c']

赋值的对比

var a = 1
var b = a
a = a + 1
b = b + 2

a = 2
b = 3
由此可知基础数据类型的赋值是传递值。b开辟了新的内存来存放自己的值。a、b此时是独立的。

var a = {}
var b = a
a.date = '2018-12-12'

a = {name: '2018-12-12'}
b = {name: '2018-12-12'}
由此我们可知,引用类型的赋值其实是传递了指针。a、b都指向了同一个地址。所以改变其中一个,另一个也会随之变化。

浅拷贝对比赋值

我还是采用代码来说话吧。(从此代码不是劝退,而是爱!)

var chef1 = {
    name: '王刚',
    age: 28,
    skills: ['炒竹鼠', '麻辣龙虾', '炒鸡蛋']
}

var chef2 = chef1

var chef3 = shallowCopy(chef1)

function shallowCopy(obj) {
    var temp = {};
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            temp[prop] = obj[prop]
        }
    }
    return temp
}

chef2.age = 100
chef3.name = '王婆'

chef2.skills[0] = '打篮球'
chef3.skills[1] = '踢足球'

chef1 = {
    name: '王刚',
    age: 100,
    skills: ['打篮球', '踢足球', '炒鸡蛋']
}

chef2 = {
    name: '王刚',
    age: 100,
    skills: ['打篮球', '踢足球', '炒鸡蛋']
}

chef3 = {
    name: '王婆',
    age: 28,
    skills: ['打篮球', '踢足球', '炒鸡蛋']
}

如果大家一路看下来,相比看出一些端倪了。
让我们理一下思路:chef1(原数据), chef2(赋值数据), chef3(浅拷贝数据)

  • 赋值数据(chef2)和原数据(chef1)因为指向同意地址,所以数据始终保持一致。
  • 浅拷贝得到的数据(chef3),name和age是受自己控制,但是层级更深的skills却和原数据保持一致。
  • 结论:浅拷贝之所以叫浅拷贝是因为只能拷贝浅层的数据,更深层的数据依然指向原数据。

深拷贝

有了前面的铺垫,我相信大家应该清楚深浅拷贝了。不知道的我就嘤嘤嘤~给你看
顾名思义,深拷贝会将原数据完全拷贝,脱离原数组的控制。
没错,我们将用递归。
你真是太聪明了。

双手奉上代码

//深拷贝函数
function deepCopy(p) {
	var obj
	var str = getType(p)
	if(str == 'array') {
		obj = []
		for (var i = 0; i < p.length; i++) {
			obj.push(arguments.callee(p[i]))  //回调自己
		}
	}else if(str == 'object') {
		obj = {}
		for(var i in p) {
			obj[i] = arguments.callee(p[i])
		}
	}else {
		return p
	}
	return obj
}

// 判断变量的类型
function getType(obj) {
	var str = Object.prototype.toString.call(obj)
	var map = {
		'[object Boolean]'  : 'boolean', 
		'[object Number]'   : 'number', 
		'[object String]'   : 'string', 
		'[object Function]' : 'function', 
		'[object Array]'    : 'array', 
		'[object Date]'     : 'date', 
		'[object RegExp]'   : 'regExp', 
		'[object Undefined]': 'undefined',
		'[object Null]'     : 'null', 
		'[object Object]'   : 'object'
	}
	if(obj instanceof Element) { //判断是否是dom元素,如div等
		return "element"
	}
	return map[str]
}

我们下期再见~~~