淺探js深拷貝和淺拷貝

對象和數組的拷貝對我來講一直都是一個比較模糊的概念,一直有點只知其一;不知其二,可是在實際工做中又偶爾會涉及到,有時候還會一不當心掉坑裏,不知道你們有沒有一樣的感覺,所以,準備對js對象和數組拷貝一探究竟。提到js的對象和數組拷貝,你們必定會想深拷貝和淺拷貝,可是爲何會有深拷貝和淺拷貝呢?下面就讓我簡單介紹一下爲何拷貝會有深淺之分以及有什麼區別?javascript

緣由及區別

咱們都知道js中有兩種數據類型,一種是基本數據類型,一種是引用數據類型,基本數據類型是按值訪問的,即在操做基本類型的變量時,是直接修改變量的值,而引用數據類型的值是按引用訪問的,什麼叫按引用訪問的呢?js的引用類型,也叫對象類型,是保存在內存中的,而在js中又沒法直接操做內存中的對象,實際上操做的是對象的引用,所以在引用類型變量在進行復制操做時,並非對對象值的直接複製,而是將對象的引用複製給了另外一個變量,實際上變量指向的是同一個內存地址中對象的值,所以只要改變其中一個對象變量的值另一個就會一塊兒改變,這就是咱們常說的淺拷貝。而在深拷貝中,會開闢一個新的內存地址用來存放新對象的值,兩個對象對應兩個不一樣的內存地址 ,修改一個對象並不會對另一個對象產生影響。接下來就讓咱們更細緻的探究js中的深淺拷貝。java

淺拷貝

實現淺拷貝的方法有多種,讓咱們先來看看js中提供的幾個自帶方法實現淺拷貝的的例子:數組

  • Object.assign()方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。注意:Object.assign()拷貝的是屬性值,假如源對象的屬性值是一個指向對象的引用,它也只拷貝那個引用值,來看個例子:
var a = {a : 'old', b : { c : 'old'}}
var b = Object.assign({}, a)
b.a = 'new'
b.b.c = 'new'
console.log(a) // { a: 'old', b: { c: 'new' } }
console.log(b) // { a: 'new', b: { c: 'new' } }

如上面例子,當拷貝的源對象的屬性值是一個對象時,拷貝的只是對象的引用值,所以當修改屬性值的時候兩個對象的屬性值都會發生更新函數

  • Array.prototype.slice()方法提取並返回一個新的數組,若是源數組中的元素是個對象的引用,slice會拷貝這個對象的引用到新的數組,來看個例子:
var arr = ['a', 'b', {d: 'old'}]
var arr1 = arr.slice(1)
arr1[1].d = 'new'
console.log(arr[2].d) // new

如上例所示,但源數組中的元素是對象引用時,slice拷貝的是這個對象的引用,所以當修改其中一個的值時,兩個數組中的值都會發生改變優化

  • Array.prototype.concat()用於合併多個數組,並返回一個新的數組,和slice方法相似,當源數組中的元素是個對象的引用,concat在合併時拷貝的就是這個對象的引用,來看個例子:
var arr1 = [{a: 'old'}, 'b', 'c']
var arr2 = [{b: 'old'}, 'd', 'e']
var arr3 = arr1.concat(arr2)
arr3[0].a = 'new'
arr3[3].b = 'new'
console.log(arr1[0].a) // new
console.log(arr2[0].b) // new

除了上述js中自帶方法實現的淺拷貝外,咱們本身如何簡單實現一個淺拷貝呢?來看個例子:prototype

function copy(obj) {
  if (!obj || typeof obj !== 'object') {
    return
  }

  var newObj = obj.constructor === Array ? [] : {}
  for (var key in obj) {
    newObj[key] = obj[key]
  }
  return newObj
}
var a = {b: 'bb', c: 'cc',  d: {e: 'ee'}}
var b = copy(a)
console.log(b) // { b: 'bb', c: 'cc', d: { e: 'ee' } }

實現一個淺拷貝,就是遍歷源對象,而後在將對象的屬性的屬性值都放到一個新對象裏就ok了,是否是很簡單呢?code

深拷貝

先來介紹一個作深拷貝最簡單粗暴的方法JSON.stringify()和JSON.parse()的混合配對使用,相信你們對這兩個方法都是很是熟悉的,來看個例子:對象

var obj = {a: {b: 'old'}}
var newObj = JSON.parse(JSON.stringify(obj))
newObj.a.b = 'new'
console.log(obj) // { a: { b: 'old' } }
console.log(newObj) // { a: { b: 'new' } }

上述例子能夠看出,使用JSON.stringify()和JSON.parse()確實能夠實現深拷貝,在新對象中修改對象的引用時,並不會影響老對象裏面的值,那麼,這麼個方法是否就沒有缺陷了呢?在JSON.stringify()作序列時,undefined、任意的函數以及symbol值,在序列化過程當中會被忽略,這會在對象複製的時候致使什麼後果呢?來看一個例子:遞歸

var obj = {a: {b: 'old'}, c:undefined, d: function () {}, e:  Symbol('')}
var newObj = JSON.parse(JSON.stringify(obj))
newObj.a.b = 'new'
console.log(obj) // { a: { b: 'old' }, c: undefined, d: [Function: d], e: Symbol() }
console.log(newObj) // { a: { b: 'new' } }

從例子中能夠看到,當源對象中有undefine、function、symbol時,在序列化操做的時候會被忽略,致使拷貝生成的對象中沒有對應屬性及屬性值。那麼怎麼本身去實現一個深拷貝呢?比較常見的方法就是經過遞歸,來看個例子:ip

function copy(obj) {
  if (!obj || typeof obj !== 'object') {
    return
  }
  var newObj = obj.constructor === Array ? [] : {}
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object' && obj[key]) {
        newObj[key] = copy(obj[key])
      } else {
        newObj[key] = obj[key]
      }
    }
  }
  return newObj
}

var old = {a: 'old', b: {c: 'old'}}
var newObj = copy(old)
newObj.b.c = 'new'
console.log(old) // { a: 'old', b: { c: 'old' } }
console.log(newObj) // { a: 'old', b: { c: 'new' } }

經過對須要拷貝的對象的屬性進行遞歸遍歷,若是對象的屬性不是基本類型時,就繼續遞歸,知道遍歷到對象屬性爲基本類型,而後將屬性和屬性值賦給新對象。

總結

以上對js深拷貝和淺拷貝作了簡單的介紹,在深拷貝的實現上也只介紹了最簡單的實現形式,並未考慮複雜狀況以及相應優化,想要對深拷貝有更深刻的瞭解,須要你們花時間去深刻研究,或者能夠關注我後續文章的動態。這篇文章若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊收藏

相關文章
相關標籤/搜索