JavaScript中的深拷贝与浅拷贝[每日3题(62)]

今天只有一道题,因为这个很重要,就不搞其他的了~

JavaScript:如何实现深拷贝与浅拷贝

浅拷贝:

slice
concat
let arr = ['old', 1, true, null, undefined];

// var new_arr = arr.slice()
let new_arr = arr.concat();

new_arr[0] = 'new';

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]
Object.assign
let obj = {
  name: 'andy',
  colors: ['red', 'blue']
}

let obj2 = Object.assign({}, obj);
let obj3 = {
  ...obj
};

obj.name = 'James';
obj.colors.push('black')
console.log(obj);// {name: "James",colors: (3) ["red", "blue", "black"]}
console.log(obj2);// {name: "andy",colors: (3) ["red", "blue", "black"]}
console.log(obj3);// {name: "andy",colors: (3) ["red", "blue", "black"]}

可以看出浅拷贝只最第一层属性进行了拷贝,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存地址。

深拷贝:

简单粗暴:JSON.parse(JSON.stringify(arr))

缺陷:

+ 对象的属性值是函数时,无法拷贝。
  • 原型链上的属性无法拷贝
  • 不能正确的处理 Date 类型的数据
  • 不能处理 RegExp
  • 会忽略 symbol
  • 会忽略 undefined
实现一个 deepClone 函数(参考1)
  • 如果是基本数据类型,直接返回
  • 如果是 RegExp 或者 Date 类型,返回对应类型
  • 如果是复杂数据类型,递归。
  • 考虑循环引用的问题
  • WeakMap-MDN
  • WeakMap-应用
function deepClone(obj, hash = new WeakMap()) {
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Date) return new Date(obj);
  if (obj === null || typeof obj !== "object") return obj
  if (hash.has(obj)) {
    return hash.get(obj)
  }

  /* 
    如果obj是数组,obj.constructor 是 [Function:Array]
    如果obj是对象,obj.constructor 是 [Function:Object]
  */
  let t = new obj.constructor();
  hash.set(obj, t);
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      t[key] = deepClone(obj[key], hash)
    }
  }
  return t
}

let obj = {
  name: 'andy',
  colors: ['red', 'blue']
}

let obj2 = deepClone(obj)

obj.name = 'James';
obj.colors.push('black')
console.log(obj); // {name: "James",colors: (3) ["red", "blue", "black"]}
console.log(obj2); // {name: "andy",colors: (2) ["red", "blue"]}

有没有发现上面的写法有一些杂乱,幸好我又发现了一个好的写法,他们整体思路都差不多,可以先看下实现方式哦,下面这个和上面那个是有一定区别的,比如下面的用了Reflect.ownKeys,而上面的用了for in 那么这有什么区别呢?请看下面这篇原作者的介绍文章

function isObject(object) {
  let type = Object.prototype.toString.call(object).slice(8, -1);
  return type === 'Object' || type === 'Array';
}

function deepClone(source, hash = new WeakMap()) {
  // 1. 判断不是数组或者对象的时候则返回其本身
  if (!isObject(source)) return source
  // 2. 通过WeakMap解决循环引用问题
  if (hash.has(source)) return hash.get(source);
  // 3. 判断是否是数组
  var target = Array.isArray(source) ? [...source] : {
    ...source
  };
  hash.set(source, target);
  // 4. 利用`Reflect.ownKeys()`获取target本身的属性(非原型链上的属性), 且包含了symbol类型的属性
  Reflect.ownKeys(target).forEach(key => {
    // 5. 再次判断是否是对象类型
    if (isObject(source[key])) {
      target[key] = deepClone(source[key],hash);
    } else {
      target[key] = source[key];
    }
  })
  // 6. 返回target
  return target;
}

let obj = {
  a: 1,
  colors: ['red','blue']
}
deepClone(obj)
let obj2 = deepClone(obj)
obj.a = 12
obj.colors.push('yellow')
console.log(obj); // { a: 12, colors: [ 'red', 'blue', 'yellow' ] }
console.log(obj2);// { a: 1, colors: [ 'red', 'blue' ] }

那么下面这篇是大佬写的,可以先收藏,我是没有太懂~~~~(>_<)~~~~

有精力还是要精读参考2与参考3

JQuery的extend(参考2和参考3)
let class2type = {};
let toString = class2type.toString;
let hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
  let proto, Ctor;
  if (!obj || toString.call(obj) !== "[object Object]") {
    return false;
  }
  proto = Object.getPrototypeOf(obj);
  if (!proto) {
    return true;
  }
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}


function extend() {
  // 默认不进行深拷贝
  let deep = false;
  let name, options, src, copy, clone, copyIsArray;
  let length = arguments.length;
  // 记录要复制的对象的下标
  let i = 1;
  // 第一个参数不传布尔值的情况下,target 默认是第一个参数
  let target = arguments[0] || {};
  // 如果第一个参数是布尔值,第二个参数是 target
  if (typeof target == 'boolean') {
    deep = target;
    target = arguments[i] || {};
    i++;
  }
  // 如果target不是对象,我们是无法进行复制的,所以设为 {}
  if (typeof target !== "object" && !isFunction(target)) {
    target = {};
  }

  // 循环遍历要复制的对象们
  for (; i < length; i++) {
    // 获取当前对象
    options = arguments[i];
    // 要求不能为空 避免 extend(a,,b) 这种情况
    if (options != null) {
      for (name in options) {
        // 目标属性值
        src = target[name];
        // 要复制的对象的属性值
        copy = options[name];

        // 解决循环引用
        if (target === copy) {
          continue;
        }

        // 要递归的对象必须是 plainObject 或者数组
        if (deep && copy && (isPlainObject(copy) ||
            (copyIsArray = Array.isArray(copy)))) {
          // 要复制的对象属性值类型需要与目标属性值相同
          if (copyIsArray) {
            copyIsArray = false;
            clone = src && Array.isArray(src) ? src : [];

          } else {
            clone = src && isPlainObject(src) ? src : {};
          }

          target[name] = extend(deep, clone, copy);

        } else if (copy !== undefined) {
          target[name] = copy;
        }
      }
    }
  }

  return target;
};

let obj = {
  name: 'andy',
  colors: ['red', 'blue']
}

let obj2 = {}
obj2 = extend(true, obj2, obj)

obj.name = 'James';
obj.colors.push('black')
console.log(obj);  // { name: 'James', colors: [ 'red', 'blue', 'black' ] }
console.log(obj2); // {name: "andy",colors:  ["red", "blue"]}

参考1

参考2

参考3

猜你喜欢

转载自blog.csdn.net/weixin_44194732/article/details/106085097
今日推荐