1. 程式人生 > 程式設計 >詳解JS變數儲存深拷貝和淺拷貝

詳解JS變數儲存深拷貝和淺拷貝

變數型別與儲存空間

棧記憶體和堆記憶體

詳解JS變數儲存深拷貝和淺拷貝

基本資料型別

string、number、null、undKQgoGnuefined、boolean、symbol(ES6新增) 變數值存放在棧記憶體中,可直接訪問和修改變數的值
基本資料型別不存在拷貝,好比如說你無法修改數值1的值

引用型別

Object Function RegExp Math Date 值為物件,存放在堆記憶體中
在棧記憶體中變數儲存的是一個指標,指向對應在堆記憶體中的地址。
當訪問引用型別的時候,要先從棧中取出該物件的地址指標,然後再從堆記憶體中取得所需的資料

圖解儲存空間

let a1 = 0; // 棧記憶體
let a2 = "this is string" // 棧記憶體
let a3 = null; // 棧記憶體
let b = { x: 10 }; // 變數b存在於棧中,{ x: 10 }作為物件存在於堆中
let c = [1,2,3]; // 變數c存在於棧中,[1,3]作為物件存在於堆中

詳解JS變數儲存深拷貝和淺拷貝

引用型別的賦值

let a = { x: 10,程式設計客棧 y: 20 }
let b = a;
b.x = 5;
console.log(a.x); // 5

詳解JS變數儲存深拷貝和淺拷貝

深拷貝和淺拷貝

深拷貝

將一個物件從記憶體中完整的拷貝一份出來,從堆記憶體中開闢一個新的區域存放新物件,且修改新物件不會影響原物件

淺拷貝

淺拷貝是按位拷貝物件,它會建立一個新物件,這個物件有著原始物件屬性值的一份精確拷貝。如果屬性是基本型別,拷貝的就是基本型別的值;如果屬性是記憶體地址(引用型別),拷貝的就是記憶體地址

物件的賦值

當我們把一個物件賦值給一個新的變數時,賦的其實是該物件的在棧中的地址,而不是堆中的資料。也就是兩個物件指向的是同一個儲存空間,無論哪個物件發生改變,其實都是改變的儲存空間的內容,因此,兩個物件是聯動的。

三者對比

詳解JS變數儲存深拷貝和淺拷貝

淺拷貝的常用的五種方法

Object.assign()

Object.assign() 方法可以把任意多個的源物件自身的可列舉屬性拷貝給目標物件,然後返回目標物件。但是 Object.assign()進行的是淺拷貝

Object.assign 會從左往右遍歷源物件(sources)的所有屬性,然後用 = 賦值到目標物件(target)

var obj = { a: {a: "kobe",b: 39},b:1 };
        var initalObj = Object.assign({},obj);
        initalObj.a.a = "wade";
        initalObj.b = 2;
        console.log(obj.a.a); //wade
        console.log(obj.b); //1

擴充套件運算子

let obj = {a:1,b:{c:1}}
let obj2 = {...KQgoGnuobj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

Array.prototype.slice

slice() 方法返回一個新的陣列物件,這一物件是一個由 begin和 end(不包括end)決定的原陣列的淺拷貝。原始陣列的基本型別不會被改變,引用型別會被改變。

let arr = [1,3,{
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[0]=0;
arr3[2].username = 'wade'
console.log(arr);

Array.prototype.concawww.cppcns.comt()

let arr = [1,{
    username: 'kobe'
    }];
let arr2=arr.concat();   
arr3[0]=0;
arr2[2].username = 'wade';
console.log(arr);

手寫淺拷貝

function shallowCopy(src) {
    var dst = {};
    for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
            dst[prop] = src[prop];
        }
    }
    return dst;
}

深拷貝的常用方法

jsON.parse(jsON.stringify())

通過JSON.stringify實現深拷貝有幾點要注意

拷貝的物件的值中如果有函式,undefined,symbol則經過JSON.stringify()序列化後的JSON字串中這個鍵值對會消失

無法拷貝不可列舉的屬性,無法拷貝物件的原型鏈

拷貝Date引用型別會變成字串

拷貝RegExp引用型別會變成空物件

物件中含有NaN、Infinity和-Infinity,則序列化的結果會變成null

無法拷貝物件的迴圈應用(即obj[key] = obj)

let arr = [1,{
    username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan'; 
console.log(arr,arr4)

手寫乞丐版深拷貝

首先這個deepClone函式並不能複製不可列舉的屬性以及Symbol型別

這裡只是針對Object引用型別的值做的迴圈迭代,而對於Array,Date,RegExp,Error,Function引用型別無法正確拷貝

物件成環,即迴圈引用 (例如:obj1.a = obj)

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

皇帝版深拷貝

該例項來自ConardLi大佬的github,源地址:https://github.com/ConardLi/

const mapTag = "[object Map]";
    const setTag = "[object Set]";
    const arrayTag = "[object Array]";
    const objectTag = "[object Object]";
    const argsTag = "[object Arguments]";

    const boolTag = "[object Boolean]";
    const dateTag = "[object Date]";
    const numberTag = "[object Number]";
    const stringTag = "[object String]";
    const symbolTag = "[object Symbol]";
    const errorTag = "[object Error]";
    const regexpTag = "[object RegExp]";
    const funcTag = "[object Function]";

    const deepTag = [mapTag,setTag,arrayTag,objectTag,argsTag];

    function forEach(array,iteratee) {
      let index = -1;
      const length = array.length;
      while (++index < length) {
        iteratee(array[index],index);
      }
      return array;
    }

    function isObject(target) {
      const type = typeof target;
      return target !== null && (type === "object" || type === "function");
    }

    function getType(target) {
      return Object.prototype.toString.call(target);
    }

    function getInit(target) {
      const Ctor = target.constructor;
      return new Ctor();
    }

    function cloneSymbol(targe) {
      return Object(Symbol.prototype.valueOf.call(targe));
    }

    function cloneReg(targe) {
      const reFlags = /\w*$/;
      const result = new targe.constructor(targe.source,reFlags.exec(targe));
      result.lastIndex = targe.lastIndex;
      return result;
    }

    function cloneFunction(func) {
      const bodyReg = /(?<={)(.|\n)+(?=})/m;
      const paramReg = /(?<=\().+(?=\)\s+{)/;
      const funcString = func.toString();
      if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
          if (param) {
            const paramArr = param[0].split(",");
            return new Function(...paramArr,body[0]);
          } else {
            return new Function(body[0]);
          }
        } else {
          return null;
        }
      } else {
        return eval(funcString);
      }
    }

    function cloneOtherType(targe,type) {
      const Ctor = targe.constructor;
      switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
          return new Ctor(targe);
        case rege程式設計客棧xpTag:
          return cloneReg(targe);
        case symbolTag:
          return cloneSymbol(targe);
        case funcTag:
          return cloneFunction(targe);
        default:
          return null;
      }
    }

    function clone(target,map = new WeakMap()) {
      // 克隆原始型別
      if (!isObject(target)) {
        return target;
      }

      // 初始化
      const type = getType(target);
      let cloneTarget;
      if (deepTag.includes(type)) {
        cloneTarget = getInit(target,type);
      } else {
        return cloneOtherType(target,type);
      }

      // 防止迴圈引用
      if (map.get(target)) {
        return map.get(target);
      }
      map.set(target,cloneTarget);

      // 克隆set
      if (type === setTag) {
        target.forEach(value => {
          cloneTarget.add(clone(value,map));
        });
        return cloneTarget;
      }

      // 克隆map
      if (type === mapTag) {
        target.forEach((value,key) => {
          cloneTarget.set(key,clone(value,map));
        });
        return cloneTarget;
      }

      // 克隆物件和陣列
      const keys = type === arrayTag ? undefined : Object.keys(target);
      forEach(keys || target,(value,key) => {
        if (keys) {
          key = value;
        }
        cloneTarget[key] = clone(target[key],map);
      });

      return cloneTarget;
    }

    const map = new Map();
    map.set("key","value");
    map.set("ConardLi","code祕密花園");

    const set = new Set();
    set.add("ConardLi");
    set.add("code祕密花園");

    const target = {
      field1: 1,field2: undefined,field3: {
        child: "child"
      },field4: [2,4,8],empty: null,map,set,bool: new Boolean(true),num: new Number(2),str: new String(2),symbol: Object(Symbol(1)),date: new Date(),reg: /\d+/,error: new Error(),func1: () => {
        console.log("code祕密花園");
      },func2: function(a,b) {
        return a + b;
      }
    };

    const result = clone(target);

    console.log(target);
    console.log(result);

以上就是詳解JS變數儲存深拷貝和淺拷貝的詳細內容,更多關於JS變數儲存深拷貝和淺拷貝的資料請關注我們其它相關文章!