浅拷贝与深拷贝发现 JSON.stringify

浅拷贝与深拷贝 JSON.stringify

基本类型

基本数据类型有7种:NumberStringBooleanNullUndefinedSymbol(ES6)BigInt(ES10)。变量均按值存放于栈中,赋值直接用=即可。

引用类型

引用数据类型有1种:Object。变量内存地址存放于栈中,值存在堆中,引用类型的赋值与下面讨论的浅拷贝深拷贝密切相关。

浅拷贝

首先声明 浅拷贝 ≠ 赋值

赋值=赋的是对象的内存地址,两个对象指向堆中同一份存储空间,互相影响。

let obj1 = {

  name: 'JS',
  
  list: ['JS','CSS', 'HTML']
  
}

let obj2 = obj1

obj2.name = '11'

obj2.list[0] = 'Java'

// {name: '11', list: ['Java', 'CSS', 'HTML']} 

// {name: '11', list: ['Java', 'CSS', 'HTML']} 

浅拷贝是在堆中创建新的内存空间,拷贝后对象的基本数据类型互不影响,但引用类型依然共享同一份存储空间,会互相影响

function shallowClone (obj1) {

  let obj2 = {}
  
  for (let i in obj1) { 
  
    obj2[i] = obj1[i] 
    
  } 
  
  return obj2;
  
}
let obj1 = {

  name: '11',
  
  list: ['JS','CSS', 'HTML']
  
}

let obj2 = shallowClone(obj1)

obj2.name = 'juejin'

obj2.list[0] = 'Java'

console.log(obj1, obj2)

// {name: '11', list: ['Java', 'CSS', 'HTML']} 
// {name: 'juejin', list: ['Java', 'CSS', 'HTML']} 

Array

整理可数组浅拷贝相关 api

扩展运算符

let arr1 = [1,[2],3]

let arr2 = [...arr1]

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2,5],3] [4,[2,5],3]

Array.prototype.slice

let arr1 = [1,[2],3]

let arr2 = arr1.slice()

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2,5],3] [4,[2,5],3]

Array.prototype.concat

let arr1 = [1,[2],3]

let arr2 = arr1.concat([])

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2,5],3] [4,[2,5],3]

Array.from

将类数组或可迭代对象创建一个新的浅拷贝数组实例。

let arr1 = [1,[2],3]

let arr2 = Array.from(arr1)

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2,5],3] [4,[2,5],3]

Array.prototype.map

let arr1 = [1,[2],3]

let arr2 = arr1.map(item => item)

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2,5],3] [4,[2,5],3]

Array.prototype.filter

let arr1 = [1,[2],3]

let arr2 = arr1.filter(item => item)

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2,5],3] [4,[2,5],3]

Array.prototype.reduce

reduce这个可能有点滥竽充数,没有真正体现它的价值,算是提供一种新奇的思路吧。

let arr = [1,[2],3]

let arr2 = arr.reduce((arr1,item) => {

  arr1.push(item)
  
  return arr1
  
}, [])

arr2[0] = 4

arr2[1].push(5)

console.log(arr, arr2) // [1,[2,5],3] [4,[2,5],3]

Object

整理可对象浅拷贝相关api

扩展运算符

let obj1 = {

  name: '11',
  
  list: ['JS','CSS', 'HTML']
  
}

let obj2 = {...obj1}

obj2.name = 'juejin'

obj2.list[0] = 'Java'

console.log(obj1, obj2) 

// {name: '11', list: ['Java', 'CSS', 'HTML']} 

// {name: 'juejin', list: ['Java', 'CSS', 'HTML']} 
Object.assign
let obj1 = {

  name: '11',
  
  list: ['JS','CSS', 'HTML']
  
}
let obj2 = Object.assign({}, obj1)

obj2.name = 'juejin'

obj2.list[0] = 'Java'

console.log(obj1, obj2) 

// {name: '11', list: ['Java', 'CSS', 'HTML']} 

// {name: 'juejin', list: ['Java', 'CSS', 'HTML']} 

深拷贝

堆内存重新开辟全新的内存存放新对象,两个对象不会互相影响。

Array - 序列化

利用JSON.stringify将数组转为JSON字符串,再用JSON.parse将字符串转为新数组。

let arr1 = [1,[2],3]

let arr2 = JSON.parse(JSON.stringify(arr1))

arr2[0] = 4

arr2[1].push(5)

console.log(arr1, arr2) // [1,[2],3] [4,[2,5],3]

Object - 序列化
利用JSON.stringify将对象转为JSON字符串,再用JSON.parse将字符串转为新对象,但这个方法存在弊端。

let obj1 = {

  name: '11',
  
  list: ['JS','CSS', 'HTML']
  
}

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

obj2.name = 'juejin'

obj2.list[0] = 'Java'

console.log(obj1, obj2)

// {name: '11', list: ['JS', 'CSS', 'HTML']} 

// {name: 'juejin', list: ['Java', 'CSS', 'HTML']} 

貌似看起来没有任何问题,也不用引入lodash库。那么,现在给对象添加个方法看看~

let obj1 = {

  name: '11',
  
  list: ['JS','CSS', 'HTML'],
  
  work: function() {}
  
}

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

console.log(obj1, obj2)

// {name: '11', list: ['JS', 'CSS', 'HTML'], work: function() {}} 

// {name: '11', list: ['JS', 'CSS', 'HTML']} 

方法在JSON.stringify后丢失了… 万事总有解决办法,实在不行引lodash库。

我当时处理方法是将函数转为字符串确保不再丢失,最后再利用new Function()去将字符串转为函数。

let obj1 = {

  name: '11',
  
  list: ['JS','CSS', 'HTML'],
  
  work: function() {},
  
}
for(let i in obj1) {

  if(typeof obj1[i] === 'function') {
  
    obj1[i] = obj1[i].toString()
  
  }  
}

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

for(let i in obj2) {

  if(typeof obj2[i] === 'string' && obj2[i].indexOf('function') === 0) {
    
    obj2[i] = new Function('return ' + obj2[i])
    
  }   
}

console.log(obj1, obj2)

// {name: '11', list: ['JS', 'CSS', 'HTML'], work: function() {}} 

// {name: '11', list: ['JS', 'CSS', 'HTML'], work: function() {}} 

当然这个方法也并不是完美的,比如我确实有个字段为string类型,且值就是function,那就真是凑巧了。

这个现象引发了我对 JSON.stringify 的兴趣,还会丢失哪些类型的数据?列举了写属性

let obj1 = {
  name: '11',
  
  list: ['JS','CSS', 'HTML'],
  
  work: function() {},
  
  date: new Date(),
  
  reg: new RegExp(),
  
  symbol: Symbol(),
  
  number: NaN,
  
  address: Infinity,
  
  age: undefined
  
}

console.log(JSON.stringify(obj1))

// {

// “name”:“11”,

// “list”:[“JS”,“CSS”,“HTML”],

// “date”:“2021-08-29T11:43:33.545Z”,

// “reg”:{},

// “number”:null,

// “address”:null

// }

发现有坑

  1. 函数、Symbolundefined丢失
  2. NaNInfinitynull
  3. RegExp对象变{}
  4. Date对象转为字符串
继续在网上找JSON.stringify踩坑文章,以下情况也得小心
    • 方法自带toJSON, 直接返回函数return
    • let obj1 = {
      
        name: 'JS',
        
        toJSON: function() {
        
          return 'JS'
          
        }
      }
      
      console.log(JSON.stringify(obj1)) // JS
      
      1. 属性引用自身,会报错
      let obj1 = {
      
        name: 'JS',
        
        copy: obj1
        
      }
      console.log(JSON.stringify(obj1)) 
      

      存在不可枚举属性,也会丢失

      1. 存在不可枚举属性,也会丢失
      let obj1 = {
      
        name:  'JS'
        
      }
      
      Object.defineProperty(obj1, 'name', {
      
        enumerable: false
        
      })
      console.log(JSON.stringify(obj1)) // {}
      

      这些,都有一些同样可以先转字符串,再转回原属性类型,算是一种思路吧,但都在对象value情况可知的大前提下,不然还是用成熟的lodash中的cloneDeep吧。

    • 0
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值