不要再問我JS Clone的問題了

開篇

亦舒的海角json

又回到這個老生常談,新生絕望的問題,一般遇到這種你們都比較熟悉的問題,反而不知道怎麼列大綱,怕不夠深刻也怕脫離主題~segmentfault

emm..數組

此文系 不要再問我XX系列之 不要再問我JS Clone的問題了bash

爲何會存在這三種狀況?三者有何差別

clone原本很簡單,只是由於JS中不一樣的數據類型存儲方式(堆和棧)的差別,咱們纔會以爲它貌似有點‘複雜’數據結構

基本類型和引用類型的差別如上圖所示了 它們共同的目標就是以一個對象爲原型clone出另一個新對象,由於自身的問題產生一些反作用,三者的差別其實就體如今反作用的差別上post

差別(堆和棧)

  • 棧(stack)爲自動分配的內存空間,它由系統自動釋放
  • 而堆(heap)則是動態分配的內存,大小不定也不會自動釋放

基礎類型: 值存放在棧中,比較是值的比較 引用類型: 值存放在堆中,變量其實是一個存放在棧內存的指針,這個指針指向堆內存中的地址。每一個空間大小不同,要根據狀況開進行特定的分配,引用類型的比較是引用的比較ui

var person1 = [1,2,3];
var person2 = [1,2,3];
console.log(a === b); // false
複製代碼

賦值

賦值的概念 即便剛入行也不陌生,天天都在用的'='spa

原理

  • 基本類型:在內存中新開闢一段棧內存,而後再把再將值賦值到新的棧中,是兩個獨立相互不影響的變量
  • 引用類型:賦值是傳址,是對象保存在棧中的地址的賦值,這樣的話兩個變量就指向堆內存的同一個對象,所以二者之間操做互相有影響

Demo

var obj1 = {
  name:'maying',
  age:22,
  sex:'女',
  language : [1,[2,3],[4,5],[9,0]]
}
var sringD = 'pre';
var obj3 = sringD;
sringD = 'post';

var obj2 = obj1;
obj1.name = 'gaile',
obj1.language[0] = 'jjj'
console.log('obj1',obj1)
       /*
        {
            age: 22
            language: (4) ["jjj", Array(2), Array(2), Array(2)]
            name: "gaile"
            sex: "女"
        }
       */
console.log('obj2',obj2)
        /*
            age: 22
            language: (4) ["jjj", Array(2), Array(2), Array(2)]
            name: "gaile"
            sex: "女"
        */
console.log('sringD',sringD) //post
console.log('obj3',obj3) //pre
複製代碼

理解淺拷貝

以前的不少年,我認爲賦值差很少等於淺拷貝 寫個小demo 發現它們之間的差別指針

var obj2 = obj1;
var obj3 = {...obj1};
obj1.name = 'gaile',
obj1.language[0] = 'jjj'
console.log('obj1',obj1)
console.log('obj2',obj2)
console.log('obj3',obj3)
複製代碼

賦值對象,是將對象指針直接賦值給另外一個變量 淺拷貝,是從新建立了新對象,因此你更改 obj1.name的時候不會影響到它,可是改變引用類型時就不能倖免了

所謂的淺拷貝就是:code

  • 當對簡單的數據類型進行賦值的時候,其實就是直接在棧中新開闢一個地方專門來存儲同樣的值
  • 當對引用類型進行淺拷貝,後面的對象和前面的對象在第一層數據結構中指向同一個堆地址,可是若是前面的數據不止有一層(屬性值是一個指向對象的引用只拷貝那個引用值),相似
language : [1,[2,3],[4,5],[9,0]]
複製代碼

內部的子對象的指針仍是同一個地址

若是要實現一直往下複製 就引出了接下來要說的深拷貝

結論:淺複製要比複製來的深入一點,至少它開闢了一個新對象,一起新的堆內存

目前可行的實現方式

站在巨人的肩膀上,咱們能夠輕鬆實現淺拷貝

  • 數組的淺拷貝
1. b = [...a]
2. b = a.slice(0) / [].slice.call(a,0)
3. b = a.concat() / [].concat.call(a)
複製代碼
  • 對象的淺拷貝
1. b = Object.assign({},a)
2. b = {...a}
複製代碼

若是要你本身實現呢

原理:遍歷對象的每一個屬性進行逐個拷貝

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

  var newObj = obj.constructor === Array ? [] : {}
  for (var key in obj) {
       if(obj.hasOwnProperty(key)){
          newObj[key] = obj[key]
        }
  }
  return newObj
}
複製代碼

理解深拷貝

深拷貝的意義,就是徹底複製,若是你讀了上文,應該就沒有什麼疑問了

將a對象複製一份給對象b,無論a中的數據結構嵌套有多深,當改變a對象中的任意深度的某個值後,b中的該值不會受任何影響

目前可行的實現方式

  • JSON.stringify()``和JSON.parse()的混合配對使用
var obj4 = JSON.parse(JSON.stringify(obj1)) 

obj1.name='yishu',

obj1.language[1] = ["二","三"];
obj4.language[2] = ["四","五"];


console.log(obj1);   
console.log(obj4); 
複製代碼

deepclone

obj1,obj4 是兩個獨立的對象,更改數據互不影響,達到了咱們要的目的

它粗暴,有用,可是也有缺點

  1. 在JSON.stringify()作序列化時,undefinedfunction以及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)
console.log(newObj)
複製代碼

結果

若是要你本身實現呢

原理:使用遞歸,遍歷每個對象屬性進行拷貝

var obj = {
  a: {b: 'old'}, 
  c:undefined, 
  d: function () {},
  e:  Symbol('')
 }


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 newObj = copy(obj)
newObj.a.b = 'new'
console.log(obj)
console.log(newObj)
複製代碼

jsonquedian

總結

  • 賦值:引用複製 執向同一個對象
  • 淺拷貝 :生成一個新對象,只能拷貝一層,當屬性值是一個指向對象的引用只拷貝那個引用值
  • 深拷貝:徹底拷貝,先後對象沒有任何關係

參考連接

相關文章
相關標籤/搜索