/**
浅拷贝
浅拷贝的意思就是只复制引用,而未复制真正的值。
const originArray = [1,2,3,4,5]
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}
const cloneArray = originArray
const cloneObj = originObj
console.log(cloneArray)
console.log(originObj)
cloneArray.push(6)
cloneObj.a = {aa:'aa'}
console.log(cloneArray)
console.log(originArray)
console.log(cloneObj)
console.log(originArray)
上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 cloneArray 和 cloneObj 改变,originArray 和 originObj 也随着发生了变化。
深拷贝
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
目前实现深拷贝的方法不多,主要是两种:
利用 JSON 对象中的 parse 和 stringify
利用递归来实现每一层都重新创建对象并赋值
// 1. JSON.stringify/parse的方法
JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串
JSON.parse 是将一个 JSON 字符串转成一个 JavaScript 值或对象
它能实现深拷贝呢?我们来试试
const originArray = [1,2,3,4,5]
const cloneArray = JSON.parse(JSON.stringify(originArray))
console.log(cloneArray === originArray)
const originObj = { a: 'a', b: 'b', c: [1,2,3], d: {dd: 'dd'} }
const cloneObj = JSON.parse(JSON.stringify(originObj))
console.log(cloneObj === originObj) // false
cloneObj.a = 'aa'
cloneObj.c = [1,1,1]
cloneObj.d.dd = 'ddedd'
console.log(cloneObj) // {a:'aa',b:'b',c:[1,1,1],d:{dd:'ddedd'}}
console.log(originObj) // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}
确实是深拷贝,也很方便。但是,这个方法只能适用于一些简单的情况。比如下面这样的一个对象就不适用:
const originObj = {
name: 'renzhen',
sayHello: function() {
console.log('hello world')
}
}
console.log(originObj) // { name: 'renzhen', sayHello: f}
const cloneObj = JSON.parse(JSON.stringify(originObj))
console.log(cloneObj) // { name: 'renzhen' }
发现在 cloneObj 中,有属性丢失了。。。那是为什么呢?
在 MDN 上找到了原因:
undefined、function、symbol 会在转换过程中被忽略。。。
明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。
3.递归的方法
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {} // 判断复制的目标是对象还是数组
for (let keys in source) { // 遍历目标
if(source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
// 如果值是对象就递归下
targetObj[keys] = source[keys].constructor === Array ? [] : {}
targetObj[keys] = deepClone(source[keys])
} else {
// 如果不是 就直接赋值
targetObj[keys] = source[keys]
}
}
}
return targetObj
}
// 我们来试试:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}
const cloneObj = deepClone(originObj)
console.log(cloneObj === originObj)
cloneObj.a = 'aa'
cloneObj.c = [1,1,1]
cloneObj.d.dd = 'doubled'
console.log(cloneObj)
console.log(originObj)
// 可以。那再试试带有函数的:
const originObj = {
name:'axuebin',
sayHello:function(){
console.log('Hello World')
}
}
console.log(originObj)
const cloneObj = deepClone(originObj)
console.log(cloneObj)
// 4. JavaScript中的拷贝方法
我们知道在 JavaScript 中,数组有两个方法 concat 和 slice 是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。
同时,ES6 中 引入了 Object.assgn 方法和 ... 展开运算符也能实现对对象的拷贝。
结论:concat 只是对数组的第一层进行深拷贝。
结论:slice 只是对数组的第一层进行深拷贝。
结论:... 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
Object.assgn 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
*/
/**
// ok 第一趴只是开胃小菜,只能是初级的解决问题的方法我们来看木易杨老哥的讲解
*/
// 第一步当然还是写一个简单的浅拷贝
let a = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
},
a1: undefined,
a2: null,
a3: 123,
a4: [1, 2, 3],
a5: [2, [2,3]]
}
function cloneShallow (source) {
let target = {}
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
}
return target
}
// 以上是浅拷贝的简单实现当然还有直接赋值等等方法,接下来我们实现简单的深拷贝
function cloneDeep1 (source) {
let target = {}
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === 'object') {
target[key] = cloneDeep1(source[key])
} else {
target[key] = source[key]
}
}
}
return target
}
// tips 为什么用Object.prototype.hasOwnProperty.call,因为对象并不保护hasOwnProperty这属性,所以他可能会被改变 而hasOwnProperty.call是用的真正的Object 方法,
// 这还是简单的深拷贝的实现但是还有以下几个问题
// 1. 没有对传入的值进行校验 比如 null,传入null应该返回null
// 2. 对象判断的逻辑不严谨 typeof null === 'object'
// 3. 没有考虑数组兼容
// 深拷贝的第二步 拷贝数组
// 之前介绍的对象判断
// function isObject(obj) {
// return Object.prototype.toString.call(obj) === '[object Object]'
// }
// 在这里并不合适,因为我们要保留数组这种情况,所以这里使用 typeof 来处理。
// typeof null //"object"
// typeof {} //"object"
// typeof [] //"object"
// typeof function foo(){} //"function" (特殊情况)
// 改动后的isObject逻辑如下
function isObject(obj) {
return typeof obj === 'object' && obj != null
}
// 所以兼容数组的写法如下
function cloneDeep2 (source) {
if (!isObject(source)) return source // 不是对象返回自身
let target = Array.isArray(source) ? [] : {}
for (let key in source) { // 对数组也适用
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep2(source[key])
} else {
target[key] = source[key]
}
}
}
return target
}
// 测试下
// let b = cloneDeep2(a)
// console.log(b)
// a1: undefined
// a2: null
// a3: 123
// a4: Array(3)
// a5: (2) [2, Array(2)]
// book: {title: "You Don't Know JS", price: "45"}
// name: "muyiy"
// 写到这里觉得OK了但是还有问题没有解决 那就是循环引用、递归爆栈
// 好,接下来深拷贝第三步循环引用
// 解决循环引用的方法1使用哈希表循环检测,我们设置一个数组或者哈希表存储已拷贝过的对象,当检测到当前对象已存在于哈希表中时,取出该值并返回即可。
function cloneDeep3 (source, hash = new WeakMap()) {
if (!isObject(source)) return source
if (hash.has(source)) return hash.get(source) // 查哈希表有没有
let target = Array.isArray(source) ? [] : {}
hash.set(source, target)
console.log(hash, 'assadassa')
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], hash) // 传入哈希表
} else {
target[key] = source[key]
}
}
}
return target
}
// 上边使用了WeakMap处理接下来我们用数组处理
function find(arr, item) {
for (let i =0
if (arr[i].source === item) {
return arr[i]
}
}
return null
}
function cloneDeep32(source, uniqueList) {
if (!isObject(source)) return source
if (!uniqueList) uniqueList = [] // 初始化数组
let target = Array.isArray(source) ? [] : {}
// 数据已经存在返回保存的数据
let uniqueData = find(uniqueList, source)
if (uniqueData) {
return uniqueData.target
}
// 如果之前数据不存在,保留原数据以及对应引用
uniqueList.push({
source: source,
target: target
})
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source.key)) {
target[key] = deepClone32(source[key], uniqueList)
} else {
target[key] = source[key]
}
}
}
// var obj1 = {}
// var obj2 = {a: obj1, b: obj1}
// obj2.a === obj2.b
// // true
// var obj3 = cloneDeep2(obj2)
// obj3.a === obj3.b
// 引用丢失在某些情况下是有问题的,比如上面的对象 obj2,obj2 的键值 a 和 b 同时引用了同一个对象 obj1,使用 cloneDeep2 进行深拷贝后就丢失了引用关系变成了两个不同的对象,那如何处理呢。
// 接下来进入拷贝的第四步 递归爆栈
function cloneDeep5 (x) {
const root = {}
// 栈
const loopList= [
{
parent: root,
key: undefined,
data: x
}
]
while (loopList.length) {
// 广度优先
const node = loopList.pop()
const parent = loopList.parent
const key = node.key
const data = node.data
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent
if (typeof key !== 'undefined') {
res = parent[key] = {}
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k]
})
} else {
res[k] = data[k]
}
}
}
}
return root
}