问题:
一个对象赋值给另一个对象后,新对象的值更改原对象的参数值随之变化(即改变新对象的值会影响原对象值)
直接用
=
的方式把一个对象赋值给另一个对象,会导致修改新对象时,原对象也发生变化
- 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() 深克隆方法