问题:

一个对象赋值给另一个对象后,新对象的值更改原对象的参数值随之变化(即改变新对象的值会影响原对象值)

直接用 的方式把一个对象赋值给另一个对象,会导致修改新对象时,原对象也发生变化

let obj1 = {'name': '小红'};
let obj2 = obj1;
obj2.name = '小明';
console.log(obj1.name); //'小明'

原因:

JavaScript 中对象的赋值是默认引用赋值的(两个对象指向相同的内存地址),所以修改另一个对象时,即修改了内存地址里的对象,其他关联对象也会改变  

解决方法(转换类型法): JSON.parse(JSON.stringify(obj))

obj2=JSON.parse(JSON.stringify(obj1))

 注:

普通的对象也可以进行深拷贝,但是!!! 当对象内容项为number,string.boolean的时候,是没有什么问题的。但是,如果对象内容项为undefined,null,Date,RegExp,function,error的时候。使用JSON.stringify()进行拷贝就会出问题了。

解决方法(es6之Object.assign()法):obj2=Object.assign({},obj1)

 注:

Object.assign()方法有不足之处,Object.assign()只是让对象里第一层的数据没有了关联性(即修改obj2.name时obj1.name不会发生变化),但是对象内的对象则跟被复制的对象有着关联性的(即当修改更深层的obj2.name的值时,原对象obj1.name也跟着发生了变化)

 解决方法(使用递归的方式进行对象(数组)的深拷贝)

//已封装的深拷贝函数
function deepClone(obj = {}, hashMap = new WeakMap()) {
  //变量先置空
  let objClone = null,
    hashKey = hashMap.get(obj);
  if (obj instanceof RegExp) return new RegExp(obj); //正则表达式的情况
  if (obj instanceof Date) return new Date(obj); //日期对象的情况
  if (hashKey) return hashKey;
  //判断是否需要继续进行递归
  if (typeof obj == "object" && obj !== null) {
    objClone = obj instanceof Array ? [] : {};
    hashMap.set(obj, objClone);
    //进行下一层递归克隆
    for (const i in obj) {
      if (obj.hasOwnProperty(i)) {
        objClone[i] = deepClone(obj[i], hashMap);
      }
    }
  } else {
    //如果不是对象直接赋值
    objClone = obj;
  }

  return objClone;
}
//模拟数据
let obj = {
  name: "xm",
  birth: new Date(),
  desc: null,
  reg: /^123$/,
  ss: [1, 2, 3],
  fn: function () {
    console.log("123");
  },
};
//使用方式
let obj2 = deepClone(obj);
obj.fn() //123
obj2.fn() // 123
console.log(obj, obj2,'深拷贝');

所以推荐使用深拷贝函数这种方法

解决方法(使用自定义工具库之深克隆):创建utils.js

/*
 * 获取所有私有属性,包含Symbol私有属性
 */
const getOwnProperties = obj => {
  if (obj === null) return []
  return [
    ...Object.keys(obj), // 同 ...Object.getOwnPropertyNames(obj) 获取实例的私有属性
    ...Object.getOwnPropertySymbols(obj)
  ]
}

/*
 * 浅克隆
 */
const shallowClone = obj => {
  let type = toType(obj)
  if (/^(string|number|boolean|null|undefined|symbol|bigint)$/.test(type)) return obj
  if (/^function$/.test(type)) {
    return function proxy() {
      return obj()
    }
  }
  if (/^date|regexp$/.test(type)) return new obj.constructor(obj)
  if (/^error$/.test(type)) return new obj.constructor(obj.message)
  // 只处理数组(最后返回的是数组)和对象(普通对象/类数组对象等=》最后返回的都是普通对象)
  let keys = getOwnProperties(obj),
    clone = {}
  //clone = new obj.constructor(); // 类数组的时候会有问题
  Array.isArray(obj) ? (clone = []) : null
  keys.forEach(key => {
    clone[key] = obj[key]
  })
  return clone
}

/*
 * 深克隆
 */
const deepClone = (obj, cache = new Set()) => {
  // Set是一种存储结构,值的集合,保存非重复项,即Set中的元素是唯一的
  // 只有数组和对象才处理深拷贝,其余的情况直接按照浅克隆处理即可
  let type = toType(obj)
  if (!/^(array|object)$/.test(type)) return shallowClone(obj)

  // 避免自己套用自己导致无限递归
  if (cache.has(obj)) return shallowClone(obj)
  cache.add(obj)

  let keys = getOwnProperties(obj),
    clone = {}
  type === 'array' ? (clone = []) : null
  keys.forEach(key => {
    clone[key] = deepClone(obj[key], cache)
  })
  return clone
}

/*
 * 实现两个对象的深合并(Object.assign(obj1,obj2)为浅合并)
 *    + obj1对象 obj2对象 -> 依次遍历obj2,把obj2中的每一项替换obj1中的每一项
 *    + obj1对象 obj2不是对象 -> 不进行任何处理
 *    + obj1不是对象 obj2对象 -> obj2直接替换obj1
 *    + obj1不是对象 obj2也不是对象 -> obj2直接替换obj1
 */
const merge = (obj1, obj2) => {
  let isPlain1 = isPlainObject(obj1),
    isPlain2 = isPlainObject(obj2)
  if (!isPlain1) return obj2
  if (!isPlain2) return obj1
  // 遍历obj2中的每一项,让其替换obj1中的每一项
  let obj2Arr = getOwnProperties(obj2)
  obj2Arr.forEach(key => {
    obj1[key] = merge(obj1[key], obj2[key])
  })
  return obj1
}

//===========================================================

// 数据类型检测通用方法
let getProto = Object.getPrototypeOf, // 获取实例的原型对象
  class2type = {},
  toString = class2type.toString, // 取Object.prototype.toString
  hasOwn = class2type.hasOwnProperty, // 取Object.prototype.hasOwnProperty
  fnToString = hasOwn.toString, // 取Function.prototype.toString 转换字符串用
  ObjectFunctionString = fnToString.call(Object) // 同Object.toString() 把Object函数转成字符串 "function Object() {[native code]}"

/*
 * 循环数据中的每一项:建立数据类型检测的映射表
 *    + [object String]/[object Number]/[object Boolean]都是为了处理基于构造函数创建的基本数据值的引用类型值,最后期许检测出来的结果依然是"string/number/boolean"
 *    + typeof new Number(10) => "object"
 *    + toString.call(new Number(10)) => "[object Number]"
 */
let assembleKeys = [
  'String',
  'Number',
  'Boolean',
  'Symbol',
  'Function',
  'Array',
  'Object',
  'Date',
  'RegExp',
  'Error',
  'GeneratorFunction'
]
assembleKeys.forEach(name => (class2type[`[object ${name}]`] = name.toLowerCase()))

/*
 * 检测数据类型的公共方法
 */
const toType = obj => {
  // '=='判断null或者undefined,+""转成字符串 "null"或者"undefined"
  if (obj == null) return obj + ''
  // 如果是引用数据类型(包含:new Number(10)这种),则基于Object.prototype.toString检测(拿检测的结果到之前建立的映射表中去匹配查找,找到对象的小写数据类型,找不到则返回"object");而基本数据类型,之前排除了null/undefined,剩下的基于typof即可解决
  return typeof obj === 'object' || typeof obj === 'function'
    ? class2type[toString.call(obj)] || 'object'
    : typeof obj
}

/*
 * 检测是否为一个纯粹对象
 */
const isPlainObject = obj => {
  let proto = null,
    Ctor,
    type = toType(obj);
  if (obj === undefined) return false
  // 不存在或者检测数据类型的结果都不是object,则一定不是纯粹对象
  if (!obj || type !== 'object') return false;
  // 不存在原型的情况:Object.create(null),相当于创建空对象{}
  proto = getProto(obj)
  if (!proto) return true;
  // 获取当前值原型对象上的constructor(即获取它的构造函数)
  Ctor = hasOwn.call(proto, 'constructor') && proto.constructor;
  // 有构造函数,并且构造函数需要直接是Object才可以(这样排除了NodeList等子类/自定义类的实例)
  // ObjectFunctionString === fnToString.call(Object)
  return typeof Ctor === 'function' && fnToString.call(Ctor) === ObjectFunctionString
}


export default {
  getOwnProperties,
  shallowClone,
  deepClone,
  toType,
  isPlainObject,
  merge
}

工具库使用方法

import utils from '@/libs/utils'  // vue文件引入utils.js

obj2 = utils.deepClone(obj1)  // deepClone() 深克隆方法

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐