基础类型与引用(地址)

例子一

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123
}
let obj1 = obj
console.log(obj1) // { c: 'l am an obj', d: 123 }
obj1.c = 'l am an obj1'
console.log(obj) // { c: 'l am an obj1', d: 123 }
console.log(obj1) // { c: 'l am an obj1', d: 123 }

变量obj存的是地址address01,执行let obj1 = obj后,变量obj1存的还是address01。就好像主卧室(obj),或大房间(obj1),都指的是房间号为address01的房间。所以,你说“换掉大房间的床”,obj1.c = 'l am an obj1',等同于“换掉主卧室的床”,因为都是“换掉房间号为address01的房间里的床”。

clipboard.png

例子二

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123
}
let obj1 = {}
obj1.c = obj.c
obj1.d = obj.d
console.log(obj1) // { c: 'l am an obj', d: 123 }
obj1.c = 'l am an obj1'
console.log(obj) // { c: 'l am an obj', d: 123 }
console.log(obj1) // { c: 'l am an obj1', d: 123 }

变量obj存的是地址address01,执行let obj1 = {}后,变量obj1得到了一个新的地址address02。现在不仅有房间号为address01的主卧室(obj),还有房间号为address02的次卧(obj1),在那里你可以布置你自己的新房间。你可以先说“要一张和主卧一样的床”,obj1.c = obj.c,再说“还要一台和主卧一样的灯”,obj1.d = obj.d,最后你又觉得床不舒服,“换了张床”,obj1.c = 'l am an obj1',但这些都不会影响主卧的床obj.c

clipboard.png

例子三

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123,
  e: [1, 3, 5]
}
let obj1 = {}
obj1.c = obj.c
obj1.d = obj.d
obj1.e = obj.e
console.log(obj1) // { c: 'l am an obj', d: 123, e: [ 1, 3, 5 ] }
obj1.c = 'l am an obj1'
obj1.e[0] = 2
console.log(obj) // { c: 'l am an obj', d: 123, e: [ 2, 3, 5 ] }
console.log(obj1) // { c: 'l am an obj1', d: 123, e: [ 2, 3, 5 ] }

主卧里面还可以有卫生间obj.e的呢,obj.e存的也是地址adress03,只要是对象或数组都存的是地址。这时obj1.e = obj.e,存的就是主卧的卫生间的房间号address03。目前只有一个卫生间,你知道卫生间的地址adress03,保存在obj1.e中,然后你就知道如何“把卫生间的热水器换掉”,obj1.e[0] = 2

clipboard.png

例子四

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123,
  e: [1, 3, 5]
}
let obj1 = {}
obj1.c = obj.c
obj1.d = obj.d
obj1.e = obj.e
console.log(obj1) // { c: 'l am an obj', d: 123, e: [ 1, 3, 5 ] }
obj1.c = 'l am an obj1'
obj1.e = 'l am not an arr'
console.log(obj) // { c: 'l am an obj', d: 123, e: [ 1, 3, 5 ] }
console.log(obj1) // { c: 'l am an obj1', d: 123, e: 'l am not an arr' }

后来你觉得知道主卧的卫生间的地址adress03有什么用,有那闲工夫还不如用来放个柜子,故obj1.e = 'l am not an arr',这样做以后,没有了卫生间的地址,你就不能在主卧的卫生间里捣乱了。

clipboard.png

例子五

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123,
  e: [1, 3, 5]
}
let obj1 = {}
obj1.c = obj.c
obj1.d = obj.d
obj1.e = []
for (let i = 0; i < obj.e.length; i++) {
  obj1.e[i] = obj.e[i]
}
console.log(obj1) // { c: 'l am an obj', d: 123, e: [ 1, 3, 5 ] }
obj1.c = 'l am an obj1'
obj1.e[0] = 2
console.log(obj) // { c: 'l am an obj', d: 123, e: [ 1, 3, 5 ] }
console.log(obj1) // { c: 'l am an obj1', d: 123, e: [ 2, 3, 5 ] }

其实你还是想要个独立卫生间的,obj1.e = [],这样你得到了房间号为adress04的房间,然后你按照主卧卫生间的样子来改造这个房间for (let i = 0; i < obj.e.length; i++) { obj1.e[i] = obj.e[i] }。这时候有两个独立的卫生间了,房间号分别是adress04adress03,尽管布局装饰一样,但还是两个房间,宾馆里的房间还都一样呢,但它们有不同的房间号,实际上是相互独立的房间。你换adress04房间的热水器,obj1.e[0] = 2,并不会影响房间adress03房间。

clipboard.png

抽象例子五的复制过程

即深拷贝

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123,
  e: [1, 3, 5]
}
function deepCopy(val) {
  if (!val || typeof val !== 'object') {
    return val
  }
  let copy = Array.isArray(val) ? [] : {}
  for (key in val) {
    if (val.hasOwnProperty(key)) {
      if (val[key] && typeof val[key] === 'object') {
        copy[key] = deepCopy(val[key])
      } else {
        copy[key] = val[key]
      }
    }
  }
  return copy
}

let obj1 = deepCopy(obj)
obj1.c = 'l am an obj1'
obj1.e[0] = 2
console.log(obj) // { c: 'l am an obj', d: 123, e: [ 1, 3, 5 ] }
console.log(obj1) // { c: 'l am an obj1', d: 123, e: [ 2, 3, 5 ] }

例子六

let a = 'l am a string'
let b = 6
let obj = {
  c: 'l am an obj',
  d: 123,
  e: {}
}
obj.e.parent = obj

let keyValArr = []
let copyArr = []

function deepCopy(val) {
  if (!val || typeof val !== 'object') {
    return val
  }
  let copy = Array.isArray(val) ? [] : {}

  let index = keyValArr.indexOf(val)
  if (index === -1) {
    keyValArr.push(val)
    copyArr.push(copy)
  } else {
    return copyArr[index]
  }

  for (key in val) {
    if (val.hasOwnProperty(key)) {
      if (val[key] && typeof val[key] === 'object') {
        copy[key] = deepCopy(val[key])
      } else {
        copy[key] = val[key]
      }
    }
  }
  return copy
}

let obj1 = deepCopy(obj)
obj1.e.parent.c = 'l am an obj1'
console.log(obj) // { c: 'l am an obj', d: 123, e: { parent: [Circular] } }
console.log(obj1) // { c: 'l am an obj1', d: 123, e: { parent: [Circular] } }

总有人想寻根问祖,obj.e.parent = obj,如此形成了环,之前的代码就要重新调整。简单来说,就是把复制过程中的源对象[obj, obj.e]都记录下来,以及对应的拷贝对象[copy, copy.e],碰到属性值是之前复制过的对象的情况,obj.e.parent的属性值是obj,就把对应的拷贝对象给它,copy.e.parent = copy,而不进行递归深拷贝。
从地址的角度来说,复制过程中,把地址都记录下来,用valArr记录源对象的地址[address01, address03],用copyArr记录拷贝对象的地址[address02, address04],当碰到需要拷贝的是之前记录过源对象的地址时,就把对应的拷贝对象的地址返回。例如obj.e.parent的属性值是obj的地址,那么拷贝过程中,obj1.e.parent的属性值就是obj1的地址。

clipboard.png


nbb3210
436 声望31 粉丝

优雅地使用JavaScript解决问题