阅读 100

对象的浅合并与深合并、浅拷贝与深拷贝

在学习对js的array、number、objects、string的操作时,我们可以对比loadash工具库的实现,在实际开发过程中进行使用。

在开始之前我们先明确一个概念,js的栈与堆。栈与堆都是js的数据结构,栈的空间较小,用来存储基础类型的数据,例如string,number;堆空间较大,用来存储大的数据,例如function、object等。

在执行js的时候,会先创建一个上下文,这里就是一个栈结构

    var a = 1     var b = {}     // 上下文环境存储的是      // 键 a : 值 1     // 键 b : 值 对象的引用地址 复制代码

一、浅合并

浅合并通俗来理解就是只合并第一层,不对相同属性做递归合并处理,而是直接替换,比如以下示例

let obj1 = {     a: 'a1',     b: 'b1',     c: {         d: 'c1',         e: 'c2'     } } let obj2 = {     a: 'a2',     c: {         d: 'c3'     } } console.log(shallowMerge(obj1, obj2)) // 输出 {     a: 'a2',     b: 'b1',     c: {         d: 'c3'     } } 复制代码

实现浅合并思想如下:

  • 边界值判定,如果object1不是对象,使用object2替换object1

  • 如果object1是对象,object2不是对象,返回object1

  • 如果object1是对象且object2是对象,遍历object2进行合并,遇到相同属性直接进行替换

js已经替我们实现了Object.assign,但是会有一些边界值问题,我们可以进行自定义实现

// 是否是引用类型,具体指使用typeof类型为object,并且不是null的值 function isObjectLike(value) {  return typeof value === 'object' && value !== null } // 使用Object.prototype.toString获取表示该对象的字符串,例如[object Array] // 在es5之后,toString方法已经可以返回正确的类型,null对应[object Null] const toString = Object.prototype.toString function getTag(value) {  return toString.call(value) } // 判断是否是普通对象,typeof性能会更好 function isPlainObject(value) {  if (!isObjectLike(value) || getTag(value) != '[object Object]') {    return false  }  // 例如:Object.create(null)  if (Object.getPrototypeOf(value) === null) {    return true  }  // 循环遍历对象,如果是自定义构造器实例化的object则返回false  let proto = value  while (Object.getPrototypeOf(proto) !== null) {    proto = Object.getPrototypeOf(proto)  }  return Object.getPrototypeOf(value) === proto } // 浅合并  shallowMerge (obj1, obj2) {      let isPlain1 = isPlainObject(obj1)      let isPlain2 = isPlainObject(obj2)     // 1.边界值判定,如果object1不是对象,使用object2替换object1     if(!isPlain1){         return obj2     }     // 2.如果object1是对象,object2不是对象,返回object1     if(!isPlain2){         return obj1     }     // 3.如果object1是对象且object2是对象,遍历object2进行合并     // Object.keys 获取可枚举普通键     // Object.getOwnPropertyNames 获取除symbol以外的所有键     // Object.getOwnPropertySymbols 获取symbol键     // 根据Object.assign定义,它会合并Object.keys与Object.getOwnPropertySymbols的所有值     [...Object.keys(obj2),...Object.getOwnPropertySymbols(obj2)].forEach((key)=>{         obj1[key] = obj2[key]     })     return obj1 } 复制代码

二、深合并

深合并也就是要进行递归合并,将对象的所有子属性也进行合并,代码演示如下:

 let obj1 = {      a: 'a1',      b: 'b1',      c: {          d: 'c1',          e: 'c2'      }  }  let obj2 = {      a: 'a2',      c: {          d: 'c3'      }  }  console.log(deepMerge(obj1, obj2))  // 输出  {      a: 'a2',      b: 'b1',      c: {          d: 'c3',          e: 'c2'      }  } 复制代码

实现深合并思想如下:

  • 边界值判定,如果object1不是对象,使用object2替换object1

  • 如果object1是对象,object2不是对象,返回object1

  • 如果object1是对象且object2是对象,遍历object2进行合并,遇到键相同的普通对象值,递归合并,其他直接进行替换

与浅拷贝唯一的区别就是赋值的时候判断一下值类型,进行递归调用

deepMerge (obj1, obj2) {     let isPlain1 = isPlainObject(obj1)     let isPlain2 = isPlainObject(obj2)     if(!isPlain1){         return obj2     }     if(!isPlain2){         return obj1     }     [...Object.keys(obj2),...Object.getOwnPropertySymbols(obj2)].forEach((key)=>{             //与浅拷贝区别之处             obj1[key] = deepMerge(obj1[key],obj2[key])         })         return obj1     } 复制代码

三、浅拷贝

浅拷贝也就是只拷贝一层,对于非基础类型直接引用,其中任一对象属性的改变会影响到另一对象,示例如下:

let obj1 = {     a: 'a1',     c: {         d: 'c1'     } } let obj2 = shallowClone(obj1) obj1.a = 'a2' obj1.c.d = 'c2' console.log(obj2) // 输出 {     a: 'a1',     c: {         d: 'c2'     } } 复制代码

浅拷贝实现思想

  • 对obj1进行遍历,然后将基础类型值直接赋值给obj2,将引用类型的引用地址赋值给obj2

shallowClone(obj){     let result = {}     // for...in以任意顺序遍历一个对象的除[Symbol]以外的[可枚举]属性,包括继承的可枚举属性     for (const key in obj){         // hasOwnProperty会返回一个布尔值,指示对象自身属性中是否具有指定的属性         if(obj.hasOwnProperty(key)){            result[key] = obj[key]          }     }     return result }      复制代码

三、深拷贝

深拷贝就是对对象所有属性进行递归遍历,且拷贝对象之间的值不会有相互影响。 示例如下

let obj1 = {      a: 'a1',      c: {          d: 'c1'      }  }  let obj2 = deepClone(obj1)  obj1.a = 'a2'  obj1.c.d = 'c2'  console.log(obj2)  // 输出  {      a: 'a1',      c: {          d: 'c1'      }  } 复制代码

最简单的深拷贝使用Object.parse(Object.stringfy()),缺点是不能处理函数与正则,拷贝出来的值会变成null与空对象。

深拷贝实现思想

  • 要对子元素的类型先进行分类

  • 如果是基础类型则直接赋值

  • 如果是引用类型则根据不同情况做处理

  • 对于普通对象做递归拷贝

// 将所有类型罗列 // 基础类型 const stringTag = '[object String]' const symbolTag = '[object Symbol]' const numberTag = '[object Number]' const boolTag = '[object Boolean]' const nullTag = '[object Null]' const undefinedTag = '[object Undefined]' // 内置对象 const argsTag = '[object Arguments]' const arrayTag = '[object Array]' const dateTag = '[object Date]' const errorTag = '[object Error]' const mapTag = '[object Map]' const objectTag = '[object Object]' const regexpTag = '[object RegExp]' const setTag = '[object Set]' const weakMapTag = '[object WeakMap]' ... function deepClone(obj, hash = new WeakMap()) {      // 防止循环引用,缓存引用地址,如果出现过则直接返回结果     if (hash.get(obj)) return hash.get(obj);          // 基础类型与function直接返回值,function的拷贝没有考虑this指向问题     if(typeof obj !== 'object' || obj === null){        return obj      }     // 对日期和正则做特殊处理,如果数据复杂,可以针对需要支持的内置对象做响应处理     if (obj instanceof Date) return new Date(obj);     if (obj instanceof RegExp) return new RegExp(obj);          // 对其他引用对象做处理     let cloneObj = new obj.constructor()      hash.set(obj, cloneObj);     for (let key in obj) {          if (obj.hasOwnProperty(key)) {              // 实现一个递归拷贝              cloneObj[key] = deepClone(obj[key])         }     }      return cloneObj;   } 复制代码

没有尽善尽美的深拷贝,知晓其支持程度及相应优缺点用来支持业务功能。


作者:佩子
链接:https://juejin.cn/post/7026715822550679583


文章分类
百科问答
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐