JavaScript中的深拷贝
Posted LiuJun2Son
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript中的深拷贝相关的知识,希望对你有一定的参考价值。
1.什么是深拷贝
浅拷贝只是解决了第一层的拷贝问题,拷贝第一层的***基本类型值***,以及第一层的***引用类型地址***,并没有递归拷贝第二层以后的属性。
深拷贝会拷贝所有的属性,拷贝的属性指向动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。深拷贝可以解决多层拷贝的问题。
2.深拷贝的使用
1.JSON.parse(JSON.stringify(object))
案例1:根据原始A对象浅拷贝一个新的B对象出来
// 1.JSON.parse(JSON.stringify(object)) 深拷贝
let A =
name:'liujun',
age:25,
job:
name:'web',
year:4
,
lan:[
'java',
'js'
]
console.log(A) // name: 'liujun', age: 25, job: name: 'web', year: 4 , lan: [ 'java', 'js' ]
// 1.1 根据原始A对象深拷贝一个新的B对象出来
let B = JSON.parse(JSON.stringify(A))
console.log(B) // name: 'liujun', age: 25, job: name: 'web', year: 4 , lan: [ 'java', 'js' ]
// 1.2 修改A对象3个属性。发现B对象并没有影响。拷贝前后两个对象互不影响。
A.name = '刘军'
A.job.name = '前端开发'
A.lan[1] = 'javascript'
console.log('--------------------------------')
console.log(A) // name: '刘军', age: 25, job: name: '前端开发', year: 4 , lan: [ 'java', 'JavaScript' ]
console.log(B) // name: 'liujun', age: 25, job: name: 'web', year: 4 , lan: [ 'java', 'js' ]
总结:JSON.parse(JSON.stringify(object)) 深拷贝会拷贝所有的属性,拷贝的属性指向动态分配的内存。拷贝前后两个对象互不影响。
案例2:根据原始 A浅拷贝一个新的B对象出来
// 1.JSON.parse(JSON.stringify(object)) 深拷贝
let A =
name:'liujun', // 1.拷贝成功
value: undefined, // 3.没有拷贝
default: null, // 1.拷贝成功
symbol:Symbol('liujun'), // 3.没有拷贝
show:function() // 3.没有拷贝
console.log('show')
,
date:new Date(), // 2.拷贝失败,变成了字符串
reg:new RegExp(/1[3|4|5|6|7|8]\\d9/) // 2.拷贝失败,变成了
console.log(A)
/**
name: 'liujun',
value: undefined,
default: null,
symbol: Symbol(liujun),
show: [Function: show],
date: 2019-09-29T01:32:32.911Z,
reg: /1[3|4|5|6|7|8]\\d9/
*/
// 1.1 根据原始A对象深拷贝一个新的B对象出来
let B = JSON.parse(JSON.stringify(A))
console.log(B)
/**
name: 'liujun',
default: null,
date: '2019-09-29T01:32:32.911Z',
reg:
*/
console.log(typeof A.date) // object:还是Date的对象
console.log(typeof B.date) // string:不是Date的对象,不能调用Date对象的方法
总结:JSON.parse(JSON.stringify(object)) 深拷贝会拷贝部分的属性,拷贝的属性指向动态分配的内存。拷贝前后两个对象互不影响。
存在的问题:
-
Date 和 RegExp
类型虽然拷贝了,但是拷贝还是失败,类型发生了改变。Date 类型变成了字符串,RegExp变成了, 在使用它拷贝的时候一定要注意 -
undefined、null、Symbol、function
压根就没有拷贝, 在使用它拷贝的时候一定要注意。 -
对象的循环引用时的拷贝会报错, 例如下面 c 指向了 b 的引用,属于循环引用:
let obj = a: 1, b: c: 2, d: 3 obj.a = obj.b; obj.b.c = obj.a; let b = JSON.parse(JSON.stringify(obj)); // Uncaught TypeError: Converting circular structure to JSON
2.编写深拷贝函数
1.编写浅拷贝
function deepCopy(object)
var target =
for(var key in object)
// 1.判断是否是自身的属性。object借用hasOwnProperty(key)函数,传递key参数
if(Object.prototype.hasOwnProperty.call(object, key))
target[key] = object[key]
return target
2.编写深拷贝函数
function deepCopy(object)
var target =
for(var key in object)
// 1.判断是否是自身的属性。object借用hasOwnProperty(key)函数,传递key参数
if(Object.prototype.hasOwnProperty.call(object, key))
// 2.如果object[key]是对象或者是数组需要递归遍历
if(typeof object[key] === 'object')
target[key] = deepCopy(object[key]) // 3.深拷贝比浅拷贝就是多了这里
else
target[key] = object[key]
return target
3.优化深拷贝函数
function deepCopy(object)
// typeof null // "object"
// typeof // "object"
// typeof [] // "object"
// typeof new Date() // "object"
// typeof new RegExp() // "object"
// typeof function foo() // "function"
if(typeof object === 'object' && object !=null)
var target = Array.isArray(object) ? [] :
for(var key in object)
// 1.判断是否是自身的属性。object借用hasOwnProperty(key)函数,传递key参数
if(Object.prototype.hasOwnProperty.call(object, key))
// 2.如果object[key]是对象或者是数组需要递归遍历,例如:, [], new Date(), new RegExp()
if(typeof object[key] === 'object')
target[key] = deepCopy(object[key]) // 3.深拷贝比浅拷贝就是多了这里
else
target[key] = object[key]
return target
else
// 传入 `undefined、null、Symbol、function` ` 时应返回 `undefined、null、Symbol、function` `
return object
1.对传入参数进行校验,传入
null
时应该返回null
而不是, 例如:
if(typeof object === 'object' && object !=null)
2.考虑数组的兼容,例如:
var target = Array.isArray(object) ? [] :
3.测试深拷贝函数
// 1.deepCopy(object) 深拷贝
let A =
name:'liujun', // 1.拷贝成功
value: undefined, // 1.拷贝成功
default: null, // 1.拷贝成功
symbol:Symbol('liujun'), // 1.拷贝成功
show:function() // 1.拷贝成功
console.log('show')
,
date:new Date(), // 2.拷贝失败,变成了
reg:new RegExp(/1[3|4|5|6|7|8]\\d9/) // 2.拷贝失败,变成了
console.log(A)
/**
name: 'liujun',
value: undefined,
default: null,
symbol: Symbol(liujun),
show: [Function: show],
date: 2019-09-29T01:32:32.911Z,
reg: /1[3|4|5|6|7|8]\\d9/
*/
// 1.1 根据原始A对象深拷贝一个新的B对象出来
let B = deepCopy(A)
console.log(B)
/**
name: 'liujun',
value: undefined,
default: null,
symbol: Symbol(liujun),
show: [Function: show],
date: ,
reg:
*/
console.log(typeof A.date) // object:还是Date的对象
console.log(typeof B.date) // object:不是Date的对象,不能调用Date对象的方法
存在的问题:
- Date 和 RegExp` 类型虽然拷贝了,但是拷贝还是失败,类型发生了改变。Date 类型变成了,RegExp变成了。为什么会是 ?是递归函数返回的
undefined、null、Symbol、function
属于直接拷贝, 直接走了 deepCopy 的 else 代码块返回原始数据。- 对象的循环引用时的拷贝会报错
3.完整深度拷贝函数
// 1.获取到数据源的数据类型
function _getDataType(data)
return Object.prototype.toString.call(data).slice(8, -1)
// 2.负责拷贝 RegExp 对象
function copyRegExp(regExp)
let attrs = ''
if (regExp.global) attrs += 'g'
if (regExp.ignoreCase) attrs += 'i'
if (regExp.multiline) attrs += 'm'
const newRegExp = new RegExp(regExp, attrs)
newRegExp.lastIndex = regExp.lastIndex
return newRegExp
// 3.深克隆,考虑到(1.类型检查,2.递归爆栈,3.相同引用,4.Date和Function等特殊类型克隆,原型克隆)
function deepClone(x)
// 3.1 String Number Boolean Undefined Null 返回自身
if (x == null || typeof x !== 'object') return x
// 3.2 RegExp Date Function 克隆
const type = _getDataType(x)
let root
switch (type)
case 'RegExp':
// 3.3克隆正则
return copyRegExp(x)
case 'Date':
// 3.4 克隆Date类型
return new Date(x.getTime())
case 'Function':
// 3.5 克隆函数类型
return x
case 'Array':
root = []
break
default:
root = Object.create(Object.getPrototypeOf(x))
// 3.6 Array Object 类型的克隆
// 用来去重 解决原数据中多个属性引用同一对象克隆后不相同问题( 防止循环引用问题 )
const uniqueList = []
// 使用栈结构解决递归爆栈问题
const stack = [
parent: root,
key: undefined,
data: x
]
// 深度优先循环(防止递归爆栈)
while (stack.length)
const parent, key, data = stack.pop()
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent
if (typeof key !== 'undefined')
const type = _getDataType(data)
switch (type)
case 'RegExp':
parent[key] = copyRegExp(data)
continue
case 'Date':
parent[key] = new Date(data.getTime())
continue
case 'Function':
parent[key] = data
continue
case 'Array':
res = parent[key] = []
break
default:
const proto = Object.getPrototypeOf(data)
res = parent[key] = Object.create(proto)
// 数据引用已经存在则赋值并退出本次循环,不存在则缓存
const uniqueData = uniqueList.find(item => item.source === data)
if (uniqueData)
parent[key] = uniqueData.target
continue
else
uniqueList.push(
source: data,
target: res
)
for (const k in data)
if (data.hasOwnProperty(k))
if (data[k] == null || typeof data[k] !== 'object')
// 基础类型克隆
const descriptor = Object.getOwnPropertyDescriptor(data, k)
Object.defineProperty(res, k, descriptor)
else
// 引用类型加入stack循环处理
stack.push(
parent: res,
key: k,
data: data[k]
)
return root
4.其它深拷贝函数
1.jQuery.extend()
案例1:根据原始 A浅拷贝一个新的B对象出来
var A = [1,2,3,4];
var B = $.extend(true,[],A);
2. lodash.cloneDeep()
案例1:根据原始 A浅拷贝一个新的B对象出来
var A = [ 'a': 1 , 'b': 2 ];
var B = lodash.cloneDeep(A); // _.cloneDeep()
以上是关于JavaScript中的深拷贝的主要内容,如果未能解决你的问题,请参考以下文章