天天看點

JavaScript的淺拷貝和深拷貝

前言:

閱讀此文前需掌握:​​javascript的堆棧原理,引用類型與基本類型差別​​​ 在 ​

​JavaScript​

​ 中,我們将資料分為 ​

​基本資料類型(原始值)​

​ 與 ​

​引用類型​

  • 基本資料類型的值是按值通路的,基本類型的值是不可變的
  • 引用類型的值是按引用通路的,引用類型的值是動态可變的
var fx = 100;
var fx1 = 100;
console.log(fx === fx1) // true
var fx2 = {a: 1, b: 2};
var fx3 = {a: 1, b: 2};
console.log(fx2 === fx3) // false 兩個不同的對象      
  • 基本資料類型的比較是值得比較
  • 引用類型的比較是引用位址的比較

鑒于以上資料類型的特點,我們可以初步想到:所謂 ​

​淺拷貝​

​ 與 ​

​深拷貝​

​ 可能就是對于值的拷貝和引用的拷貝(基本資料類型都是對值的拷貝,不進行區分)。一般來說,我們所涉及的拷貝對象,也都是針對引用類型的。

  • 淺拷貝是拷貝一層,深層次的對象級别的就拷貝引用;深拷貝是拷貝多層,每一級别的資料都會拷貝出來;
  • 淺拷貝和深拷貝都隻針對于引用資料類型,淺拷貝隻複制指向某個對象的指針,而不複制對象本身,新舊對象還是共享同一塊記憶體;但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享記憶體,修改新對象不會改到原對象;
  • 差別:淺拷貝隻複制對象的第一層屬性、深拷貝可以對對象的屬性進行遞歸複制;
JavaScript的淺拷貝和深拷貝
var fxArr = ["One", "Two", "Three"]
var fxArrs = fxArr
fxArrs[1] = "love";
// 由于是指派是以fxArr的值也發生改變
console.log(fxArr) // ["One", "love", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]      

淺拷貝:

對對象進行淺層次的複制,隻複制一層對象的屬性,并不包括對象裡面的引用類型資料。

數組的淺拷貝:

解決方法一:數組的slice方法

var fxArr = ["One", "Two", "Three"]
var fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]      

解決方法二:數組的concat方法

var fxArr = ["One", "Two", "Three"]
var fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]      

解決方法三:​​ES6的擴充運算符​​...

var fxArr = ["One", "Two", "Three"]
var fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]      

對象的淺拷貝:

第一種方法

// 隻複制第一層的淺拷貝
function simpleCopy (obj1) {
    var obj2 = Array.isArray(obj1) ? [] : {}
    for (let i in obj1) {
        obj2[i] = obj1[i]
    }
    return obj2
}
var fxObj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newFxObj = simpleCopy(fxObj)
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
    console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18 基本資料類型不會改變
console.log(fxObj.nature) //  ["smart", "good", "why"] 引用類型會改變
console.log(fxObj['names']) // {name1: "why fx", name2: "xka"} 引用類型會改變
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}      

第二種方法:Object.assign方法(隻能處理深度隻有一層的對象)

var fxObj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newFxObj = Object.assign({}, fxObj);
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
    console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18
console.log(fxObj.nature) //  ["smart", "good", "why"]
console.log(fxObj['names']) // {name1: "why fx", name2: "xka"}
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}      

方法三:ES6的對象擴充方法var newFxObj = {...fxObj}

var fxObj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newFxObj = {...fxObj}

newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
    console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18
console.log(fxObj.nature) //  ["smart", "good", "why"]
console.log(fxObj['names']) // {name1: "why fx", name2: "xka"}
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}      

例子:

var person = {
    name: 'tt',
    age: 18,
    friends: ['oo', 'cc', 'yy']
}

function shallowCopy(source) {
    if (!source || typeof source !== 'object') {
        throw new Error('error');
    }
    var targetObj = source.constructor === Array ? [] : {};
    for (var keys in source) {
        if (source.hasOwnProperty(keys)) {
            targetObj[keys] = source[keys];
        }
    }
    return targetObj;
}

var p1 = shallowCopy(person);

console.log(p1)      

在上面的代碼中,我們建立了一個 ​

​shallowCopy​

​ 函數,它接收一個參數也就是被拷貝的對象。

  • 首先建立了一個對象
  • 然後​

    ​for...in​

    ​​ 循環傳進去的對象,為了避免循環到原型上面會被周遊到的屬性,使用​

    ​hasOwnProperty​

    ​ 限制循環隻在對象自身,将被拷貝對象的每一個屬性和值添加到建立的對象當中
  • 最後傳回這個對象

通過測試,我們拿到了和 ​

​person​

​​ 對象幾乎一緻的對象 ​

​p1​

​​。看到這裡,你是不是會想那這個結果和 ​

​var p1 = person​

​ 這樣的指派操作又有什麼差別呢?

var p2 = person;

// 這個時候我們修改person對象的資料
person.name = 'tadpole';
person.age = 19; 
person.friends.push('tt')

p2.name // tadpole
p2.age // 19
p2.friends // ["oo", "cc", "yy", "tt"]

p1.name // tt
p1.age // 18
p1.friends // ["oo", "cc", "yy", "tt"]      

上面我們建立了一個新的變量 ​

​p2​

​​ ,将 ​

​person​

​​ 指派給 ​

​p2​

​ ,然後比較兩個變量

JavaScript的淺拷貝和深拷貝

深拷貝:

淺拷貝由于隻是複制一層對象的屬性,當遇到有子對象的情況時,子對象就會互相影響。是以,深拷貝是對對象以及對象的所有子對象進行拷貝

方法一:用​

​JSON.stringify​

​把對象轉成字元串,再用​

​JSON.parse​

​把字元串轉成新的對象。

缺點:            

        1、會忽略 ​​

​undefined​

2、會忽略 ​

​symbol​

3、不能序列化函數,,無法拷貝函數

4、不能解決循環引用的對象   const a = {val:2};   a.target = a; 拷貝a會出現系統棧溢出,因為出現了​

​無限遞歸​

​的情況

5、不能正确處理RegExp, Date, Set, Map等

6、不能處理正則

7、會抛棄對象的constructor。也就是深拷貝之後,不管這個對象原來的構造函數是什麼,在深拷貝之後都會變成Object。

var fxObj = {
    age: 18,
    why: undefined,
    why1: Symbol('why1'),
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newFxObj = JSON.parse(JSON.stringify(fxObj))
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'

newFxObj.love = function () {
    console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18
console.log(fxObj.nature) // ["smart", "good"]
console.log(fxObj['names']) // {name1: "fx", name2: "xka"}
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}
console.log(newFxObj['love']) // undefined function沒辦法轉成JSON。
console.log(newFxObj) // {age: 8, nature: Array(3), names: Object, love: function} why why1 love 都會被忽略      

循環引用情況下,會報錯。

let obj = {
    a: 1,
    b: {
        c: 2,
        d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON      

方法二:循環遞歸

function deepClone(initalObj, finalObj) {
    var obj = finalObj || {};
    for (var i in initalObj) {
        var prop = initalObj[i]; // 避免互相引用對象導緻死循環
        if(prop === obj) {
            continue;
        }
        if (typeof prop === 'object') {
            obj[i] = (prop.constructor === Array) ? [] : {};
            arguments.callee(prop, obj[i]);
        } else {
            obj[i] = prop;
        }
    }
    return obj;
}
var fxObj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newFxObj = deepClone(fxObj);
newFxObj.age = 8
newFxObj.names.name1 = 'newfx'
console.log(fxObj)
console.log(newFxObj)      

輸出

JavaScript的淺拷貝和深拷貝

方法三:jquery 和 zepto 裡的 $.extend 方法可以用作深拷貝

var $ = require('jquery');
var newObj = $.extend(true, {}, obj);      
function deepCopy(source){
   if(!source || typeof source !== 'object'){
     throw new Error('error');
   }
   var targetObj = source.constructor === Array ? [] : {};
   for(var keys in source){
      if(source.hasOwnProperty(keys)){
         if(source[keys] && typeof source[keys] === 'object'){
           targetObj[keys] = source[keys].constructor === Array ? [] : {};
           targetObj[keys] = deepCopy(source[keys]);
         }else{
           targetObj[keys] = source[keys];
         }
      } 
   }
   return targetObj;
}
var obj1 = {
    arr: [1, 2, 3],
    key: {
        id: 22
    },
    func: function() {
        console.log(123)
    }
}

var obj2 = deepCopy(obj1);

obj1.arr.push(4);

obj1.arr // [1, 2, 3, 4]
obj2.arr // [1, 2, 3]
obj1.key === obj2.key // false
obj1.func === obj2.func // true      

繼續閱讀