Bootstrap

JS之深拷贝与浅拷贝

一、理解

 深拷贝 与 浅拷贝

 针对于 引用类型(Object,Array,Function) 来说的

浅拷贝:在栈中分配一块新内存,拷贝需要拷贝的值,

          对简单数据类型,就是拷贝值;对复杂数据类型,就是拷贝了一份栈内存储的堆内存的地址

 深拷贝:在堆中重新分配新内存,存储新的拷贝数据;

                在栈中分配新内存,存储新数据分配的堆的地址。

标准浅拷贝:

// =
let obj1 = obj
obj.a = '88888'
console.log(obj)
console.log( obj1)

结果:

实现方式:

 浅拷贝的实现方式:

        1. 基于Object.assign()

        2. 基于for in = 直接赋值

        3. ... 原地展开

 深拷贝的三种实现方式:

      1. 递归实现(重点)

      2. JSON.stringify + JSON.parse 方法

      3. JQuery的$extend方法(了解)

 其他奇怪的api方法?可能非真正的深拷贝

     1. slice 一维数组深拷贝,多维拷贝不彻底(针对数组)

     2. Object.assign

     3. Object.create

二、一些奇怪的api的使用及测试

1.Object.assign()

Object.assign用法:

  用于将源对象的所有可枚举属性到目标对象,返回目标对象

 至少需要两个对象做参数:Object.assign(目标对象, 源对象1,...)

 只要有一个参数不是对象,就会抛出TypeError错误

 如果有同名属性,后覆盖前;只复制自身属性(也会复制Symbol值的属性),不复制不可枚举/继承属性

 可用于: 为对象添加属性或方法;克隆对象;合并多个对象;为属性指定默认值

拷贝实现:

// Object.assign 
let obj = {
  a: 1,
  b: 2,
  c: [1, 2, 3],
  d: {
    g:777,
    s: {},
    f: function() { console.log('f')}
  }
}
let obj1 = Object.assign({}, obj)
console.log(obj1)

拷贝结果:

 拷贝测试:

对第一层的测试:

obj1.a = 666 // 对第一层的基本数据类型修改=>深拷贝
obj1.c = [] // 对第一层的引用数据类型 => 深拷贝
// obj1.c[0] = '000' // 对第一层的引用数据类型 内的某一项进行修改=>浅拷贝
//obj1.d.g = 8888 //
obj1.d.s.w = '9999999' // 对内层引用数据类型进行修改(添加)=>浅拷贝
console.log(obj)
console.log( obj1)

测试结果:

 其他测试:

obj1.c[0] = '000' // 对第一层的引用数据类型 内的某一项进行修改=>浅拷贝
obj1.d.g = 8888 // 对内层基础数据类型修改 => 浅拷贝
obj1.d.s.w = '9999999' // 对内层引用数据类型某一项 进行修改(添加)=>浅拷贝
obj1.d.f = {} // 对内层引用数据类型 整体修改 => 浅拷贝
console.log(obj)
console.log( obj1)

测试结果:

小总结:

 对拷贝的第一层的基础数据类型 实现了深拷贝(修改拷贝值,不影响新值);

 对深层数据,依然是浅拷贝,修改数据值会影响原数据

注意:当整体修改第一层引用数据后,新对象的整个引用数据的指向应该是修改后的,这时再对引用数据中某一项修改时,不会影响原来的对象;但直接对于第一层引用数据类型的某一项进行修改时,还是会影响原来的对象。

2.Object.create

api用法:

 方法用于 创建一个新对象,使用现有的对象来提供新创建的对象的proto

 参数: Object.create(proto新建对象的原型对象,[添加到新创建对象的可枚举属性])

 返回值:在指定原型对象上添加新属性后的对象

拷贝:

// 3. Object.create 浅拷贝
let obj1 = Object.create(obj)
console.log(obj1) // obj1 是一个空对象,其原型为obj;obj1.__proto__是obj的浅拷贝
console.log(obj1.__proto__ === obj) // true

运行结果:

浅拷贝,新对象的原型为旧对象

obj1 是一个空对象,其原型为obj;obj1.__proto__是obj的浅拷贝

3. ES6 ...扩展运算符

代码实现:

// ES6 扩展运算符
let obj1 = { ...obj }
console.log(obj1)

测试结果同Object.assign

4.slice+concat实现拷贝(针对数组)

slice:

实现及测试:

// 针对数组
let arr = ['a', 'b', 'c', {d:4}]

// slice
let arr1 = arr.slice(0)
// console.log(arr)
// console.log(arr1)

arr1[0] = '1'
arr1[3].d = '777'
console.log(arr)
console.log(arr1)

运行结果:

 可见,slice 对于数组中的基础数据类型,能够实现深拷贝,对于引用数据类型,无法实现深拷贝。

concat:

// concat
let arr1 = arr.concat()

// console.log(arr)
// console.log(arr1)

测试结果与slice相同。

三、实现深拷贝的三种方式

1. JSON.stringify + JSON.parse

拷贝实现:

let obj = {
  a: 1,
  b: 2,
  c: [1, 2, 3],
  d: {
    g:777,
    s: {w:'www'},
    f: function() { console.log('f')}
  },
  e: undefined,
  p: NaN,
  date:new Date()
}
// JSON.stringify + JSON.parse 方法
let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj)
console.log(obj1)

结果截图:

可见,拷贝对象无函数f、无undefined属性e,即该方法无法实现对函数、对undefined值的拷贝 ;

经测试,为深拷贝。

该方法存在的问题:

1.对象中有时间类型时,序列化后会变成字符串类型;

2.对象中有function、undefined类型的数据,会直接丢失;

3.对象中有NaN、Infinity、-Infinity时,序列化后会显示null;

4.对象循环引用时,会报错。

2.JQuery中的$.extend方法可以实现深拷贝(知道)

3.递归实现深拷贝(待升级完善版)

深拷贝的简单递归实现:

let obj = {
  a: 1,
  b: 2,
  c: [1, 2, 3],
  d: {
    g:777,
    s: {w:'www'},
    f: function() { console.log('f')}
  },
  e: undefined,
  p: NaN,
  date:new Date()
}

// 递归实现深拷贝--简单版()
function deepClone(obj) {
  let newObj = null
  // if (obj === null) return obj//???
  if (typeof obj == 'undefined') return undefined
  if (typeof obj === 'function') return obj//浅拷贝函数?
  if (obj.constructor == Date) return new Date(obj)
  if(obj.constructor == RegExp) return new RegExp(obj)
  
  if (typeof obj == 'object') {
    newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        newObj[key] = deepClone(obj[key])
      }
    }
  } else {
    newObj = obj
  }
  return newObj
}

let obj1 = deepClone(obj)
console.log(obj)
console.log(obj1)

运行截图:

复杂版:

// 深拷贝 复杂版?
function checkType(target) {
  console.log(Object.prototype.toString.call(target).slice(8,-1))
  return Object.prototype.toString.call(target).slice(8,-1)
}

function deepClone2(data) {
  const obj = checkType(data) === 'Array' ? [] : {}
  let arr = ['Object','Array']
  if (arr.includes(checkType(data))) {
    for (let key in data) {
      let value = data[key]
      //value为简单类型,直接赋值
      if (!arr.includes(checkType(value))) {
        obj[key] = value
      } else {
        // 定义一个映射,初始化时,将data本身加入映射中
        const map = new WeakMap()
        // 如果拷贝的是复杂数据类型第一次拷贝后存入map
        // 第二次再遇到该值时,直接赋值为null,结束递归
        map.set(data, true)
        if (map.has(value)) {
          obj[key] = null
        } else {
          map.set(value, true);
          obj[key] = deepClone2(value)
        }
      }
    }
  } else {
    return data
  }
  return obj
}

let obj1 = deepClone2(obj)
console.log(obj)
console.log(obj1)

结果:

 

;