9

JS 深拷贝函数封装

ES5深拷贝函数封装

简述JavaScript深拷贝与浅拷贝

假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

前置知识:JavaScript基本数据类型与复杂(引用)数据类型。

var obj = {
  name: 'oiov',
  age: 18,
  info: {
    hobby: ['travel', 'piano', {
      a: 1
    }],
    career: {
      teacher: 4,
      engineer: 9
    }
  }
};
 
function deepClone(origin, target) {
  var tar = target || {}
  var toStr = Object.prototype.toString
  var arrType = '[object Array]'
 
  for (var k in origin) {
    if (origin.hasOwnProperty(k)) { // 判断是否为org自己的属性
      if (typeof origin[k] === 'object' && origin[k] !== null) {
        tar[k] = toStr.call(origin[k]) === arrType ? [] : {} // 判断是数组还是对象
        deepClone(origin[k], tar[k])
      } else {
        // 非object类型则直接赋值
        tar[k] = origin[k]
      }
    }
  }
  return tar
}
 
const newObj = deepClone(obj, {})
newObj.info.hobby[2].a = 123
console.log(obj, newObj);
 
// var type = Object.prototype.toString.call([]) // [object Array]
// var type = Object.prototype.toString.call({}) // [object Object]
// console.log(type);
 
// console.log(typeof obj['info']); // ''

ES6深拷贝函数封装

前置:WeakMap数据类型

Map与WeakMap区别:

  • Map的键名可以是任意类型、[])
  • WeakMap的键名只能是对象

例:从垃圾回收来感受WeakMap

const oBtn1 = document.querySelector('#btn1')
const oBtn2 = document.querySelector('#btn1')
 
oBtn1.addEventListener('click', handleClick1, false)
oBtn2.addEventListener('click', handleClick2, false)
 
function handleClick1() {}
function handleClick2() {}
 
oBtn1.remove() // 删除节点,但是不能回收事件回调函数
oBtn2.remove()
 
// 必须使用这种方法删除回调
handleClick1 = null;
handleClick2 = null

使用WeakMap:

const oBtn1 = document.querySelector('#btn1')
const oBtn2 = document.querySelector('#btn1')
 
const oBtnMap = new WeakMap()
 
// 将节点与回调绑定在一起,垃圾回收时一起被回收(键名键值一起回收)
// 键名是弱引用
oBtnMap.set(oBtn1, handleClick1) 
oBtnMap.set(oBtn2, handleClick2)
 
oBtn1.addEventListener('click', handleClick1, false)
oBtn2.addEventListener('click', handleClick2, false)
 
function handleClick1() {}
function handleClick2() {}
 
// oBtn1.remove() // 删除节点,但是不能回收事件回调函数
//oBtn2.remove()

换一个角度看,WeakMap的键名必须是对象,弱引用才有意义。

深拷贝函数封装

拷贝之前,需要先判断原始值(origin)的数据类型再进行下一步操作。

function当做静态数据类型,不做特殊处理。

1)关于null和undefined的判断

// null undefined
var a = undefined;
console.log(a == null); // true
console.log(a === null); // false

使用 origin === undefined 表达式就能判断origin是否为null和undefined值。

2)判断Date、RegExp类型数据

if (origin instanceof Date) {
  return new Date(origin);
}
if (origin instanceof RegExp) {
  return new RegExp(origin);
}

3)判断 或 []:

使用构造器

const obj = {}
console.log(obj)

打印结果:

可以看到,字面量obj是通过其原型上的constructor使用new Object构造出来的,那么是否可以自己手动构造一个obj呢?

const obj = {}
const newObj = new obj.constructor()
console.log(obj) // {}
newObj.a = 1
console.log(newObj); // {a:1}

同理,数组:

const arr = []
const newArr = new arr.constructor()
console.log(arr);
newArr.push(1)
console.log(newArr);

知道这点,直接使用 const target = new origin.constructor() 生成一个新 或 [] 即可,不用再判断了。

封装函数1

function deepClone(origin) {
  if (origin === undefined || typeof origin !== 'object') { // 排除null、undefined、非object值
    return origin;
  }
  if (origin instanceof Date) {
    return new Date(origin);
  }
  if (origin instanceof RegExp) {
    return new RegExp(origin);
  }
 
  // [] -> [], {} -> {}, 不需要判断是对象还是数组
  const target = new origin.constructor()
 
  for (let k in origin) {
    if (origin.hasOwnProperty(k)) {
      target[k] = deepClone(origin[k])
    }
  }
 
  return target;
}

测试1

var obj = {
  name: 'oiov',
  age: 18,
  info: {
    hobby: ['travel', 'piano', {
      a: 1
    }],
    career: {
      teacher: 4,
      engineer: 9
    }
  }
};
 
const newObj = deepClone(obj)
newObj.info.hobby[2].a = 1234
console.log(newObj);

结果:

测试2

let test1 = {}
let test2 = {}
test2.test1 = test1
test1.test2 = test2
console.log(deepClone(test2));
 
报错:
Uncaught RangeError: Maximum call stack size exceeded

原因:test1和test2分别作为属性值赋值给对方,在拷贝时,会发生重复拷贝,简而言之,就是缺少记录是否拷贝。

解决:使用 hashMap 哈希表方式记录键名是否存在,通过 WeakMap 实现。

封装函数2

function deepClone(origin, hashMap = new WeakMap()) {
  if (origin === undefined || typeof origin !== 'object') { // 排除null、undefined、非object值
    return origin;
  }
  if (origin instanceof Date) {
    return new Date(origin);
  }
  if (origin instanceof RegExp) {
    return new RegExp(origin);
  }
 
  const hashKey = hashMap.get(origin); // 寻找键是否存在
  if (hashKey) {
    return hashKey
  }
 
  // [] -> [], {} -> {}, 不需要判断是对象还是数组
  const target = new origin.constructor()
  hashMap.set(origin, target)
  for (let k in origin) {
    if (origin.hasOwnProperty(k)) {
      target[k] = deepClone(origin[k], hashMap)
    }
  }
 
  return target;
}

测试3

let test1 = {}
let test2 = {}
test2.test1 = test1
test1.test2 = test2
console.log(deepClone(test2));

结果: