owned this note
owned this note
Published
Linked with GitHub
# 【JS30】Day14 - Object and Arrays - Reference VS
###### tags: `深拷貝`、`淺拷貝`、`陣列方法`、`Reference`、`Copy`
以下會介紹幾個陣列方法(搭配ES6),接觸到淺拷貝的特性(然後就會順便學習深拷貝...),以及"傳參考(reference)"的特性。
範例影片一開始提到,下方程式範例中,```let age2 = age;```,但是後來更改age的值,age2卻沒有改變。這樣的特性也適用於字串。**但是在陣列中,卻有不同的作用!**
```javascript
let age = 100;
let age2 = age;
console.log(age, age2); //100,100
age = 200;
console.log(age, age2); //200,100
let name = 'Wes'; //字串範例
let name2 = name;
console.log(name, name2); //Wes,Wes
name = 'wesley';
console.log(name, name2); //wesley,Wes
```
在陣列中,會有這樣的特性。
首先,將players陣列用const命名為team,接著,去改變team內的內容,結果players也跟著改變!**這是因為team是reference,而不是陣列!**
```javascript
const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
const team = players;
console.log(players, team);
team[3] = 'Lux'; //這樣會改變原本player陣列的內容
```
那該怎麼做,才不會改變到原有陣列的內容?
<br />
# 陣列的拷貝
## slice()
slice() 方法會回傳一個新陣列物件,為原陣列選擇之 begin 至 end(不含 end)部分的**淺拷貝**(shallow copy)。而**原本的陣列將不會被修改**。
```javascript
const team2 = players.slice(); //影片範例
```
```javascript
//此為MDN範例
var animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]
console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]
console.log(animals.slice(1, 5));
// expected output: Array ["bison", "camel", "duck", "elephant"]
```
<br><br/>
## concat()
concat() 方法被用來合併兩個或多個陣列。**此方法不會改變現有的陣列**,回傳一個包含呼叫者陣列本身的值,作為代替的是回傳一個新陣列。
```javascript
var array1 = ['a', 'b', 'c'];
var array2 = ['d', 'e', 'f'];
console.log(array1.concat(array2));
// expected output: Array ["a", "b", "c", "d", "e", "f"]
```
<br><br/>
## [...]展開其餘
展開其餘(Spread)是ES6的寫法,也是**淺拷貝**的一種,上述的concat可以合併陣列,影片範例也提到可以用ES6的展開其餘(Spread),像是下方程式碼中```const team4 = [...players];```,players陣列內的項目被賦值到team4的陣列內。
```team4[3] = 'heeee hawww';```只是把team4陣列內的第4個項目改變為```'heeee hawww'```,此舉不會改變原本players陣列內的內容。
```javascript
// or create a new array and concat the old one in
const team3 = [].concat(players);
// or use the new ES6 Spread
const team4 = [...players];
team4[3] = 'heeee hawww';
console.log(team4);
```
<br><br/>
## Array.from()
顧名思義,Array.from()即是:從某地方開始變成陣列內的項目。下方程式碼中,就是把players陣列內的內容變成team5陣列的內容。
```javascript
const team4 = [...players];
const team5 = Array.from(players);
```
---
<br><br/>
> **這樣的情形,也適用於物件(object)唷!**(指的是拷貝會改變原陣列內容的情形。)
# 物件的拷貝
```javascript
// with Objects
const person = {
name: 'Wes Bos',
age: 80 //這時的person沒有number
};
// and think we make a copy:
const captain = person;
captain.number = 99; //這時的person多了一個number是99
```
上述範例改變了原本陣列的內容,想要淺拷貝,必須要像下方那樣做。
```javascript
// how do we take a copy instead?
const cap2 = Object.assign({}, person, { number: 99, age: 12 });
console.log(cap2);
//這時cap2多了一個number是99,也有原本person的內容,但person物件內容沒有被改變
```
等等!person明明是用const去定義的,const命名不是不可以再次被更改的嗎?
沒有錯,const的命名不可以再次被更改。但是上述範例卻更改了person,讓person多了一個number是99。
這是因為,const命名不能改變,是"名字(person)"不能改變,物件內容的改變方式可以從上方範例學習。
> 總而言之,陣列跟物件都有不只傳參考的特性。拷貝時,必須小心。
<br><br/>
類似陣列的拷貝方式,物件也可以利用展開其餘(spread)來拷貝唷!
```javascript
const cap3 = {...person};
// Things to note - this is only 1 level deep - both for Arrays and Objects.
lodash has a cloneDeep method, but you should think twice before using it.
```
上方程式碼是介紹淺拷貝,也就是**只拷貝第一層,且不影響原陣列**。這樣會有小狀況要注意。請看下方程式碼。
```javascript
const wes = {
name: 'Wes',
age: 100,
social: {
twitter: '@wesbos',
facebook: 'wesbos.developer'
}
};
const dev = Object.assign({}, wes); //這時候dev得到wes的物件內容。
```
```javascript
dev.name = "wesley" //這時dev的name被改變,但wes的name沒有改變。
```
到這邊都跟預想的一樣,沒有問題。
但是改變物件的第2層內容呢?下圖顯示,改變dev的第2層內容,wes的第2層內容跟著改變啦!因為是淺拷貝的關係。
![](https://i.imgur.com/NUY6PH1.png)
<br/>
但該怎麼改變dev的第2層物件內容,又不影響wes本身呢?作者用了下方的手法。(高招呢!)
或是使用**深拷貝**的技巧。
![](https://i.imgur.com/wJS5odT.png)
<br><br/>
---
# 深拷貝、淺拷貝
* 淺拷貝
只複製指向某個物件的指標
而不複製物件本身
新舊物件還是共用同一塊記憶體
* 深拷貝
深拷貝會另外創造一個一模一樣的物件
新物件跟原物件不共用記憶體
修改新物件不會改到原物件