深浅拷贝
赋值、深拷贝和浅拷贝
赋值:
-
基本类型赋值:赋值后两个变量互不影响
-
引用类型赋值:赋值后两个变量具有相同的引用,指向同一个对象,互相影响。
var a = 1;
var b = a;
var obja = {}
var objb = obja;
浅拷贝:
浅拷贝基本类型和赋值一样,浅拷贝引用类型的时候创建了一个新的对象,是对原始对象属性值的一份拷贝。如果属性是基本类型就拷贝类型值,如果是引用类型就拷贝地址值。
var obja = {
a: 1
b: {}
}
//objb浅拷贝obja
深拷贝:
深拷贝会拷贝所有的属性,并且拷贝属性指向的动态分配的内存。
var obja = {
a: 1
b: {
name: '1'
}
}
//objb深拷贝obja
JSON
JSON对象中有两个方法,parse()解析JSON,stringify()将对象转换成JSON。
const sym = Symbol('sym');
let obj = {
name: 'names',
age: 18,
un: undefined,
nu: null,
fn: () => {
console.log('fn');
},
[sym]: 'sym'
}
let obj2 = obj;
let obj3 = JSON.parse(JSON.stringify(obj));
console.log('obj', obj, 'obj2', obj2, 'obj3', obj3);
我们通过图片观察到新对象中的un、fn、Symbol(‘sym’)属性丢失。所以不管JSON是不是深拷贝他的缺点就是不会对undefined、Symbol、function拷贝。
然后我们来验证JSON是深拷贝还是浅拷贝。
const sym = Symbol('sym');
let obj = {
name: 'names',
age: 18,
un: undefined,
nu: null,
fn: () => {
console.log('fn');
},
[sym]: 'sym'
}
obj.age = 19;
let obj2 = obj;
let obj3 = JSON.parse(JSON.stringify(obj));
console.log('obj', obj, 'obj2', obj2, 'obj3', obj3);
我们加了一行obj.age = 19
;
我们可以看到,原对象和赋值的对象中的age属性都发生了改变,而拷贝的obj3中的age属性没有发生改变,那么我们可以说JSON是深拷贝吗?显然不行,因为浅拷贝是对对象中的属性进行拷贝,如果属性是基本类型就拷贝类型值,如果是引用类型就拷贝地址值。我们应该给obj添加一个obj属性,值是{age: 18}
。
const sym = Symbol('sym');
let obj = {
name: 'names',
age: 18,
un: undefined,
nu: null,
fn: () => {
console.log('fn');
},
[sym]: 'sym',
obj: {
age: 18
}
}
let obj2 = obj;
let obj3 = JSON.parse(JSON.stringify(obj));
obj.obj.age = 19;
console.log('obj', obj, 'obj2', obj2, 'obj3', obj3);
我们通过图片可以看到当原对象obj.obj.age
参数改变后,obj3中的obj.obj.age
没有发生改变,这就说明JSON是深拷贝。
Array.concat()
concat()方法用于合并两个或多个数组,此方法不会更改现数组,而是返回一个新数组。
let arr = [1, '2', NaN, undefined, null, (function () { console.log('fn') }),{
age: 18
}];
let arr2 = arr;
let arr3 = [].concat(arr);
arr[6].age = 19;
console.log('arr',arr,'arr2',arr2,'arr3',arr3);
我们可以看到,当arr[6].age = 19
后,arr3[6].age
也跟着改变了,所以说,Array.concat()是浅拷贝。
展开运算符
const sym = Symbol('sym');
let obj = {
name: 'names',
age: 18,
un: undefined,
nu: null,
fn: () => {
console.log('fn');
},
[sym]: 'sym',
obj: {
age: 18
}
}
let obj2 = obj;
let obj3 = {...obj};
obj.obj.age = 19;
console.log('obj', obj, 'obj2', obj2, 'obj3', obj3);
我们通过图片可以看到当原对象obj.obj.age
参数改变后,obj3中的obj.obj.age
发生改变,这就说明扩展运算符是浅拷贝。
Object.assign()
Object.assign() 方法将所有可枚举的自有属性从一个或多个源对象复制到目标对象,返回修改后的对象。
const sym = Symbol('sym');
let obj = {
name: 'names',
age: 18,
un: undefined,
nu: null,
fn: () => {
console.log('fn');
},
[sym]: 'sym',
obj: {
age: 18
}
}
let obj2 = obj;
let obj3 = Object.assign({},obj);
obj.obj.age = 19;
console.log('obj', obj, 'obj2', obj2, 'obj3', obj3);
我们通过图片可以看到当原对象obj.obj.age
参数改变后,obj3中的obj.obj.age
发生改变,这就说明Object.assign()是浅拷贝。
Array.slice()
slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
let arr = [1, '2', NaN, undefined, null, (function () { console.log('fn') }),{
age: 18
}];
let arr2 = arr;
let arr3 = arr.slice(0,arr.length);
arr[6].age = 19;
console.log('arr',arr,'arr2',arr2,'arr3',arr3);
我们可以看到,当arr[6].age = 19
后,arr3[6].age
也跟着改变了,所以说,Array.slice()是浅拷贝。
Lodash的_.cloneDeep方法
html
手写深拷贝
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//判断target是否是对象、数组、函数
const isObject = (target) => target !== null && (typeof target === 'object' || typeof target === 'function');
//判断target的具体类型
const getType = (target) => Object.prototype.toString.call(target);
const mapTag = '[object Map]'
const setTag = '[object Set]'
const boolTag = '[object Boolean]'
const stringTag = '[object String]'
const numberTag = '[object Number]'
const dateTag = '[object Date]'
const funTag = '[object Function]'
const regexp = '[object RegExp]'
const errorTag = '[object Error]'
const canTraverse = {
'[object Object]': true,
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Arguments]': true,
}
function handleNotTraverse(target, type) {
const ctor = target.constructor;
switch (type) {
case boolTag:
return new Boolean(Boolean.prototype.valueOf.call(target));
case numberTag:
return new Number(Number.prototype.valueOf.call(target));
case stringTag:
return new String(String.prototype.valueOf.call(target));
case dateTag:
case errorTag:
return new ctor(target);
case funTag:
return handleFun(target);
case regexp:
return handleRexExp(target);
default:
return new ctor(target);
}
}
function handleFun(target) {
//箭头函数直接返回自身
if (!target.prototype) return target;
target = target.toString();
const bodyReg = /(?<={)(.|\n)+(?=})/m
const paramReg = /(?<=\().+(?=\)(\s*)+{)/
const param = paramReg.exec(target);
const body = bodyReg.exec(target);
console.log('fun',target,'param',param);
if(!body)return null;
if(param){
const paramsArr = param[0].split(',');
return new Function(...paramsArr,body[0]);
}else{
return new Function(body[0]);
}
}
function handleRexExp(target) {
const { source, flags } = target;
return new target.constructor(source, flags);
}
//深拷贝
function deepClone(target, map = new WeakMap()) {
let cloneTarget = null;
//判断是否是引用类型
if (!isObject(target)) return target;
//获取更精确的类型
let type = getType(target);
if (!canTraverse[type]) {
//不可遍历
return handleNotTraverse(target, type);
} else {
//可遍历
let ctor = target.constructor;
cloneTarget = new ctor();
}
//判断是否循环引用
if (map.get(target)) return map.get(target);
map.set(target, cloneTarget);
if (type === mapTag) {
//处理Map
target.forEach((element, key) => {
cloneTarget.set(deepClone(key, map), deepClone(element, map));
});
}
if (type === setTag) {
//处理Set
target.forEach((element, key) => {
cloneTarget.add(deepClone(item, map));
})
}
//处理数组和对象
for (let props in target) {
if (target.hasOwnProperty(props)) {
cloneTarget[props] = deepClone(target[props], map);
}
}
return cloneTarget;
}
function fnc(a, b) {
console.log(a + b);
}
let obj = {
name: '1',
age: {
age: function fnc(a, b){
console.log(a+b);
}
}
};
let obj2 = deepClone(obj);
console.log('obj',obj,'obj2',obj2);
</script>
</body>
</html>