欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于JavaScript深拷贝的一些理解

程序员文章站 2024-03-23 08:23:49
...

JavaScript 中的深拷贝

什么是深拷贝?

JavaScript中存在三种可执行代码,global,function,eval。可执行代码的执行依赖于执行上下文。我们可以抽象的将执行上下文理解为一个对象。这个对象上会包含一些属性,如variable object(变量对象),this value(this指针),scope chain(作用域链)。

JavaScript中的基础数据类型都是存储在变量对象中。JavaScript*有5种基础数据类型,Undefined、Null、Boolean、Number、String。我们可以直接操作存储在变量对象中的,基础数据类型。

JavaScript中的引用数据类型的值,是保存在堆内存中的。我们不能直接操作堆内存空间。我们通过操作variable object中的数据的引用,访问修改对象。这里的应用是堆内存空间中的地址。

 

关于JavaScript深拷贝的一些理解

 

 

当我们使用 var a = b赋值的时候,如果b是引用类型,我们赋于a的只是内存空间的地址。

 

关于JavaScript深拷贝的一些理解

 

 

这就是产生了一个问题,我们该如何复制引用类型的对象。

常见的深拷贝的方式

JSON.stringify 和 JSON.parse

最简单的方式将对象使用JSON.stringify将对象序列化为JSON格式的字符串,然后使用JSON.parse将JSON字符串反序列化为对象。

 

关于JavaScript深拷贝的一些理解

 

 

for in 配合 递归

另外一种方式是使用for in循环,配合递归

 

关于JavaScript深拷贝的一些理解

 

 

深拷贝中遇到的问题

到这里,问题解决了吗?并没有,上面方式有以下的问题

  1. JSON以及for…in无法对Date,正则,Set,Map等数据结构做出正确的处理
  2. 没有对Symbol的属性做出正确的处理。
  3. for…in没有对对象中可能存在的循环引用做出正确的处理。

如何解决这些问题?

对于上面问题的解决, 我参考了lodash的源码,以及ramda的源码。lodash的源码中涉及到非常多的边界条件的处理,可读性相对差一些。我在此基础上做出了一定的简化,具体代码在文末。

Date的的处理

当我们检测到value的为[object Date]类型的时候,我们通过Date实例的constructor属性,获取实例的构造函数。Date的构造函数可以接收一个Date的实例作为参数。从而创建一个新的Date实例。

 

关于JavaScript深拷贝的一些理解

 

 

Map,Set的处理

和Date对象同理,使用Map或者Set实例的constructor属性,创建新的实例后,通过forEach对Map和Set进行遍历,重新设置Map以及Set的内容。

 

关于JavaScript深拷贝的一些理解

 

 

 

关于JavaScript深拷贝的一些理解

 

 

正则的处理

  • source,正则对象的源模式文本
  • global, global属性表明正则表达式是否使用了"g"标志
  • ignoreCase, ignoreCase属性表明正则表达式是否使用了"i"标志
  • multiline, multiline属性表明正则表达式是否使用了"m"标志
  • sticky, sticky属性表明正则表达式是否使用了"y"标志
  • unicode, unicode属性表明正则表达式带有"u"标

 

关于JavaScript深拷贝的一些理解

 

 

循环引用的处理

当出现循环引用的时候,如果不进行判断,使用递归。会爆栈

 

关于JavaScript深拷贝的一些理解

 

 

WeakMap支持使用引用类型作为key,我们可以利用这个特性,将对象的每一个引用类型的属性作为key,存储在WeakMap中。当同样的key再次出现时,可以证明发生了循环引用。直接返回结果。

 

关于JavaScript深拷贝的一些理解

 

 

 

关于JavaScript深拷贝的一些理解

 

 

Symbol属性的处理

Symbols类型的属性无法使用Object.keys获取,可以通过Object.getOwnPropertySymbols方法获取对象上的Symbol类型的属性。

关于JavaScript深拷贝的一些理解

源码:


// 使用WeakMap判断是否形成了环,避免爆栈
const hash = new WeakMap()

function hasHash (value) {
  return hash.has(value)
}

function setHash (value, result) {
  hash.set(value, result)
}

function getHash (value) {
  return hash.get(value)
}

function getSymbols (value) {
  let symKeys = []
  const propertyIsEnumerable = Object.prototype.propertyIsEnumerable
  const nativeGetSymbols = Object.getOwnPropertySymbols
  symKeys = nativeGetSymbols(value)
  // 判断是否是可枚举的属性
  symKeys = symKeys.filter(symkey => propertyIsEnumerable.call(value, symkey))
  return symKeys
}

function getAllKeys (value) {
  let keys = Object.keys(value)
  keys = [...keys, ...getSymbols(value)]
  return keys
}

const types = {
  '[object Array]': true,
  '[object Boolean]': true,
  '[object Date]': true,
  '[object Map]': true,
  '[object Set]': true,
  '[object Number]': true,
  '[object Object]': true,
  '[object RegExp]': true,
  '[object Symbol]': true,
  '[object String]': true,
  '[object ArrayBuffer]': true,
  '[object Function]': true,
  '[object WeakMap]': false,
  '[object Error]': false
}

function isObject (value) {
  const type = typeof value;
  return value != null && (type === 'object' || type === 'function')
}

function getType (value) {
  return Object.prototype.toString.call(value);
}

function initCloneArray (value) {
  const { length } = value
  return new value.constructor(length)
}

function initCloneArrayBuffer (value) {
  const result = new value.constructor(value.byteLength)
  new Uint8Array(result).set(new Uint8Array(value))
  return result
}

function initCloneObject (value) {
  return Object.create(Object.getPrototypeOf(value))
}

function initCloneRegExp (value) {
  return new RegExp(value.source,
    (value.global     ? 'g' : '') +
    (value.ignoreCase ? 'i' : '') +
    (value.multiline  ? 'm' : '') +
    (value.sticky     ? 'y' : '') +
    (value.unicode    ? 'u' : '')
  )
}

function initCloneFunction (value) {
  return function (...args) {
    return value.apply(null, ...args)
  }
}

function initClone (value, type) {
  const Ctor = value.constructor
  switch (type) {
    case '[object ArrayBuffer]':
      return initCloneArrayBuffer(value)
    case '[object Date]':
    case '[object Map]':
    case '[object Set]':
      return new Ctor(value)
    case '[object RegExp]':
      return initCloneRegExp(value)
    case '[object Array]':
      return initCloneArray(value)
    case '[object Object]':
      return initCloneObject(value)
    case '[object Function]':
      return initCloneFunction(value)
  }
}

export default function deepClone (value) {

  let result;

  const type = getType(value)

  // 如果不是引用类型直接返回
  if (!isObject(value)) {
    return value
  }

  // 如果是weakMap,Error类型直接返回
  if (!types[type]) {
    return value
  }

  let isArr = Array.isArray(value)

  result = initClone(value, type)

  // 判断是否产生了循环引用, 避免爆栈
  if (hasHash(value)) {
    // 如果存在直接返回
    return getHash(value)
  }
  // 在weakMap添加标记
  setHash(value, result)

  if (type === '[object Map]') {
    value.forEach((val, key) => {
      result.set(key, deepClone(val))
    })
    return result
  }

  if (type === '[object Set]') {
    value.forEach((val, key) => {
      result.add(deepClone(val))
    })
    return result
  }

  const props = getAllKeys(value)

  for (let i = 0; i < props.length; i++) {
    const key = props[i]
    const val = value[key]
    result[key] = deepClone(val)
  }

  return result
}

 

相关标签: y javascript

上一篇: Java-通过URL来调用WebAPI

下一篇: