JS对象的拷贝

Posted 初心不负

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS对象的拷贝相关的知识,希望对你有一定的参考价值。

1:对数据进行备份的时候,如果这个数据是基本的数据类型,那么很好办,通过赋值实现复制即可。

浅拷贝
let obj1 = {
    name:‘pan‘
}

let obj2 = obj1

obj2.name = ‘rui‘

console.log(obj1)  // { name: rui}
console.log(obj1 === obj2) //true

 

深拷贝方法1:JSON.parse(JSON.stringify(obj))
let obj1 = {
    name:‘pan‘
}

let obj2 = JSON.parse(JSON.stringify(obj1))

obj2.name = ‘rui‘
console.log(obj1)  //{name:pan}

console.log(obj2)    //{name:rui}
console.log(obj1 === obj2) //false
//缺点:对象必须遵从JSON的格式
let obj1 = {
  a: ‘1‘,
  b: ‘2‘,
  c: function func() {}
}

let obj4 = JSON.parse(JSON.stringify(obj1))

console.log(obj4) //{ a: ‘1‘, b: ‘2‘ } 好像漏了些什么

 

深拷贝方法2:Object.assign(target, …sources)
//Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
// 复制
let c = Object.assign({}, { a: ‘apple‘ });
console.log(c); // { a: ‘apple‘ }

//合并
let o = {}
let c = Object.assign(o, { a: ‘apple‘ }, { b: ‘banana‘ }, { c: ‘cake‘ } )
console.log(c) // { a: ‘apple‘, b: ‘banana‘, c: ‘cake‘ }

//如果对象本身存在的属性会更新,不存在的属性会增加
let o = {
  name:‘pan‘
}
let oo = {
  name:‘rui‘,
  id:100
}

let c = Object.assign(o, oo);
console.log(o);    //{name:‘rui‘,id:100}
console.log(oo);//{name:‘rui‘,id:100}
console.log(c);//{name:‘rui‘,id:100}

 

// 判断是否为对象
function isObject(o) {
    return (typeof o === ‘object‘ || typeof o === ‘function‘) && o !== null
}
//测试用例需要的对象
let test = {
    num: 0,
    str: ‘‘,
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: ‘我是一个对象‘,
        id: 1
    },
    arr: [0, 1, 2],
    func: function() {
        console.log(‘我是一个函数‘)
    },
    date: new Date(0),
    reg: new RegExp(‘/我是一个正则/ig‘),
    err: new Error(‘我是一个错误‘)
}
深度拷贝方法3:迭代递归法 for...in
// 迭代递归法:深拷贝对象与数组
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error(‘obj 不是一个对象!‘)
    }
    //判断传进来的是对象还是数组
    let isArray = Array.isArray(obj)  
    let cloneObj = isArray ? [] : {}
    //通过for...in来拷贝
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    }

    return cloneObj
}
let result = deepClone(test)

console.log(result)

for (let key in result) {
    if (isObject(result[key]))
        console.log(`${key}相同吗? `, result[key] === test[key])
}
//我们发现,arr 和 obj 都深拷贝成功了,它们的内存引用已经不同了,但 func、date、reg 和 err 并没有复制成功,因为它们有特殊的构造函数。
深度拷贝方法4:迭代递归法 Reflect 
// 代理法
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error(‘obj 不是一个对象!‘)
    }

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [...obj] : { ...obj }
    Reflect.ownKeys(cloneObj).forEach(key => {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    })

    return cloneObj
}

let result = deepClone(test)

console.log(result)

for (let key in result) {
    if (isObject(result[key]))
        console.log(`${key}相同吗? `, result[key] === test[key])
}
//结果其实和使用for...in相同。

2 lodash 中的深拷贝  https://github.com/lodash/lodash/blob/master/.internal/baseClone.js

  let result = _.cloneDeep(test)

  我们发现,arr、obj、date、reg深拷贝成功了,但 func 和 err 内存引用仍然不变 //这跟 lodash 中的 cloneableTags 有关

3lodash自定义拷贝对象

function customizer(value) {
  if (_.isElement(value)) {
    return value.cloneNode(true);
  }
}

var el = _.cloneDeepWith(document.body, customizer);
 
console.log(el === document.body);
// => false
console.log(el.nodeName);
// => ‘BODY‘
console.log(el.childNodes.length);
// => 20

4小姐姐,在了解一下

  4.1:对象成环,当我们使用上述方法(for...in 与Reflect都会出现栈溢出的错误,但是lodash却可以)

    注意:因为 lodash 使用的是栈把对象存储起来了,如果有环对象,就会从栈里检测到,从而直接返回结果,悬崖勒马。这种算法思想来源于 html5 规范定义的结构化克隆算法,它同时也解释了为什么 lodash 不对 Error 和 Function 类型进行拷贝。

  4.2:键不是字符串而是 Symbol

    使用for...in拷贝的时候就会拷贝失败,因为 Symbol 是一种特殊的数据类型,它最大的特点便是独一无二,所以它的深拷贝就是浅拷贝

改造for...in
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error(‘obj 不是一个对象!‘)
    }

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [] : {}
    let symKeys = Object.getOwnPropertySymbols(obj)
    // console.log(symKey)
    if (symKeys.length > 0) {
        symKeys.forEach(symKey => {
            cloneObj[symKey] =  isObject(obj[symKey]) ? deepClone(obj[symKey]) : obj[symKey]
        })
    }
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    }

    return cloneObj
}

    使用Reflect是可以的

  4.3 for...in会拷贝原型上面的属性,对于下面代码,只有for..in能够成功,而其他的方法都不能追踪到原型上面

let childTest = Object.create(test)
let result = deepClone(childTest)

    4.4 对象上的属性又分为可枚举属性和不可枚举属性,如何拷贝不可枚举的属性呢?

//定义对象
Object.defineProperties(test, {
    ‘obj‘: {
        writable: false,
        enumerable: false,
        configurable: false
    },
    ‘arr‘: {
        get() {
            console.log(‘调用了get‘)
            return [1,2,3]
        },
        set(val) {
            console.log(‘调用了set‘)
        }
    }
})

function deepClone(obj, hash = new WeakMap()) {
    if (!isObject(obj)) {
        return obj
    }
    // 查表,防止循环拷贝
    if (hash.has(obj)) return hash.get(obj)

    let isArray = Array.isArray(obj)
    // 初始化拷贝对象
    let cloneObj = isArray ? [] : {}
    // 哈希表设值
    hash.set(obj, cloneObj)
    // 获取源对象所有属性描述符
    let allDesc = Object.getOwnPropertyDescriptors(obj)
    // 获取源对象所有的 Symbol 类型键
    let symKeys = Object.getOwnPropertySymbols(obj)
    // 拷贝 Symbol 类型键对应的属性
    if (symKeys.length > 0) {
        symKeys.forEach(symKey => {
            cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey], hash) : obj[symKey]
        })
    }

    // 拷贝不可枚举属性,因为 allDesc 的 value 是浅拷贝,所以要放在前面
    cloneObj = Object.create(
        Object.getPrototypeOf(cloneObj),
        allDesc
    )
    // 拷贝可枚举属性(包括原型链上的)
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key];
    }

    return cloneObj
}

    总结:微笑中透露着贫穷

  

 

以上是关于JS对象的拷贝的主要内容,如果未能解决你的问题,请参考以下文章

js对象拷贝

js对象浅拷贝和深拷贝详解

JS中如何进行对象的深拷贝

两行代码搞定js对象深浅拷贝

js对象深拷贝

jquery怎样深拷贝一个数组