数组的浅拷贝
concat()、slice()、Array.from()方法返回一个新数组的特性来实现拷贝。如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。
var arr = [1, {a: 'a'}];
var arr2 = arr.concat();
var arr3 = arr.slice();
var arr4 = Array.from(arr);
arr[1].a = 'b';
console.log(arr2);// [1, {a: 'b'}]
console.log(arr3);// [1, {a: 'b'}]
console.log(arr4);// [1, {a: 'b'}]
Object.assign()方法可以实现浅拷贝,不只针对数组,也可以用在对象上
var arr = [1, {a: 'a'}];
var arr5 = Object.assign([], arr);
arr[1].a = 'b';
console.log(arr5);// [1, {a: 'b'}]
JSON.stringify深拷贝
然而使用这种方法会有一些隐藏的坑,它能正确处理的对象只有 Number, String, Boolean, Array,平面对象,即那些能够被json直接表示的数据结构。
1、如果里面有时间对象,结果时间将只是字符串的形式。而不是时间对象;
var date = JSON.parse(JSON.stringify( new Date()));
//"2019-10-08T14:11:47.842Z"
var obj = JSON.parse(JSON.stringify({date: new Date()}));
//{date: "2019-10-08T14:11:47.842Z"}
var arr = JSON.parse(JSON.stringify([new Date()]));
//["2019-10-08T14:11:47.842Z"]
2、如果里面有RegExp、Error对象,则序列化的结果将只得到空对象;
var re = JSON.parse(JSON.stringify({re: new RegExp()}));
//{re: {}}
var error = JSON.parse(JSON.stringify({error: new Error()}));
//{error: {}}
3、如果里面有函数,undefined,则序列化的结果会把函数或 undefined丢失
var fun = JSON.parse(JSON.stringify({fun: function(){}}));
//{}
var un = JSON.parse(JSON.stringify({un: undefined}));
//{}
4、如果里面有NaN、Infinity和-Infinity,则序列化的结果会变成null
var nan = JSON.parse(JSON.stringify({nan: NaN}));
//{nan: null}
var infinity = JSON.parse(JSON.stringify({in: Infinity}));
//{in: null}
5、只能序列化对象的可枚举的自有属性,例如:如果obj中的对象是有构造函数生成的,则使用stringify深拷贝后,会丢弃对象的constructor;
function Person(name) {
this.name = name;
console.log(name)
}
const liai = new Person('liai');
const test = {
name: 'a',
date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)

6、如果对象中存在循环引用的情况也无法正确实现深拷贝;
var a = {name : b};
var b = {name : a}
var c = JSON.parse(JSON.stringify({a, b}));
console.log(c);

自定义深拷贝,单项数据
const toString = Object.prototype.toString;
const MAX_SAFE_INTEGER = 9007199254740991;
function getTag(value){
if(value == null){
return value === 'undefined' ? '[object Undefined]' : '[object Null]';
}
return toString.call(value);
}
function isLength(value){
return value === 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
function isArrayLike(value){
return value != null && typeof value !== 'function' && isLength(value.length)
}
function isArray(value){
return (Array.isArray && Array.isArray(value)) || getTag(value) === '[object Array]';
}
function isObjectLike(value){
//object(不包括null),不包括function
return typeof value === 'object' && value !== null;
}
function isObject(value){
//object(不包括null) + function
let type = typeof value
return value !== null && (type === 'object' || type === 'function');
}
function isPlainObject(value){
if(getTag(value) != '[object Object]'){
return false;
}
if(Object.getPrototypeOf(value) === null){
return true;
}
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
}
function isFunction(value){
if(!isObject(value)){
return false;
}
const tag = getTag(value);
return (tag == '[object Function]'
|| tag == '[object AsyncFunction]'
|| tag == '[object GeneratorFunction]'
|| tag == '[object Proxy]');
}
function deepClone(value) {
let type = getTag(value);
let object;
if (type === '[object Date]') {
return new Date(value);
} else if(type === '[object RegExp]'){
let flags = '';
flags += value.global ? 'g' : '';
flags += value.ignoreCase ? 'i' : '';
flags += value.multiline ? 'm' : '';
return new RegExp(value.source, flags);
} else if (isArray(value)) {
object = [];
for (let i = 0, len = value.length; i < len; i++) {
object.push(deepClone(value[i]));
}
} else if (isObject(value)) {
object = {};
// 对原型上的方法也拷贝了....
for (const key in value) {
object[key] = deepClone(value[key]);
}
} else {
return value;
}
return object;
}
注意:
支持基础类型数据、数组、平板对象、Date、RegExp的深拷贝
不支持函数、Error等对象的深拷贝
自定义深浅拷贝,参数可以为多项数据
function extend() {
let options,
src,
copy,
copyIsArray,
clone,
deep = false,
target = arguments[0] || {},
i = 1;
if (typeof target == 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
}
if (typeof target !== 'object' && !isFunction(target)) {
target = {};
}
for (let len = arguments.length; i < len; i++) {
options = arguments[i];
if (options == null) {//可以省掉
continue;
}
for (let name in options) {
src = target[name];
copy = options[name];
//防止循环引用
if (target === copy) {
continue;
}
//要递归的数据必须是对象或数组
if (deep && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {//如果待复制数据是数组,目标属性不是数组,就将目标属性设置为数组
clone = src && isArray(src) ? src : [];
} else {//如果待复制数据是对象,目标属性不是对象,就将目标属性设置为对象
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
return target;
}
注意的情况:
解决循环引用的情况
对 Date, RegExp, 函数的深拷贝(不支持)
参考: