淺談JS的淺拷貝和深拷貝(遞歸和樹遍歷)

1.對象的特殊性

由於對象的是經過指針仔細內存地址的,因此對象的拷貝不能像變量通常簡單的賦值,對象的賦值只是將指針的地址賦值過去而已,修改屬性值會對全部指向這個內存地址的對象的屬性值都會被改變,見下面的例子:javascript

// 變量賦值
var a = 5;
var b = a;
// 修改b不會對a形成影響
b=10;
> a
> 5

// 對象賦值
var obj = {
    a: '5',
    b: '10',
    c: {
        d: 1
    }  
}
var obj1 = obj
// 修改obj1.a會對obj.a形成影響
obj1.a = 9
> obj.a
> 9

因此當你使用object(array也是object的一種特殊形式)時,想複製一個object,但兩個object互不影響時,這就須要咱們說的淺拷貝和深拷貝了java

2.最簡單的實現(有侷限性)

// object數據中只能有基本的數據類型(String,Number,Boolean,Array,object,null,undefined),其它數據會丟失,但不會報錯
obj1 = JSON.parse(JSON.stringify(obj))

3.淺拷貝

基於Object的特殊性,在對對象進行簡單的拷貝,只拷貝第一層級的屬性,這種拷貝就是淺拷貝。segmentfault

function Copy(source, obj) {
    for (let key in obj) {
        source[key] = obj[key]
    }
    return source
}

Copy(obj1, obj)
obj1.a=10
obj1.c.d=100
// 淺拷貝對第一層的屬性進行了拷貝,因此obj.a不受影響
> obj.a = 9
// 可是obj.c.d是第二層級的屬性,它受到了影響,它的值被改變了
> obj.c.d =100

3.深拷貝

3.1遞歸遍歷

注意:遞歸遍歷可能爆棧,通常不會出現這種狀況,除非對象的深度達到10000+prototype

// 判斷數據類型的方法--更新於2019-03-28
function isType(data, type) {
  return Object.prototype.toString.call(data) === '[object ' + type + ']'
}
// extendEasy實現深拷貝;extendSuper在深拷貝的基礎上實現多個繼承相似(source, obj1, obj2, obj3 ...)
function extendSuper () {
  var arg = arguments
  for (let i = 1; i < arg.length; i++) {
    arg[0] = extendEasy(arg[0], arg[i])
  }
  return arg[0]
}

// 實現深拷貝
function extendEasy (source, obj) {
  for (let key in obj) {
// 原來使用instanceof判斷類型會存在BUG
    if (isType('Object',obj[key])) {
      source[key] = {}
      source[key] = extendEasy(source[key], obj[key])
    }
    if (isType(' Array',obj[key])) {
      source[key] = []
      source[key] = extendEasy(source[key], obj[key])
    }
    source[key] = obj[key]
  }
  return source
}

3.2樹遍歷(推薦)

注意:下面代碼中Object.isType的實現方式-->https://segmentfault.com/a/11...
深度和廣度:pop跟push決定了深度優先,廣度優先請用shift和push指針

/*
* @param target object 非必填,默認{}
* @param source object 必填
*
* */
function copyObject() {
  let root = {}
  const obj = arguments[arguments.length]
  if(!Object.isType(obj, "Object") && !Object.isType(obj, "Array")) {
    throw "source not Object"
  }
  if(arguments.length >= 2 && (Object.isType(arguments[0], "Object") || Object.isType(arguments[0], "Array"))) {
    root = arguments[0]
  } else {
    if(Object.isType(arguments[0], "Array")) {
      root = []
    }
  }
  // 棧
  const arr = [
    {
      // key: undefined,
      parent: root,
      data: obj
    }
  ]
  while (arr.length) {
    // arr: pop跟push決定了深度優先,廣度優先請用shift和push
    const o = arr.pop()
    // if(o.key === undefined) {
    //   o
    // }
    for (let item in o.data){
      if (Object.isType(o.data[item], "Array")){
        o.parent[item] = []
        arr.push({
          key: item,
          parent: o.parent[item],
          data: o.data[item]
        })
      }
      else if (Object.isType(o.data[item], "Object")) {
        o.parent[item] = {}
        arr.push({
          key: item,
          parent: o.parent[item],
          data: o.data[item]
        })
      }
      // 若是對其它類型支持請在這裏拓展
      else {
        o.parent[item] = o.data[item]
      }
    }
  }
  return root
}
相關文章
相關標籤/搜索