关于javascript:如何实现深浅拷贝

31次阅读

共计 3892 个字符,预计需要花费 10 分钟才能阅读完成。

本文 github 地址,欢送 star

在平时开发中,个别解决两种数据类型:

  • 根本数据类型
  • 援用数据类型

根本数据类型存在栈中,援用数据类型寄存在堆中。

在谈到深浅拷贝时,也是围绕这两种数据类型开展的。

浅拷贝

创立一个对象,从新复制或援用的源对象的值。

  • 如果对象属性是根本的数据类型,复制的就是根本类型的值给新对象。
  • 如果属性是援用数据类型,拷贝进去的指标对象的指针和源对象的指针指向的内存空间是同一块空间, 批改源对象同时也会对指标对象产生影响。

JS 提供了如下办法来对象和数据进行浅拷贝。

开展运算符

能够在函数调用 / 数组结构时, 将数组表达式或者 string 在语法层面开展;还能够在结构字面量对象时, 将对象表达式按 key-value 的形式开展。

// 数据的拷贝
let arr = [1, 2, 3, 4, {a: 1}]
let arrCopy = [...arr]
arrCopy[4].a = 100
console.log(arrCopy[4].a) // 100
console.log(arr[4].a) // 100

// 对象的拷贝
let obj = {
    a: 1,
    b: {c: 3}
}
console.log(obj.b.c) // 3
let objCopy = {...obj}
objCopy.b.c = 4
console.log(obj.b.c) // 4
console.log(objCopy.b.c) // 4

不难发现数据类型都是根本类型,应用开展运算符拷贝十分不便。

Object.assign

Object.assign 办法用于将所有可枚举属性的值从一个或多个源对象调配到指标对象。它将返回指标对象。

let t = {}
let s = {
    a: {b: 100}
}
Object.assign(t, s)
console.log(s.a.b) // 100
t.a.b = 200
console.log(s.a.b) // 200
console.log(t.a.b) // 200

须要留神的几个中央

  • 不会拷贝对象的继承属性
  • 不会拷贝对象的不可枚举的属性
  • 能够拷贝 Symbol 类型的属性

Object.assign 会遍历原对象的属性,通过复制的形式将其赋值给指标对象的相应属性(包含 Symbol 类型的对象)

手写实现浅拷贝

对于根本类型间接进行拷贝复制,对于援用类型,在内存中开拓一块新的空间进行拷贝复制。

const isObject = (obj) => typeof obj === 'object' && obj !== null;

const shallowClone = (obj) => {if (!is(obj)) return obj
    const cloneObj = Array.isArray(obj) ? [] : {};
    for (let prop in obj) {if (obj.hasOwnProperty(prop)) {cloneObj[prop] = obj[prop];
        }
    }
    return cloneObj;
}

深拷贝

将一个对象从内存中残缺地拷贝进去一份给指标对象,并从堆内存中开拓一个全新的空间寄存新对象,且新对象的批改并不会扭转原对象,二者实现真正的拆散。

JSON.stringfy

JSON.stringfy 是前端中最简略的深拷贝形式,把对象序列化成为 JSON 的字符串,并将对象外面的内容转换成字符串,再用 JSON.parseJSON 字符串生成一个新的对象。

let obj1 = {
    a: 1,
    b: [1, 2, 3]
}
let str = JSON.stringify(obj1);let obj2 = JSON.parse(str);console.log(obj2); //{a:1,b:[1,2,3]} 
obj1.a = 2;obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}

然而 JSON.stringfy 在深拷贝会呈现以下的问题:

  • 拷贝的对象的值中如果有函数、undefinedsymbol 这几种类型,通过 JSON.stringify 序列化之后的字符串中这个键值对会隐没。
  • 拷贝 Date 援用类型会变成字符串。
  • 无奈拷贝不可枚举的属性。
  • 无奈拷贝对象的原型链。
  • 拷贝 RegExp 援用类型会变成空对象。
  • 对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的后果会变成 null
  • 无奈拷贝对象的循环利用。
  let testObj = {[Symbol('test')]: 1,
      number: -1,
      string: 'Hello World',
      boolean: true,
      undefined: undefined,
      nul: null,
      obj: {
          name: '我是一个对象',
          id: 1
      },
      list: [11, 22, 33],
      fn: function() {console.log('Hello World')
      },
      date: new Date(),
      regExp: new RegExp('/[\w]/ig'),
  };
  Object.defineProperty(testObj, 'innumerable', {
      enumerable: false,
      value: 'innumerable'
  });
  obtestObjj = Object.create(testObj, Object.getOwnPropertyDescriptors(testObj))
  // 设置循环援用, 不能拷贝,将会报错
  // testObj.loop = testObj
  let cloneObj = JSON.parse(JSON.stringify(testObj))
  cloneObj.list.shift()
  console.log('testObj', testObj)
  console.log('cloneObj', cloneObj)

手写深拷贝

对于上述 JSON.stringfy 毛病,在实现深拷贝进行如下的改善:

  • 当对象为 DateRegExp 类型,间接返回一个新的实例。
  • 对象的不可枚举和 symbol 属性,采纳 Reflect.ownKeysReflect.ownKeys办法用于返回对象的所有属性,根本等同于 Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。
  • 无奈拷贝对象的原型链对象,能够 Object.getPrototypeOf 获取对象的原型对象,以及对象的属性形容对象Object.getOwnPropertyDescriptors
  • 对于循环援用,能够采纳WeakMap, WeakMap 是弱援用类型,能够无效避免内存透露。
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function(obj, hash = new WeakMap()) {
    // 解决 RegExp 对象
    if (obj.constructor === RegExp) return new RegExp(obj)
    // 解决 Date 对象
    if (obj.constructor === Date) return new Date(obj)
    // 解决循环援用
    if (hash.has(obj)) return hash.get(obj)
    // 获取属性的形容器
    let allDesc = Object.getOwnPropertyDescriptors(obj)
    // 创立新的对象,设置原型链
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
    // 解决循环援用
    hash.set(obj, cloneObj)
    // Reflect.ownKeys 能够获取不可枚举和 symbol 属性
    for (let key of Reflect.ownKeys(obj)) {cloneObj[key] = (isComplexDataType(obj[key]) ? deepClone(obj[key], hash) : obj[key]
        }
        return cloneObj
    }
}

接下来深拷贝测试

// 测试 对象
let testObj = {[Symbol('test')]: 1,
    number: -1,
    string: 'Hello World',
    boolean: true,
    undefined: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1
    },
    list: [11, 22, 33],
    fn: function() {console.log('Hello World')
    },
    date: new Date(),
    regExp: new RegExp('/[\w]/ig'),
};
Object.defineProperty(testObj, 'innumerable', {
    enumerable: false,
    value: 'innumerable'
});
obtestObjj = Object.create(testObj, Object.getOwnPropertyDescriptors(testObj))
// 设置循环援用
testObj.loop = testObj
let cloneObj = deepClone(testObj)
cloneObj.list.shift()
console.log('cloneObj', testObj)
console.log('cloneObj', cloneObj)

能够看出解决了 JSON.stringfy 在进行深拷贝的时候的毛病。

总结

在平时开发过程,咱们能够采纳第三方库来实现深拷贝,比方 loadsh.cloneDeep

正文完
 0