一、基本类型 和 引用类型
1、ECMAScript 中的变量类型分为两类:
- 基本类型:undefined ,null , 布尔值(Boolean) , 字符串(String) , 数值(Number)
- 引用类型: 统称为Object类型,细分的话,有:Object类型,Array类型,Date类型,Function类型等。
2、不同类型的存储方式:
基本数据类型 保存在 栈内存,形式如下:栈内存中分别存储着变量的标识符以及变量的值。
引用类型 保存在 堆内存 中, 栈内存存储的是变量的标识符以及对象在堆内存中的存储地址,当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从对应的堆内存中取得所需的数据。
3、不同类型的复制方式:
基本类型 的复制:当你在复制基本类型的时候,相当于把值也一并复制给了新的变量。
引用类型 的复制:当你在复制引用类型的时候,实际上只是复制了指向堆内存的地址,即原来的变量与复制的新变量指向了同一个东西。
所谓 深浅拷贝:
01、对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝。
02、而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝。
深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例。
由深复制的定义来看,深复制要求如果源对象存在对象属性,那么需要进行递归复制,从而保证复制的对象与源对象完全隔离。然而还有一种可以说处在浅复制和深复制的粒度之间,也是jQuery的extend方法在deep参数为false时所谓的“浅复制”,这种复制只进行一个层级的复制:即如果源对象中存在对象属性,那么复制的对象上也会引用相同的对象。这不符合深复制的要求,但又比简单的复制引用的复制粒度有了加深。
一. 深拷贝:不是简单的拷贝引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深拷贝的对象的引用不包含任何原有对象
1. JSON对象的parse和stringify JSON.parse(JSON.stringify(xxx))
JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。
let bbb = [1,[2,3,4]]
let ccc = JSON.parse(JSON.stringify(bbb))
ccc[1][0] = 222
console.log(bbb,ccc) // [1,[2,3,4]] [1,[222,3,4]]
这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理
let a = [{
'aa': undefined,
'bb': [1, 2, 3],
'cc': null,
'dd': new Date(),
'ee': () => {}
}]
let b = JSON.parse(JSON.stringify(a))
console.log(a, b)
打印结果:
a的打印结果:
aa:undefined
bb:[1, 2, 3]
cc:null
dd:Wed Mar 13 2019 09:46:13 GMT+0800 (中国标准时间)
ee:ƒ ee()
b的打印结果:
bb:[1, 2, 3]
cc:null
dd:Wed Mar 13 2019 09:46:13 GMT+0800 (中国标准时间)
2. jQuery中的extend复制方法
jQuery中的extend方法可以用来扩展对象,这个方法可以传入一个参数:deep(true or false),表示是否执行深复制(如果是深复制则会执行递归复制)
这个方法基本的思路就是如果碰到array或者object的属性,那么就执行递归复制,这也导致对于Date,Function等引用类型,jQuery的extend也无法支持
let a=[0,1,[2,3],4],
b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b); // [1,1,[1,3],4] [0,1,[2,3],4]
3. 自定义深拷贝:封装一个深拷贝的函数
利用 递归 来实现深复制,对属性中所有引用类型的值,遍历到是基本类型的值为止。
// 深拷贝的实现
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {}
if (obj && typeof obj == 'object') {
for (key in obj) {
// for-in遍历的是原型链,需要用hasOwnProperty 判断是否是自有属性
if (obj.hasOwnProperty(key)) {
// 判断obj子元素是否为对象。如果是,则递归复制
if (obj[key] && obj[key] === 'object') {
objClone[key] = deepClone(obj[key])
} else {
// 如果不是,简单复制
objClone[key] = obj[key]
}
}
}
return objClone
} else {
//如果obj不是对象,那么直接返回值就可以了
return obj
}
}
console.log('使用自定义的深拷贝')
let arrA = [1, 2, 3]
let arrAcopy = deepClone(arrA)
arrAcopy[0] = 111
console.log(arrA, arrAcopy) // (3) [1, 2, 3] (3) [111, 2, 3]
let array2 = [1, [2, 3, 4]]
let array2Copy = deepClone(array2)
array2[0] = 111
console.log(array2, array2Copy) // [111,[2,3,4]] [1,[2,3,4]]
let stringA = 11
let aCopy = deepClone(stringA)
stringA = 22
console.log(stringA, aCopy) // 22 11
let stringB = null
let bCopy = deepClone(stringB)
console.log(bCopy) // null
二、浅拷贝:复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响
1. 直接赋值
let aa = [1,2,3]
let bb = aa
bb[1] = 22
console.log(aa,bb) // (3) [1, 22, 3] (3) [1, 22, 3]
2. ES6拓展运算符 [...list]
let arr = [1,2,3,4]
let list = [...arr]
list[0] = '111'
console.log(arr,list)
arr的打印结果:
[1, 2, 3, 4]
list的打印结果:
["111", 2, 3, 4]
ES6的拓展运算符也可以实现数据的拷贝,但是只遍历一层。属于浅拷贝
let arr = [{
name: '小草莓',
hobby: ['a', 'b', 'c']
}]
let list = [...arr]
list[0].hobby = ['a', 'b', 'c', 'd']
list[0].name = 'new 小草莓'
console.log(arr)
console.log(list)
arr的打印结果:
[{
name: 'new 小草莓',
hobby: ['a', 'b', 'c', 'd']
}]
list的打印结果
[{
name: 'new 小草莓',
hobby: ['a', 'b', 'c', 'd']
}]
3. js的slice()方法
rrayObj.slice(start, [end])
数组中的slice()方法返回指定截取的一段,原数组不改变。当start为0素,end不填则默认复制整个数组
let c = [1,2,3,4,5]
let d = c.slice(0)
d[2] = 333
console.log(c,d) // (5) [1, 2, 3, 4, 5] (5) [1, 2, 333, 4, 5]
Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用了,因此这两个方法并不是真正的深复制
let cc = [1,[2,3,4]]
let ee = cc.slice(0)
ee[1][0] = 222
console.log(cc,ee) // [1,[222,3,4]]
4. js的concat()方法
arrayObject.concat(arrayX,arrayX,......,arrayX)
数组中的concat()方法返回合并之后的数组,原数组不改变。
let e = [1,2,3,4]
let f = e.concat()
f[1] = 222
console.log(e,f) // (4) [1, 2, 3, 4] (4) [1, 222, 3, 4]
Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用了,因此这两个方法并不是真正的深复制
let g = [1,[2,3,4]]
let h = g.concat()
h[1][0] = 222
console.log(g,h)
5. Object.assign(target, …sources)
console.log('对象浅拷贝')
let arr = [1,2,3,4]
let obj1 = { a: 1, b: 2, c: 3 }
let obj2 = { a: 11, b: 22, c: 33, d:arr}
let obj3 = Object.assign({}, obj1, obj2)
obj1.a = 'aaa'
obj2.d[0] = 'bbb'
console.log(obj1, obj2, obj3)
// {a: "aaa", b: 2, c: 3}
// {a: 11, b: 22, c: 33, d: ['bb',2,3,4]}
// {a: 11, b: 22, c: 33, d: ['bb',2,3,4]}
自己实现一个拷贝方法,通过传入deep参数来表示是否执行深拷贝
//util作为判断变量具体类型的辅助模块
var util = (function() {
var class2type = {};
[('Null',
'Undefined',
'Number',
'Boolean',
'String',
'Object',
'Function',
'Array',
'RegExp',
'Date')
].forEach(function(item) {
class2type['[object ' + item + ']'] = item.toLowerCase()
})
function isType(obj, type) {
return getType(obj) === type
}
function getType(obj) {
return class2type[Object.prototype.toString.call(obj)] || 'object'
}
return {
isType: isType,
getType: getType
}
})()
function copy(obj, deep) {
//如果obj不是对象,那么直接返回值就可以了
if (obj === null || typeof obj !== 'object') {
return obj
}
//定义需要的局部变量,根据obj的类型来调整target的类型
var i,
target = util.isType(obj, 'array') ? [] : {},
value,
valueType
for (i in obj) {
value = obj[i]
valueType = util.getType(value)
//只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制
if (deep && (valueType === 'array' || valueType === 'object')) {
target[i] = copy(value)
} else {
target[i] = value
}
}
return target
}
更过例子可参考:https://blog.csdn.net/lyt_angularjs/article/details/86599820