Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

手写深拷贝 #9

Open
Jsmond2016 opened this issue Jul 10, 2021 · 0 comments
Open

手写深拷贝 #9

Jsmond2016 opened this issue Jul 10, 2021 · 0 comments
Labels
手写系列 各种手写原理

Comments

@Jsmond2016
Copy link
Owner

Jsmond2016 commented Jul 10, 2021

手写深浅拷贝

浅拷贝

  • 使用 Object.assign(target, source)
let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
  • 使用展开运算符 ...
let a = {
    age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1

深拷贝

  • 使用 JSON.stringify(obj) / JSON.parse(str), 具体原理参考 JSON-MDN
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

但是该方法也是有局限性:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

以下代码会报错:

let obj = {
    a: 1,
    b: {
        c: 2,
        d: 3,
    },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)

这里包含循环引用对象,无法实现深拷贝。

  • 若对象内置类型不包含函数,可以使用 MessageChanel
function structuralClone(obj) {
    return new Promise(resolve => {
        const { port1, port2 } = new MessageChannel()
        port2.onmessage = ev => resolve(ev.data)
        port1.postMessage(obj)
    })
}
var obj = {
    a: 1,
    b: {
        c: 2
    }
}
obj.b.d = obj.b
// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
    const clone = await structuralClone(obj)
    console.log(clone)
}
test()
  • 完整版本
    • 考虑基本数据类型
    • 考虑复杂对象类型
      • 数组
      • 对象
      • 内置对象:Date, RegExp

此时代码为:

function clone (obj) {
    if (obj == null) return obj
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return new RegExp(obj)

    // 可能是对象或者普通的值,若为函数,则不需要深拷贝
    if (typeof obj !== 'object') return obj

    let cloneObj = new obj.constructor
    for (let k in obj) {
        if (obj.hasOwnProperty(k)) {
            cloneObj[k] = obj[k]
        }
    }
    return cloneObj
}

let obj = { name: 1, date: new Date(), reg: new RegExp("\\w+"), address: {x: 1000} }
let d = clone(obj)
console.log('obj', obj)
console.log('d', d)
console.log('obj === d', obj === d)
  • 处理循环引用
    • 使用 WeakMap
function deepClone(obj, hash = new WeakMap()) {
    if (obj == null) return obj
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return new RegExp(obj)
    
    // 可能是对象或者普通的值,若为函数,则不需要深拷贝
    if (typeof obj !== 'object') return obj
    // 若为对象,则进行深拷贝
    if (hash.get(obj)) return hash.get(obj)
    // [], {} , Object.prototype.toString.call(obj) === '[Object Array]' ? [] : {}
    let cloneObj = new obj.constructor
    hash.set(obj, cloneObj)

    for (let k in obj) {
        if (obj.hasOwnProperty(k)) {
            // 递归拷贝
            cloneObj[k] = deepClone(obj[k], hash)
        }
    }
    return cloneObj
}

let obj = {name: 1, address: {x: 100}}
obj.o = obj

let d = deepClone(obj)
obj.address.x = 300
console.log('d', d)
console.log('obj', obj)
console.log('obj === d', obj === d)
// 若对象复杂一些,循环引用

其他方式

const v8 = require('v8')

 const list = [1,2,3, {name: 'hey'}, [1,2,3]]
 let list2 = v8.deserialize(v8.serialize(list));

 list2[3].name = 'hi~'
 list2[4].shift()
 console.log('list', list)
 console.log('list2', list2)

// list [ 1, 2, 3, { name: 'hey' }, [ 1, 2, 3 ] ]
// list2 [ 1, 2, 3, { name: 'hi~' }, [ 2, 3 ] ]

参考资料:

@Jsmond2016 Jsmond2016 added the 手写系列 各种手写原理 label Jul 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
手写系列 各种手写原理
Projects
None yet
Development

No branches or pull requests

1 participant