JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks、No Compilers、No Libraries、No Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。
另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。
瞭解 JavaScript 的 Passed by Value
和 Passed by Reference
以及 Object 的 shallow copy
和 deep copy
。
在 JavaScript 裡,如果變數的資料型態屬於 Primitive(原生型別),則在傳遞變數時,採用的是 Passed by Value (傳遞值)。
在下面的兩個例子,我們分別將age
、name
傳遞給age2
、name2
,之後改變age2
、name2
的數值。結果原本的age
、name
均不會受到影響,因為在傳遞過程是 Passed by Value,將原本存於age
、name
的值複製一份給age2
、name2
。
let age = 100;
let age2 = age;
console.log(age,age2);
age = 200;
console.log(age,age2);
let name = 'Mes';
let name2 = name;
console.log(name,name2);
name = 'wesley';
console.log(name,name2);
在 JavaScript 裡,如果變數的資料型態屬於 Object(物件型別),則在傳遞變數時,採用的是 Passed by Reference (傳遞位址)。
下面的例子,我們將陣列 players
傳遞給常數 team
,之後將team[3]
(Poppy)改成Lux
,再把兩個陣列都印到 console,會發現到兩個陣列的第四個位置都被修改成新的值(Lux)。
這是因為物件在傳遞的過程中採取的是 Passed by Reference,也就是將物件在記憶體上的位置傳遞給另一個物件(兩者共享同一個記憶體位置),所以只要更改其中一方,另一方也會受到影響。
const players = ['Wes','Sarah','Ryan','Poppy'];
const team = players;
console.log(players,team);
team[3] = 'Lux';
console.log(players,team);
如果想修改陣列的值又不影響另一個陣列的話,我們可以使用slice()
來複製陣列並回傳新陣列(擁有自己的記憶體空間)。
下面的例子,我們呼叫players.slice()
將陣列players
完整的複製一份到新陣列中並傳遞給team2
,之後再修改team2
的第四個位置,這次我們發現到原來的陣列players
並沒有被修改到。
注意:slice()
進行的是shallow copy
(淺複製)!!!
const team2 = players.slice()
team2[3] = 'Lux';
console.log(players,team2)
其他修改陣列的值又不影響另一個陣列的方法有:
slice()
一樣會回傳串聯後的新陣列。const team3 = [].concat(players);
//use the new ES6 Spread
const team4 = [...players];
const team5 = Array.from(players);
另一個例子,我們宣告物件person
並把它傳遞給captain
,在captain
上新增number: 90
後將兩個物件都印到 consle,此時會發現在captain
新增的number: 90
,也會被加到person
上。會這樣是因為物件在傳遞過程是 Passed by Reference,兩物件同時指向一個記憶體空間。
const person = {
name: 'Wes Bos',
age: 80
};
const captain = person;
captain.number = 99;
console.log(person,captain);
如果不想修改到另一個物件的話,我們可以使用Object.assign(target, ...sources)
來複製一個或多個物件的屬性到另一個目標物件,最後回傳目標物件。
下面我們利用Object.assign({},person,{number: 99,age:12});
,將物件person
和{number:99,age:12}
的屬性複製到目標物件({ })。
如果複製的多個物件的屬性有重複,以後面物件的屬性為準進行合併。舉例來說age
是重複出現的屬性,最後複製屬性值時,要以後面出現的age:12
為準合併物件。
注意 : Object.assign()
做的是 shallow copy(淺複製),如果要複製的物件屬性包含子物件,就會複製到子物件的參照(reference)!!!。
const person = {
name: 'Wes Bos',
age: 80
};
const cap2 = Object.assign({},person,{number: 99,age:12});
console.log(person,cap2);
原來的person
並沒有被改動到。
前面我們有提到,Object.assign()
做的是 shallow copy(淺複製),若要複製的物件屬性包含子物件,就會複製到子物件的參照。
所以只要去改動新物件的子物件屬性值,就會連帶影響原本複製的物件子屬性值。
下面我們用Object.assign()
複製來源物件(wes
)的屬性到目標物件({}),再將目標物件傳遞給dev
物件,要留意wes
的屬性包含子物件屬性(social
)。
例子中我們修改複製來的物件屬性(name
)和子物件屬性(social.twitter
),之後把wes
、dev
物件印到 console 會發現複製的來源物件wes
的屬性(name
)不受影響,但wes
的子物件屬性(social.twitter
)卻被影響。
const wes = {
name: 'Wes',
age: 100,
social: {
twitter: '@wesbos',
facebook: 'wesbos.developer'
}
}
//shallow copy
const dev = Object.assign({},wes);
dev.name = 'Wesley';
console.log(wes,dev);
dev.social.twitter = '@coolman';
console.log(wes.social,dev.social);
如果不想改動複製來源物件的子屬性值(social.twitter
),我們可以將要複製的物件用JSON.stringify()
先轉換成 JSON String(因為是Primitive,所以是 Passed by Value),再用JSON.parse()
把它還原成物件再回傳給dev2
。
下面我們一樣修改子物件的屬性值,但這次的原物件子屬性值並沒有受到影響。
const wes = {
name: 'Wes',
age: 100,
social: {
twitter: '@wesbos',
facebook: 'wesbos.developer'
}
}
// deep copy
const dev2 = JSON.parse(JSON.stringify(wes));//先轉成string再換回object
dev2.social.twitter = '@coolman';
console.log(wes.social,dev2.social);
JavaScript: var, let, const 差異
Array.prototype.concat()
Spread syntax (...)
Array.from()
Object.assign()
JSON.stringify()
JSON.parse()