這邊先講一下什麼是資料結構,資料結構(Data Structure)是電腦儲存並組織資料的方式,簡單的來說,就是當資料處存在記憶體中時,決定資料的存放位子以及順序的,就是所謂的資料結構。
在這裡先把話題拉回來,實際上在 JavaScript 當中,我們的任何東西其實都是物件,雖然這並不意味著我們無法操作資料結構,但在此我們還是會先以物件的操作為主,並且實際上資料結構是門很深的學科,常見的資料結構就有陣列(Array)、堆疊(Stack)、樹狀結構(Tree)等等,因此若我們所學習的目標並非演算法的話,那這道大題我們可以先跳過不做。
物件操作是 JavaScript 的根基,基本上 JavaScript 的一切始於物件並終於物件,雖然這樣說有些玄學,但實際上其實並沒有這麼複雜,就跟 DOM 以及全部的變數宣告與函式呼叫一樣,他在語法上並非難以操作,也一般物件的使用也是如此。
在語法的操作上,設計一個物件並不困難,我們僅需要藉由一個 {} 即可完成宣告,並且在建立物件的階段,我們也可以決定是否放置內容值,而即使在此數不放置內容,後續的內容擴充新增也並不困難。
在物件宣告中,我們可以藉由 new Object() 這個函式建立物件,但這樣做除了讓字數變多外,其實並沒有任何顯著的特點,因此大多數情況我們會避免此操作。
const person = new Object();
以下的範例是物件最常使用的宣告形式,並且在賦予值的過程也非常簡單
const person = {};
console.log(person);
// 輸出:{}
const person = { name: 'Alex', age: 25 };
console.log(person);
// 輸出:{name: 'Alex', age: 25}
這此處有一個概念,理論上 key 跟 value 是程度出現的,在操作上不會出現有 key 但沒有 value 的情況,而若目前真的沒有值得話,可以先填入如 null 等純值。
object = {name1: value1, name2: value2, ...}
在物件的操作上,我們最常用的方式其實就是訪問該物件的某個特定內容(key),舉例來說,一個人可以被命名,同時他的名字也可以被拿到各種地方使用,而操作 key 內容的這個方式,可以很輕鬆的藉由一個 dot notation (.) 完成。
我們這邊可以藉由物件後面接一個 . ,就可以去取得他 key 的內容,並且在使用指定運算式的情況下,還可以將 key 進行賦值操作
objectName.propertyName
const person = {};
person.name = 'Alex'
person.age = 25
console.log(person);
// 輸出:{name: 'Alex', age: 25}
console.log(person.name);
// 輸出:Alex
其實在通常的情況下,我們使用 . 就可以幫我們去取得 Object 的值,但有時我們可能會需要全部的內容,因此 Object 也提供了相應的操作,並且會幫我們將內容存成陣列
Object.values()
const person = { name: 'Alex', age: 25 };
console.log(Object.values(person));
// 輸出:['Alex', 25]
// 這裡也可以藉由[]的方式輸入內容,內容將匹配到對應字串的 key 中
console.log(person['name'])
// 輸出: Alex
Object.keys()
const person = { name: 'Alex', age: 25 };
console.log(Object.keys(person));
// 輸出:['name', 'age']
delete obj.key1
const person = { name: 'Alex', age: 25 };
delete person.name;
console.log(person);
// 輸出:{age: 25}
const person = { name: 'Alex', age: 25 };
console.log(Object.entries(person));
// (2) [Array(2), Array(2)]
// 0: (2) ['name', 'Alex']
// 1: (2) ['age', 25]
// length: 2
const person = [
['name', 'Alex'],
['age', 25],
];
console.log(Object.fromEntries(person));
// 輸出:{name: 'Alex', age: 25}
<a class="book-container" href="#" target="_blank">
<div class="book">
<img alt="" src="" />
</div>
</a>
.book-container {
display: flex;
align-items: center;
justify-content: center;
perspective: 600px;
}
@keyframes initAnimation {
0% {
transform: rotateY(0deg);
}
100% {
transform: rotateY(-11deg);
}
}
.book {
width: 300px;
height: 300px;
position: relative;
transform-style: preserve-3d;
transform: rotateY(-11deg);
transition: 1s ease;
animation: 1s ease 0s 1 initAnimation;
}
.book:hover {
transform: rotateY(0deg);
}
.book > :first-child {
position: absolute;
top: 0;
left: 0;
background-color: red;
width: 300px;
height: 300px;
transform: translateZ(25px);
background-color: #01060f;
border-radius: 0 2px 2px 0;
box-shadow: 5px 5px 20px #666;
}
.book::before {
position: absolute;
content: ' ';
background-color: blue;
left: 0;
top: 3px;
width: 48px;
height: 294px;
transform: translateX(172px) rotateY(90deg);
background: linear-gradient(
90deg,
#fff 0%,
#f9f9f9 5%,
#fff 10%,
#f9f9f9 15%,
#fff 20%,
#f9f9f9 25%,
#fff 30%,
#f9f9f9 35%,
#fff 40%,
#f9f9f9 45%,
#fff 50%,
#f9f9f9 55%,
#fff 60%,
#f9f9f9 65%,
#fff 70%,
#f9f9f9 75%,
#fff 80%,
#f9f9f9 85%,
#fff 90%,
#f9f9f9 95%,
#fff 100%
);
}
.book::after {
position: absolute;
top: 0;
left: 0;
content: ' ';
width: 300px;
height: 300px;
transform: translateZ(-25px);
background-color: #01060f;
border-radius: 0 2px 2px 0;
box-shadow: -10px 0 50px 10px #666;
}
const book = document.querySelector('.book-container');
const gameObject = {
name: 'Spirit Island',
chineseName: '精靈島 / 靈跡島',
type: '合作遊戲',
weight: 4.04,
rating: 8.4,
bggUrl: 'https://boardgamegeek.com/boardgame/162886/spirit-island',
coverUrl:
'https://cf.geekdo-images.com/a13ieMPP2s0KEaKNYmtH5w__itemrep/img/OxbJV22OSI7wvhs0MFn1rF4tHz4=/fit-in/246x300/filters:strip_icc()/pic3615739.png',
author: {
name: 'R. Eric Reuss',
age: 33,
email: 'test@foo.com',
directions:
'Eric Reuss is a board game designer living in the greater Boston area',
},
aboutGame: function () {
return `這個遊戲叫${this.name}`;
},
};
console.log(gameObject);
// 輸出:整個物件
console.log(gameObject.name);
// 輸出:Spirit Island
console.log(gameObject.aboutGame());
// 輸出:這個遊戲叫Spirit Island
// 修改這個 HTML 本身的 href 屬性(這邊因為剛好是 a tag 所以方便直接操作)
book.href = gameObject.bggUrl;
// 修改書本裡的 img 的 src 屬性
book.querySelector('img').src = gameObject.coverUrl;
// 修改書本裡的 img 的 alt 屬性
book.querySelector('img').alt = gameObject.name;
這邊其實同學應該會發現,我們大多數的資料是沒有做使用的,而在實際專案上這其實是蠻常發生的事情,而大多數時候我們其實也很難去預期需求的擴充,因此偶爾多準備一些資料其實沒有什麼大礙(但以資料傳輸的區塊來看,多傳的資料確實是浪費的)
其實在 JavaScript 中陣列也是物件,但實際上的差異這裡可以先忽略不談,跟大多數程式語言一樣,陣列的 index 計算都是由 0 開始,並且所以值雷同於我們在 object 中的 key,也可以理解為我們訪問或操作陣列時的鑰匙。
零號房間住的是可頌、一號房間住的是三明治、二號房間是甜甜圈,最後的三號房間是不同的甜甜圈
額外提及一個概念,在網頁開發中我的的複數資料幾乎都是藉由 array 進行儲存,因此學習好 array 的操作是一件非常重要的事情。
在語法的操作上,設計一個陣列並不困難,我們僅需要藉由一個 [] 即可完成宣告,但在與建立物件時的不同, new Array() 實際上有一些額外的操作在下方會提及,因此 new Array()並不會完全不見蹤影。
我們可以藉由 new Array() 這個函式建立陣列,但跟物件的宣告一樣,我們在宣告時往往喜歡使用更簡單的方式來進行操作。
const fruits = new Array();
較常見的宣告方式
const fruits = [];
console.log(fruits);
// 輸出:[]
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits);
// 輸出: ['apple', 'banana', 'orange']
除此之外其實陣列有一些比較有趣的操作方式
const fruits = new Array(5);
console.log(fruits);
// 輸出:[空白*5] 五個空值的陣列,長度為 5
// 如果是輸入非單一數字的話將會建立內容
const arrayDemo = new Array(1, 2, 3);
console.log(arrayDemo);
// 輸出:[1, 2, 3]
const array = [
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4],
];
console.log(array);
// 輸出: [Array(4), Array(4), Array(4)]
array.fill(value, start, end)
let fruits = new Array(5);
console.log(fruits);
// 輸出:[]
// 將房間內填滿輸入的內容
fruits.fill('');
console.log(fruits);
// 輸出:['', '', '', '', '']
fruits = fruits.map((_e, index) => index + 1);
// fruits = fruits.map(function (e, index) {
// return index + 1;
// });
console.log(fruits);
// 輸出:[1, 2, 3, 4, 5]
const test = [];
for (let index = 1; index <= 10; index++) {
test.push(index);
}
console.log(test);
// 輸出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let fruits = new Array(8).fill('').map((e, index) => (e = index * 2));
console.log(fruits);
// 輸出:[0, 2, 4, 6, 8, 10, 12, 14]
在陣列的操作中,我們很常去取得陣列的長度,以實際的範例來舉例,購物車使用陣列儲存時,商品數將等於陣列長度。
array.length
const fruits = [1, 2, 3];
console.log(fruits.length);
// 輸出: 3
有趣的是,陣列的長度實際上是可以在被指派的,因此此處可以藉由指定長度的方式,新增額外的空格在陣列中
const fruits = [1, 2, 3];
fruits.length = 5;
console.log(fruits);
// 輸出:[1, 2, 3, 空白 × 2]
在陣列長度的控制中,我們甚至能故意縮小長度,來直接刪除後方的資料
const fruits = [1, 2, 3];
fruits.length = 2;
console.log(fruits);
// 輸出:[1, 2]
在取得陣列內容中,我們可以藉由指定房間號碼(index)的形式來操作,並且同理,也可以藉由這個房間號碼來改變內部的值
arrayName[index]
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits[0]);
// 輸出: apple
console.log(fruits[2]);
// 輸出: orange
反向藉由房間號碼進行修改操作
const fruits = ['apple', 'banana', 'orange'];
fruits[0] = 'bad apple';
console.log(fruits);
// 輸出:['bad apple', 'banana', 'orange']
在陣列的操作中,我們很長需要使用新增的操作,以下會提供幾種主要的方式,對應不同的使用情況。
push 操作,可以幫我們在陣列的後方新增上內容,並且可以藉由 , 的分隔,來一次新增多筆的資料。
array.push(item1, item2, ..., itemX)
const fruits = ['apple', 'banana', 'orange'];
fruits.push('bad apple', 'bad banana');
console.log(fruits);
// 輸出: ['apple', 'banana', 'orange', 'bad apple', 'bad banana']
unshift 的操作,會在陣列的前方幫我們新增所輸入的資料,也就是從 index 0 的位子開始。
array.unshift(item1, item2, ..., itemX)
const fruits = ['apple', 'banana', 'orange'];
fruits.unshift('bad apple', 'bad banana');
console.log(fruits);
// 輸出: [ 'bad apple', 'bad banana', 'apple', 'banana', 'orange']
pop 的操作,會進行以下行為
array.pop()
const fruits = ['apple', 'banana', 'orange'];
const lastFruits = fruits.pop();
console.log(fruits);
// 輸出: ['apple', 'banana']
console.log(lastFruits);
// 輸出: orange <-剛剛刪除的最後一個項目
shift 的操作,會進行以下行為
array.shift()
const fruits = ['apple', 'banana', 'orange'];
const lastFruits = fruits.shift();
console.log(fruits);
// 輸出: ['banana', 'orange']
console.log(lastFruits);
// 輸出: apple <-剛剛刪除的最後一個項目
其實在前端的開發中,我們比較少會使用到 indexOf 的操作,但在後端開發上,該筆資料在第幾筆出現或是否存在該筆資料,都是蠻常使用的參考依據。
array.indexOf(item, start)
const fruits = ['Banana', 'Orange', 'Apple', 'Mango'];
console.log(fruits.indexOf('Apple'));
// 輸出:2
console.log(fruits.indexOf('Appleefef'));
// 輸出:-1 <- 代表沒有找到
這裡還可以指令搜尋開始的位子
const fruits = ['Banana', 'Orange', 'Apple', 'Mango', 'Apple'];
console.log(fruits.indexOf('Apple', 3));
// 輸出:4
splice函式被用於在陣列指定範圍新增或刪除元素,並且回傳被刪除的元素
array.splice(index, howmany, item1, ....., itemX)
陣列.splice(開始索引數,刪除元素數目,element1,element2)
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits);
// 輸出:['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
console.log(fruits.splice(1, 3));
// 輸出:['Orange', 'Lemon' , 'Apple']
console.log(fruits);
// 輸出:['Banana', 'Mango']
在這裡也可以帶入其他元素,他將回從原先移除的 index 開始新增內容
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.splice(1, 3, 'bad Orange', 'bad Lemon'));
// 輸出:['Orange', 'Lemon', 'Apple']
console.log(fruits);
// 輸出: ['Banana', 'bad Orange', 'bad Lemon', 'Mango']
slice函式可取得陣列中指定範圍的元素陣列,語法中所輸入的索引值可以輸入複數,若為負數則由末端開始算起
array.slice(start, end)
陣列.slice(開始索引值,結束索引值)
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.slice(1, 3));
// 輸出:['Orange', 'Lemon']
console.log(fruits);
// 輸出:['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
在 start 與 end 的設定中,也可以藉由給予負數的操作來使其從後方進行
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.slice(-3, -2));
// 輸出:['Lemon']
console.log(fruits.slice(-2));
// 輸出:['Apple', 'Mango']
concat函式被用於連結複數以上的陣列,並回傳連結後的結果
array1.concat(array2, array3, ..., arrayX)
陣列.concat(陣列1,陣列2,陣列3,…)
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
const badFruits = ['bad Orange', 'bad Lemon'];
console.log(fruits.concat(badFruits));
// 輸出: ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango', 'bad Orange', 'bad Lemon']
陣列的排序操作可以很粗略的排序陣列內容,並且還可以額外進行函式的操作
array.sort(compareFunction)
陣列.sort(排序函式)
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.sort());
// 輸出: ['Apple', 'Banana', 'Lemon', 'Mango', 'Orange']
array.reverse()
陣列.reverse()
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.reverse());
// 輸出: ['Mango', 'Apple', 'Lemon', 'Orange', 'Banana']
陣列的操作都可以共同使用,如此處先排序再反轉的操作
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.sort().reverse());
// 輸出: ['Orange', 'Mango', 'Lemon', 'Banana', 'Apple']
join函式會將陣列內的內容加上分隔區塊,然後以字串的形式進行回傳。
array.join(separator)
陣列.join(分隔區塊)
const fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
console.log(fruits.join(' and '));
// 輸出: Banana and Orange and Lemon and Apple and Mango