JS中深拷貝和淺拷貝

1、棧和堆

JS引擎中對變量的存儲主要有兩種,棧內存堆內存

  • 棧內存:主要存儲JS中的基本數據類型的變量,包括String、Number、Boolean、undefined、Null、 Symbol和對象變量的指針。棧內存中的變量一般都是已知大小或者有範圍上限的,所以棧內存都是自動分配內存和自動釋放內存。
  • 堆內存:主要存儲JS中的引用數據類型,包括Object、Array、function,他們的地址指針是存儲於棧中的。堆內存中的變量大小一般是不知道的,所以堆內存是動態分配內存,內存大小不一,也不會自動釋放內存。

從棧和堆的概念我們也可以分析出const 、let 定義的變量不能二次定義了?每次使用const或者let 去初始化一個變量的時候,會首先遍歷當前的棧內存,看看有沒有重名的變量,有的話就返回錯誤。
const 定義的基本數據類型不能修改,而const定義的引用數據類型就可以修改了? 因爲我們定義一個const對象的時候,我們說的常量其實是棧中的指針,就是const 對象對應的堆內存的這個指針是不變的,但是堆內存中的數據本身的大小或者屬性是可變的,而對於const定義的基礎變量而言,這個值就相當於const對象的指正,是不可變的。

// const 定義的對象
function fn(){
  const c={name:'9'}
  c.age='20'
  c.name="90"
  console.log("數據",c)
}
fn() // 數據 {name: "90", age: "20"}
//const 定義的基礎數據類型
function fn(){
 const a = 9
 a = 10
return a 
}
fn() //VM1949:3 Uncaught TypeError: Assignment to constant variable.

2、賦值和淺拷貝的區別

  • 賦值:當我們把一個對象賦值給一個新的變量時,賦的其實是該對象的在棧中的地址,而不是堆中的數據。也就是兩個對象指向的是同一個存儲空間,無論哪個對象發生改變,其實都是改變的存儲空間的內容,因此,兩個對象是聯動的。
  • 淺拷貝是按位拷貝對象,它會創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內存地址(引用類型),拷貝的就是內存地址 ,因此如果其中一個對象改變了這個地址,就會影響到另一個對象。即默認拷貝構造函數只是對對象進行淺拷貝複製(逐個成員依次拷貝),即只複製對象空間而不復制資源。

看個例子:

// 給對象賦值
var obj1={
	name:'張三',
	age:400,
	like:{paly:'玩'}
}
var obj2 = obj1
obj2.name='李四'
obj2.like.paly="喫"
console.log(obj1,obj2)

在這裏插入圖片描述

// 淺拷貝
var obj1={
	name:'張三',
	age:400,
	like:{paly:'玩'}
}
function copy(obj){
 var objs={}
 for(var item in obj){
   objs[item]=obj[item]
  }
return objs
}
var obj3 = copy(obj1)
obj3.name="老張"
obj3.like.paly="滿"
console.log(obj1,obj3)

在這裏插入圖片描述
從上述例子我們可以得出,賦值得到的新對象obj2改變原始對象obj1也會發生改變,淺拷貝出來的新對象obj3,只要第一層是基本數據類型的值,新對象改變它,原始對象不會發生改變,但是原始對象的第一層屬性是個對象的話,obj3改變這個對象,原始對象的數據也會發生改變。所以得出 淺拷貝只能拷貝第一層基本數據類型的值,改變他們的值不會影響原始對象,但是原始對象中含有對象的話,改變新對象中的對象,原始對象中的對象也會發生改變

3、淺拷貝 和 深拷貝 的實現

淺拷貝和深拷貝只針對Object 和Array這樣的引用數據類型的。淺拷貝只複製指向某個對象的指針,而不復制對象本身,新舊對象還是共指向一塊內存,新對象改變舊對象也會隨着改變。但是深拷貝會另外創建一個一模一樣的對象,新對象跟舊對象不共享一塊內存,所以修改新對象,舊對象也不會發生改變

(1)淺拷貝的實現方式
  • Object.assign()
    Object.assign()可以把任意多個的源對象自身的可枚舉的屬性拷貝拷貝給目標對象,然後返回目標對象,但是Object.assign()進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。但是如果對象只有一層的時候,是深拷貝
var obj3={
	name:'張三',
	age:400
}
var objAssign = Object.assign(obj3)
objAssign.name='李四'
objAssign.age = 900
console.log(obj3) // {name:'張三',age:400}
console.log(objAssign) // {name:'李四',age:900}
  • Array.prototype.concat()
    這個方方法是將對個數組組合成一個新數組,返回一個新數組,跟Object.assign()類似,只拷貝引用不拷貝對象本身。
let arr = [1, 3, {
    username: '科比'
    }];
let arr2=arr.concat();    
arr2[2].username = '我的';
console.log(arr);

在這裏插入圖片描述

var arr1=[1,2,34,4]
var arr2 = [99999]
var arr3 = arr1.concat(arr2)
arr3[2] = 88888

在這裏插入圖片描述

  • Array.prototype.slice()
    slice() 方法返回一個新的數組對象,這一對象是一個由 begin 和 end 決定的原數組的淺拷貝(包括 begin,不包括end)。原始數組不會被改變。
let arr = [1, 3, {
    username: ' 科比'
    }];
let arr3 = arr.slice();
arr3[2].username = '我的'
console.log(arr);

在這裏插入圖片描述

(2)深拷貝的實現方式
  • JSON.parse(JSON.stringify())
    用JSON.stringify將對象轉成JSON字符串,再用JSON.parse把字符串解析成對象,一去一來,新的對象產生了,而且對象會開闢新的棧,實現深拷貝。
	let arr = [666, 888, {
	    username: ' 999'
	}];
	let arr2 = JSON.parse(JSON.stringify(arr));
	arr2[2].username = '100'; 
	console.log(arr, arr2)

在這裏插入圖片描述
缺點: 不能處理函數和正則,因爲這兩者基於JSON.stringify和JSON.parse處理後,得到的正則就不在是正則(變爲空對象),得到的函數不在是函數(變爲null)

let arr = [1,2,{name:'嘿嘿'},function(){}]
let arr3 = JSON.parse(JSON.stringify(arr))
arr3[2].name = '哈哈哈'
console.log(arr, arr3)

在這裏插入圖片描述
這是因爲JSON.stringify() 方法是將一個JavaScript值(對象或者數組)轉換爲一個 JSON字符串,不能接受函數。

  • 手寫遞歸的方法
    遞歸實現深度克隆的原理:遍歷對象、數組知道里邊都是基本數據類型,然後再去複製,就是深度拷貝。
//定義檢測數據類型的功能函數
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
//實現深度克隆---對象/數組
function clone(target) {
  //判斷拷貝的數據類型
  //初始化變量result 成爲最終克隆的數據
  let result,
    targetType = checkedType(target)
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }
  //遍歷目標數據
  for (let i in target) {
    //獲取遍歷數據結構的每一項值。
    let value = target[i]
    //判斷目標結構裏的每一值是否存在對象/數組
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
      //對象/數組裏嵌套了對象/數組
      //繼續遍歷獲取到value值
      result[i] = clone(value)
    } else {
      //獲取到value值是基本的數據類型或者是函數。
      result[i] = value
    }
  }
  return result
}

  • 函數庫lodash
    該函數庫也有提供_.cloneDeep用來做 Deep Copy
var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

本文參考文章:
淺拷貝與深拷貝
JS中的棧內存和堆內存
覺得他們寫的很好,很容易懂,記錄下來以防自己忘記。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章