JavaScript 的參數傳遞

1. 傳值 & 傳址

call by value 傳值 (原始型別的特性)

  • 每個宣告的變數的值(value)都會儲存在不同的記憶體空間
  • 使用容量較小,原始型別的資料儲存在 Stack 內
  • 複製變數時,會完全複製一份新的「值 (value)」
let a = 6let b = aa = 9console.log(b) // 6

Stack

宣告的變數儲存的值記憶體空間
a6空間 A
b6空間 B
  • 使用 == , === 比較時,是比較值是否相同
let a = 6let b = aconsole.log(a == b) //trueconsole.log(a === b) //true
  • 函式接收到的參數值是原始值的一個複製
  • 當函式修改參數的值時,原始值不會被改變
let age = 18function increase(age) {  age++}increase(age)console.log(age) // 18;

call by reference 傳址 (物件型別的特性)

  • 儲存資料值所在的記憶體位置的地址 (address)
  • 物件類別的資料通常較為複雜,佔用記憶體容量較大,會儲存在 Heap 內
  • 複製變數時,是複製一份新的「地址 (address)」,並非複製值 (value)
  • 注意重點: 原物件是否會被修改
//因為是儲存記憶體位置,所以用const宣告的陣列可以被修改const arr = [55, 66]arr.push(77)//值會被變更console.log(arr) // [55,66,77]

Stack

宣告的變數儲存的值
arr記憶體位址 0x00

Heap

記憶體位址
0x0055,66,77
  • 相同的地址會指向同樣的值
// object example//x有一個記憶體位址 0x01 指向儲存的值 {value:10}let x = { value: 10 }//y有同一個記憶體位址 0x01 指向相同的值let y = xx.value = 20console.log(x) // {value: 20}console.log(y) // {value: 20}//array examplelet animals = ['zebra', 'bear', 'deer']let animals2 = animalsanimals2.push('lion')console.log(animals) // ['zebra','bear','deer','lion']

Stack

宣告的變數儲存的值
x記憶體位址 0x01
y記憶體位址 0x01
animals記憶體位址 0x02
animals2記憶體位址 0x02

Heap

記憶體位址
0x01{ value: 20}
0x02'zebra' ,'bear', 'deer', 'lion'
  • 使用 == , === 比較時,是比較記憶體位址是否相同
//記憶體位址不同console.log([] == []) //falseconsole.log([10] == [10]) //falseconsole.log(['hello', 'bye'] === ['hello', 'bye']) // false//有相同的值和記憶體位址let arr1 = [1, 2, 3] //0x01let arr2 = arr1 //0x01console.log(arr1 == arr2) //trueconsole.log(arr1 === arr2) //true//有相同的值,但記憶體位址不同let arr3 = [4, 5, 6] //0x01let arr4 = [4, 5, 6] //0x02console.log(arr3 == arr4) //falseconsole.log(arr3 === arr4) //false

2. 淺拷貝(Shallow Copy) & 深拷貝(Deep Copy)

  • 因為物件類別有 call by reference 特性,如果不想修改原物件,可以使用一些方法來複製一份
  • 例如想要新增物件屬性,但希望保留原本的物件資料時

淺拷貝

  • 只複製物件第一層屬性 (primitive type)
  • 修改物件第一層屬性的值不會影響原物件
  • 若有第二層以上結構時 (reference type),位址仍然相同,指向同樣的值
  • 修改物件第二層屬性的值會影響原物件
  1. 方法 1:展開運算子 (Spread Operator)
//範例:複製物件const jacket = { brand: 'UNIQLO', color: 'navy' }const Jacket2 = { ...jacket }console.log(newJacket) // { brand: 'UNIQLO', color: 'navy' }//範例:複製陣列const dinner = ['pizza', 'hamburger', 'sandwich']const dinner2 = [...dinner]console.log(dinner2) // ['pizza','hamburger','sandwich']
  1. 方法 2: Object.assign()
//語法Object.assign(target, ...sources)//範例:複製物件const obj = { a: 1 }const copy = Object.assign({}, obj)console.log(copy) // { a: 1 }
  1. 方法 3: Array.prototype.slice()
  • 會回傳一個新陣列物件
  • 為原陣列選擇之 begin 至 end(不含 end)部分的淺拷貝(shallow copy)
  • 原本的陣列不會被修改
//語法arr.slice([begin[, end]])const drinks = ['tea','coke','milk']//寫法1: 不給參數,會全部複製一份const mydrinks = drinks.slice()console.log(mydrinks) // ['tea', 'coke', 'milk']//寫法2:小括號內放0,結果同上const mydrinks = drinks.slice(0)console.log(mydrinks) // ['tea', 'coke', 'milk']
  1. 範例
  • 如果物件內有 reference type(Object,Array...),是複製其位址,仍指向相同的值
//example1//改變第一層屬性的值,不影響原物件const car1 = {  brand: 'tesla',  color: 'white',  detail: {    model: 'ModelX',    year: '2023'  }}const car2 = Object.assign({}, car1)car2.color = 'black'console.log(car1)/*{  brand: 'tesla',  color: 'white',  detail: { model: 'ModelX', year: '2023' }}*/console.log(car2)/*{  brand: 'tesla',  color: 'black',  detail: { model: 'ModelX', year: '2023' }}*///example2//改變第二層物件的值,連原物件也一起改變let car2 = Object.assign({}, car1)car2.detail.model = 'Model3'//因為car1和car2內的detail共享同個位址//當改變car2的detail內model的值時,car1也會同樣被改變console.log(car1)/*{  brand: 'tesla',  color: 'white',  detail: { model: 'Model3', year: '2023' }}*/console.log(car2)/*{  brand: 'tesla',  color: 'white',  detail: { model: 'Model3', year: '2023' }}*/

深拷貝

  • 深度複製指定物件,操作新物件不影響原物件
  • 兩者指向不同記憶體位址

方法 1: 使用JSON.stringify/parse

//深拷貝方法 JSON.stringify/parseconst myBand = {  name: 'The Strokes',  members: {    vocal: 'Julian',    guitarist1: 'Albert',    guitarist2: 'Nick',    bassist: 'Nikolai',    drummer: 'Fabrizio'  }}//使用JSON.stringify將myBand轉成字串//再透過JSON.parse將字串轉回物件const newBand = JSON.parse(JSON.stringify(myBand))console.log(myBand === newBand) // falsenewBand.members.vocal = 'Kurt'// myBand不會被改變console.log(myBand)/*{  name: 'The Strokes',  members: {    vocal: 'Julian',    guitarist1: 'Albert',    guitarist2: 'Nick',    bassist: 'Nikolai',    drummer: 'Fabrizio'  }}*/console.log(newBand)/*{  name: 'The Strokes',  members: {    vocal: 'Kurt',    guitarist1: 'Albert',    guitarist2: 'Nick',    bassist: 'Nikolai',    drummer: 'Fabrizio'  }}*/

方法 2: 使用structuredClone

  • JavaScript 的內建方法
const myBand = {  name: 'The Strokes',  members: {    vocal: 'Julian',    guitarist1: 'Albert',    guitarist2: 'Nick',    bassist: 'Nikolai',    drummer: 'Fabrizio'  }}const yourBand = structuredClone(myBand)console.log(yourBand === myBand) // falseyourBand.members.drummer = 'Ringo'console.log(myBand)/*{  name: 'The Strokes',  members: {    vocal: 'Julian',    guitarist1: 'Albert',    guitarist2: 'Nick',    bassist: 'Nikolai',    drummer: 'Fabrizio'  }}*/console.log(yourBand)// {//   "name": "The Strokes",//   "members": {//     "vocal": "Julian",//     "guitarist1": "Albert",//     "guitarist2": "Nick",//     "bassist": "Nikolai",//     "drummer": "Ringo"//   }// }

參考資料

# JavaScript