Javascript基本类型不涉及浅拷贝深拷贝的概念,都是存储在栈中的。除了基本类型数据,其他的都是引用类型,是保存在堆中的。栈只是存储了引用地址,指向堆内存中的地址。
image.png

浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

引用类型的 赋值不等于浅拷贝和 浅拷贝是一层数据的拷贝,深拷贝是多层的拷贝。https://segmentfault.com/q/1010000020437355

  1. const a = { name:'a' }
  2. const b = a //赋值是将某一数值或对象赋给某个变量的过程,这不是浅拷贝
  3. b.name = 'b' // 影响了原来的对象
  4. console.log(b.name) // b
  • 对象 ```javascript const ob2 = { …obj1 }

const obj2 = Object.assign({},obj1);

  1. - 数组
  2. ```javascript
  3. const arr2 = [...arr1]
  4. const arr2 = arr1.slice()
  5. const arr2 = arr1.concat()
  6. const arr2 = Array.from(arr1)

深拷贝

JSON.stringify

  1. let newObj = JSON.parse(JSON.stringify(obj));

缺点:

  • 1.如果json里面有时间对象,则序列化的结果为字符串的形式;
  • 2.如果json里有RegExpError对象,则序列化的结果将只得到空对象**{}**
  • 3.如果json里有 function, undefined,则序列化的结果会把 function,undefined 属性丢弃
  • 4.如果json里有NaNInfinity和-Infinity,则序列化的结果会变**null**
  • 5.如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;
  • 6.如果对象中存在循环引用的情况也无法实现深拷贝(报错
  • 参考:https://segmentfault.com/a/1190000020297508

基础版本

  1. function clone(source) {
  2. if (typeof source === 'object') {
  3. let target = Array.isArray(source) ? [] : {};
  4. for (const key in source) { // 数组也可以用in
  5. target[key] = clone(source[key]);
  6. }
  7. return target;
  8. }
  9. return source;
  10. };
  • 下一步要解决循环引用问题

    用map存一下,把当前对象当key,克隆对象当value

  1. function clone(source, map = new Map()) {
  2. if (typeof source === 'object') {
  3. if (map.get(source)) {
  4. return map.get(source); // map
  5. }
  6. let target = Array.isArray(source) ? [] : {};
  7. map.set(source, target);// map
  8. for (const key in source) {
  9. target[key] = clone(source[key], map);
  10. }
  11. return target;
  12. }
  13. return source;
  14. };

可以使用 WeakMap 存储,WeakMap的键是弱引用的,其键是对象。

for..in 循环遍历数组的效率过低,可以改用 while 循环

  1. function clone(source, map = new Map()) {
  2. if (typeof source === 'object') {
  3. if (map.get(source)) {
  4. return map.get(source);
  5. }
  6. let target = Array.isArray(source) ? [] : {};
  7. map.set(source, target);
  8. let keys = Object.keys(source);
  9. let length = keys.length;
  10. let index = 0;
  11. while (index < length) {
  12. target[keys[index]] = clone(source[keys[index]], map);
  13. index++;
  14. }
  15. return target;
  16. }
  17. return source;
  18. };
  1. // 仅遍历对象自身可枚举的属性(包括字符串属性和 Symbol 属性)
  2. Reflect.ownKeys(input).forEach(key => {
  3. if (input.propertyIsEnumerable(key)) {
  4. output[key] = copy(input[key])
  5. }
  6. })

处理其他数据类型

  1. // 可以继续遍历的类型
  2. const mapTag = '[object Map]';
  3. const setTag = '[object Set]';
  4. const arrayTag = '[object Array]';
  5. const objectTag = '[object Object]';
  6. const argsTag = '[object Arguments]';
  7. // 不可以继续遍历的类型
  8. //基本类型
  9. const boolTag = '[object Boolean]';
  10. const numberTag = '[object Number]';
  11. const stringTag = '[object String]';
  12. const symbolTag = '[object Symbol]'; // 特殊处理
  13. // 引用类型
  14. const dateTag = '[object Date]';
  15. const errorTag = '[object Error]';
  16. const regexpTag = '[object RegExp]'; // 特殊处理
  17. const funcTag = '[object Function]'; //特殊处理
  18. // 可以继续遍历的类型
  19. const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
  • 判断是否为对象

    1. function isObject(target) {
    2. const type = typeof target;
    3. return target !== null && (type === 'object' || type === 'function');
    4. }
  • 获取类型

    1. function getType(target) {
    2. return Object.prototype.toString.call(target);
    3. }

    ```javascript function getInit(target) { const Ctor = target.constructor; return new Ctor(); }

// Symbol function cloneSymbol(target) { // symbol 基本类型也要复制,为了保证唯一性 return Object(Symbol.prototype.valueOf.call(target)); }

// 正则 function cloneReg(target) { const flags = /\w$/; ///w代表匹配字母或数字或下划线,零次或多次 const result = new target.constructor(target.source, flags.exec(target)); result.lastIndex = target.lastIndex; return result; }

function cloneOtherType(target, type) { switch (type) {
case boolTag: case numberTag: case stringTag: // 可能为包装类,这三个类型也要处理 case errorTag: case dateTag: return new source.constructor(target); case regexpTag: return cloneReg(target); case symbolTag: return cloneSymbol(target); case funcTag: return cloneFunction(target); default: return null; } }

  1. ```javascript
  2. function clone(target, map = new WeakMap()) {
  3. // 基本类型
  4. if (!isObject(target)) {
  5. return target;
  6. }
  7. // 初始化
  8. const type = getType(target);
  9. let cloneTarget;
  10. if (deepTag.includes(type)) {
  11. cloneTarget = getInit(target, type);
  12. } else {
  13. return cloneOtherType(target, type);
  14. }
  15. // 防止循环引用
  16. if (map.get(target)) {
  17. return map.get(target);
  18. }
  19. map.set(target, cloneTarget);
  20. // 克隆set
  21. if (type === setTag) {
  22. target.forEach(value => {
  23. cloneTarget.add(clone(value, map));
  24. });
  25. return cloneTarget;
  26. }
  27. // 克隆map
  28. if (type === mapTag) {
  29. target.forEach((value, key) => {
  30. cloneTarget.set(key, clone(value, map));
  31. });
  32. return cloneTarget;
  33. }
  34. // 对象和数组
  35. const keys = Object.keys(target);
  36. let length = keys.length;
  37. let index = 0;
  38. while (index < length) {
  39. const key = keys[index]
  40. cloneTarget[key] = clone(target[key], map);
  41. index++;
  42. }
  43. return cloneTarget;
  44. };