0%

成品

觀察功能需求

  • Drag & Drop API

    • 使元素可以拖放,在 HTML5,只要為它加上 draggable=”true” 屬性,它就能「被拖曳」了
  • 拖曳該項目時,會使下方的格子顏色變成灰色

  • 拖曳項目到目標位置,會使了者交換
  • 排序最後會有一個檢查順序正確與否的按鈕

JS部分

1.排行榜的設置

  • 設定熱門旅遊縣市排行榜的陣列
  • 並宣告一個空陣列,以利後面使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const bestPlace = [
'台南',
'台中',
'高雄',
'台東',
'屏東',
'新北',
'宜蘭',
'花蓮',
'澎湖',
'桃園'
];


const listItems = [];

2.創建排行列表,並將其呈現於頁面上

  • 宣告新的變數,並利用Fisher-Yates,創造隨機的效果
    • 注意新變數,是帶入展開運算元(把陣列中的元素取出)
  • 利用each跑陣列,創造變數填入<li>,並於其中填入html標籤,使畫面呈現列表。
  • jquery稱增加屬性listItem.setAttribute('data-index', index);,.attr()

  • listItems.push(listItem);將列表項目,放入空陣列中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Fisher-Yates洗牌演算法
function fisherYatesShuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1)); //random index
[arr[i], arr[j]] = [arr[j], arr[i]]; // swap
}
}
/製作實際可以看到的list
function creatList() {
let placeArr = [...bestPlace];
fisherYatesShuffle(placeArr);
$.each(placeArr, function (index, place) {
const listItem = $('<li></li>').appendTo('#draggable-list');
// console.log(listItem);
listItem.attr("data-index", index);
//listItem.addClass('wrong')
listItem.html(`<span class="number">${index + 1}</span>
<div class="draggable" draggable="true">
<p class="place-name">${place}</p>
<i class="fas fa-grip-lines"></i>
</div>`)


listItems.push(listItem);
//insert into dom
$('#draggable-list').append(listItem);
});

addEventListeners()
}

參考資料:
push:
JS 從陣列 Array 尾端新增元素的 push()
JavaScript Array 陣列操作方法大全 ( 含 ES6 )

3.拖曳的監聽事件

  • 拖曳開始的監聽,元素是抓取draggables,後續的drop、dragenter、dragover等事件,元素是.draggable-list li
  • 針對要被拖曳的元素(dragSource)監聽事件
  • 利用forEach方式,允許多個可拖曳的物件
  • 抓取的元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//監聽事件
function addEventListeners() {
const draggables = document.querySelectorAll('.draggable');
const dragListItems = document.querySelectorAll('.draggable-list li');

//Dragable dot 增加監聽事件

draggables.forEach(draggable => {
draggable.addEventListener('dragstart', dragStart);
});
dragListItems.forEach(item => {
item.addEventListener('dragover', dragOver);
item.addEventListener('drop', dragDrop);
item.addEventListener('dragenter', dragEnter);
item.addEventListener('dragleave', dragLeave);
});

}

[筆記] 製作可拖曳的元素(HTML5 Drag and Drop API)

4.針對監聽事件,設置各執行的函式

  • 為了能夠追蹤抓取項目,所以一開始先宣告一個索引變數。
  • dragStart():宣告dragStartIndex變數為抓取到的li->data-index

  • 有拖曳的開始,就會有結束,也就是放置(dragDrop()):dragEndIndex同樣也是以索引來作為依據,並在設置一個交換的函式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//要能夠持續追蹤每個項目的index,所以要有個初始變數
let dragStartIndex;
//拖拉的各函式

function dragStart() {
//console.log('Event: ', 'dragstart');
dragStartIndex = +$(this).closest('li').attr('data-index');

console.log(dragStartIndex);
};
function dragOver(e) {
// console.log('Event: ', 'dragover');
e.preventDefault();
}
//設定開始的index,以及結束的index是為了交換
function dragDrop() {
// console.log('Event: ', 'dragdrop');
const dragEndIndex = +$(this).attr('data-index');
swapItems(dragStartIndex, dragEndIndex);
$(this).removeClass('over');
}

5.交換索引,使物件交換位置swapItems

  • 透過抓取到的開始與結束的索引,來填入要交換的變數
1
2
3
4
5
6
7
8
9
10
function swapItems(fromIndex, toIndex) {
//要設定交換之前,要先到dragover去設定e.preventDefault();(因為dragover會擋到交換的執行)

const itemOne = listItems[fromIndex].find('.draggable');
const itemTwo = listItems[toIndex].find('.draggable');

//console.log(itemOne, itemTwo)
listItems[fromIndex].append(itemTwo);
listItems[toIndex].append(itemOne);
}

6.延續拖曳監聽事件,所要執行的其他函式

  • dragEnter()拖曳元素進入要放置的目標位置,所觸發的事件。在此是設定要放入目標位置時,增加一個class的效果

  • 進入目標位置,其相互搭配的事件就是離開dragLeave(),當拖曳元素離開放置位置時,所發生的事件。在此為設定,去除於原本class的效果。

1
2
3
4
5
6
7
8
9
function dragEnter() {
// console.log('Event: ', 'dragenter');
$(this).addClass('over');
}
function dragLeave() {
// console.log('Event: ', 'dragleave');
$(this).removeClass('over');
}

7.核對順序

  • 抓取listItems,來跑forEach,其要參照的參數是,項目、索引。
  • 宣告placeName為參數listItem,draggable的文字字串。
    • trim()用來去除字串前後的空白
  • 判斷句placeName !== bestPlace[index]
  • console.log(bestPlace[index])會印出,正確的順序。

  • 核對結果:就是分別加入wrong'right'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //核對排名順序
    function checkOrder() {
    listItems.forEach((listItem, index) => {
    const placeName = listItem.find('.draggable').text().trim();

    if (placeName !== bestPlace[index]) {
    listItem.addClass('wrong');
    } else {
    listItem.removeClass('wrong');
    listItem.addClass('right');
    }
    });
    }

以下為補充資料:

拖曳補充資料:

Top 5: Best Sortable & Draggable List JavaScript and jQuery Plugins
實用網頁工具庫 - jQuery UI (上) 元件篇

使陣列隨機排序

  • 使用map將陣列轉換為新陣列,創造vale、以及加上sort排序值(利用Math.random()來產生亂數)
  • 使用sort隨機排序,剛剛的sort值是隨機產生,再一次使用sort,來使排的順序也隨機。
  • 最後,因為排序後陣列中的物件是{ value: , sort: },而我們要讓畫面只顯示value:,所以再使用map將新陣列轉回只有該資料的陣列。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function createList() {
    [...richestPeople]
    .map(a => ({ value: a, sort: Math.random() }))
    .sort((a, b) => a.sort - b.sort)
    .map(a => a.value)
    .forEach((person, index) => {
    const listItem = document.createElement('li');

    listItem.setAttribute('data-index', index);

    listItem.innerHTML = `
    <span class="number">${index + 1}</span>
    <div class="draggable" draggable="true">
    <p class="person-name">${person}</p>
    <i class="fas fa-grip-lines"></i>
    </div>
    `;

    listItems.push(listItem);

    draggable_list.appendChild(listItem);
    });

    addEventListeners();
    }
    [筆記] 如何正確實作 JavaScript Array Random Shuffle 亂數排序演算法
    在 JavaScript 中對一個陣列進行隨機化或洗牌

jquery ui

1
2
3
4
5
6
7
8
9
10
11
12
//https://stackoverflow.com/questions/6426795/what-is-disableselection-used-for-in-jquery-ui
function dragdrop() {
$("#draggable-list").sortable({
//connectWith: "ul",
delay: 300,
distance: 15,
//number: "span:not(.number)",
iframeFix: true,

}).disableSelection();

}

顏色

0-255
漸層顏色
colors
Why is my JQuery selector returning a n.fn.init[0], and what is it?

觀察須製作的功能

成品

  1. 播放器API的樣式

<audio>: The Embed Audio element

  1. 播放時,左方的圓型圖示會旋轉。
  • 先於css檔案製作符合動畫的class
  • 在js時,依照符合的情況加入該class

  1. 播放時,上方會跳出播放歌曲的名稱。
  • 先於css檔案製作符合動畫的class,設定opacity1或0,translateY位置的移動
  • 在js時,依照符合的情況加入該class

  1. 左右按鍵可以更換曲目,同時圖片會更換
  • 設定陣列,使歌曲與圖片相符
  • 並讓前後的跳轉鍵,依照順序或條件來變化


html部分

跳轉按鍵

  • 播放鍵、前/後跳轉的按鈕
1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="nav">
<button id="prev" class="action-btn">
<i class="fas fa-backward"></i>
</button>

<button id="play" class="action-btn action-btn-big">
<i class="fas fa-play"></i>
</button>

<button id="next" class="action-btn">
<i class="fas fa-forward"></i>
</button>
</div>

css部分

背景顏色

linear-gradient()

1
2
3
4
/* 傾斜度\顏色、從哪個位置開始 */
background-image:linear-gradient(0deg
,rgb(247, 247, 247,1) 23.8%,
rgba(252,221,221,1) 92% );

圖片圓形以及旋轉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.img-container{
position: relative;
width: 110px;
}
/*圖片圓形*/
.img-container img{
border-radius: 50%;
object-fit: cover;
/*填滿元素的寬度及高度(維持原比例),通常會剪掉部分的物件*/
height: 110px;
width: inherit; /*繼承自父層的寬度屬性值。*/
position:absolute;
bottom: 0;
left: 0;

animation: rotate 3s linear infinite;
animation-play-state: paused;
}

/* 播放class的製作 */
.music-container.play .img-container img{
animation-play-state:running;

}
@keyframes rotate{
from{
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

  • 在圖片中間加入圓形點,以看起來像光碟樣式
1
2
3
4
5
6
7
8
9
10
11
.img-container::after{
content: '';
border-radius: 50%;
background-color: #fff;
position: absolute;
left: 50%;
bottom: 100%;
width: 25px;
height: 25px;
transform: translate(-50%,50%);
}

播放進度條的製作

  1. 標題
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    .music-info{
    background-color: rgba(255,255,255,.5);
    border-radius:15px 15px 0 0 ;
    position: absolute;
    top: 0;
    left: 20px;
    opacity: 0;
    transform: translateY(0%);
    transition: transform 0.3s ease-in,opacity 0.3s ease-in;
    z-index: 0;
    }

    .music-container.play .music-info{
    opacity: 1;
    transform: translateY(-100%);
    }
  • 將曲目標題設置背景、圓角
  • 利用 transfor、opacity來呈現位置的變化

  • 修改標題位置
1
2
3
/*放入.music-info之中*/
width: calc(100% - 40px);
padding: 10px 10px 10px 150px;

calc()

calc() 的運算式一樣是按照先乘除後加減進行計算
width: calc(100% - 40px);
/* 意思是 100% 的寬度 - 去 40px */
CSS: calc() 數值運算

1
2
3
4
.music-info h4{
margin: 0;
}

2.進度條(progress)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.progress-container{
background-color: #fff;
border-radius: 5px;
height: 4px;
width: 100%;
margin: 10px 0;
cursor: pointer;
}

.progress{
background-color: #fe8daa;
border-radius: 5px;
height: 4px;
width: 0%;
transition: width .1s linear ;
}

js部分

1.音樂播放鍵(播放與停止)

  • 分別有兩個函式:playMusic()pauseMusic()
  • 在css設定.play的css,並於js綁定事件,加入css的效果(使圖片如同轉盤,會旋轉)
  • 在icon的部分:在播放時:為暫停鍵圖示;在暫停時:為播放鍵的圖示
  • 透過play()、pause(),讓音源播放與暫停

play()pause()

因為play()、pause()是DOM元素,不是jQuery的function,而要用jQuery取得DOM元素
$('#audio').get(0).play() =>$('#audio')[0].play()
$('#audio').get(0).pause() =>$('#audio')[0].pause()

參考資料:
How do I pull a native DOM element from a jQuery object?
Play/pause HTML 5 video using JQuery

1
2
3
4
5
6
7
8
9
//播放音樂
function playMusic() {

$('.music-container').addClass('play');
$('#play').find('i').removeClass('fa-play');
$('#play').find('i').addClass('fa-pause');
$('#audio').get(0).play();

};
1
2
3
4
5
6
7
//停止音樂
function pauseMusic() {
$('.music-container').removeClass('play');
$('#play').find('i').addClass('fa-play');
$('#play').find('i').removeClass('fa-pause');
$('#audio').get(0).pause();
}
  • 綁定事件:

    如果音樂是播放狀態的話,就執行pauseMusic(),反之,音樂不處於波方時,執行playMusic()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $('#play').click(function (e) {
    var hasPlay = $('#music-container').hasClass('play');
    //var a = $('#audio').paused;
    if (hasPlay) {
    pauseMusic();
    } else {
    playMusic();
    }
    });

2.載入音樂

  • 將歌曲名稱建立陣列,並將此回傳到建立的函式中
  • 設定預設的索引let songIndex = 1
  • 使用 attr( 屬性名, 屬性值 ),獲取屬性的值,在這部分,透過歌曲名稱來取得歌曲與照片
1
2
3
4
5
6
7
8
9
10
11
12
13
var songs_name = ['hey', 'summer', 'ukulele'];
let songIndex = 1;

function loadSongs(song) {
//console.log(song);
$('#title').text(song);

$('#audio').attr('src', `music/${song}.mp3`);
$('#cover').attr('src', `img/${song}.jpg`);
}

// Initially load song details into DOM
loadSongs(songs_name[songIndex]);

綁定於下一首、前一首的圖示被點擊時

1
2
3
$('#next').click(nextSong)
$('#prev').click(function () { prevSong();
});
  • 查看函式回傳

    1
    loadSongs(songs_name);

  • 要抓取到該索引

    1
    loadSongs(songs_name[songIndex]);


    此段落執行時,出現問題:

  • 搜尋到的處理方式:在html的head
    I’m getting favicon.ico error

參考資料:
jquery prop和attr的區別
jQuery .attr() vs .prop()

3.左右鍵的跳轉

  • 下一首歌曲,與跳至前一首歌曲,兩個函式的撰寫有異曲同工之妙,只有在設條條件上的差異。
  • nextSong():設定條件為歌曲索引songIndex比 歌曲陣列長度-1songs_name.length - 1還要小,我們就要將索引+1。
    • 例如這裏預設的索引為1,所以在點擊當下,判斷<songs_name.length - 1,所以需加1,索引變成2,跳至下一首 ‘ukulele’
  • prevSong():設定條件為歌曲索引songIndex比0大,就要索引-1,跳轉至上一首歌曲。若沒符合的話,索引就等於songs_name.length - 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//歌曲的索引,如果比歌曲總數-1還小,就將索引加1,跳轉下一首
function nextSong() {

if (songIndex < songs_name.length - 1) { songIndex += 1; } else {
songIndex = 0
};
loadSongs(songs_name[songIndex]);
playMusic();
}


//跳轉前一首


function prevSong() {
//如果索引沒有大於0。就會播索引[2]
if (songIndex > 0) { songIndex -= 1; } else {
songIndex = songs_name.length - 1;
};
loadSongs(songs_name[songIndex]);
playMusic();
}

參考資料:
Create a Music Player using JavaScript

4.點擊進度條,可以跳轉音樂位置

(1).顯示粉色進度條

  • duration:音源的時間(一樣是使用get方式取得)
  • currentTime:音樂播放的當前位置(以秒計)
  • progressBar:進度條
  • 計算百分比:(currentTime / duration) * 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//顯示進度條
//為何取[0]
function handleProgress() {
var duration = $('#audio').get(0).duration;
//console.log(duration)
var currentTime = $('#audio')[0].currentTime;
//console.log(currentTime)
const progressBar = $('#progress');
const progressPercent = (currentTime / duration) * 100;

//currentTime目前播放時間去與影片長度duration轉換成百分比,即可得到目前播放時間的百分比

progressBar.css('width', `${progressPercent}%`);
};

(2).點擊進度條,會跳轉到該位置播放

  • width:獲取該進度條容器的寬度
  • 抓取點擊位置:
    • e.pageX:取得滑鼠在頁面裡的位置
    • elm.offset().left:絕對座標X軸
  • 將(點擊位置除以進度條的總長)乘以 音樂的時間長度,就可以取得點擊的時間位置theTime = ((xPos / width))* duration;
  • 最後現在時間設為,計算好的點擊時間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//點擊進度條

//https://ithelp.ithome.com.tw/articles/10194871
//https://tools.wingzero.tw/article/sn/102


$('#progress-container').click(function (e) {
const width = $('#progress-container').width(); //216.25
//console.log(width);
//取得點擊位置
var elm = $(this);
var xPos = e.pageX - elm.offset().left;

//console.log(xPos);

var duration = $('#audio')[0].duration;
var theTime = ((xPos / width))
* duration;
//console.log(theTime);
$('#audio')[0].currentTime = theTime;
});

執行過程,利用console來查看

1
2
3
console.log(e.pageX);
console.log(elm.offset().left);
console.log(xPos)

參考資料:
取得滑鼠位置、元素位置與區塊內的相對位置

5.歌曲播完後,換下一首

1
$('#audio').on('ended',nextSong);

參考資料:
audio auto play next song when previous is finished

6.增加隨機的變換背景色彩

  • 色彩為0-256;而因為想取的較亮的色系,所以取64-256
    • Math.random() 會回傳一個偽隨機小數 (pseudo-random) 介於0到1之間(包含 0,不包含1)
    • Math.floor()會將所有的小數無條件捨去到比自身小的最大整數
    • Math.floor(Math.random() * 255) + 64就會取得64 to 256
  • 宣告變數bgColor為rdba(red,green,blue,a)
  • 最後將此函式,放入loadSongs()函式當中,每次播放、跳轉歌曲,同時背景顏色也會改變
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function random_bg_color() {
// Get a random number between 64 to 256

// (for getting lighter colors)
let red = Math.floor(Math.random() * 255) + 64;
let green = Math.floor(Math.random() * 255) + 64;
let blue = Math.floor(Math.random() * 255) + 64;
let a = 0.5

// Construct a color withe the given values
let bgColor = "rgba(" + red + ", " + green + ", " + blue + ", " + a + ")";

var bg = $(document.body).css('background', `${bgColor}`);
// Set the background to the new color
bg = bgColor;
console.log(bg)

}

參考資料:
Create a Music Player using JavaScript
Won’t Math.floor(Math.random() * 255) generate uneven probabilities? [筆記][JavaScript]用Math.random()取得亂數的技巧

觀察功能

  1. 製作一個會員申請的表單
    • 帳號
    • email
    • 密碼
  2. 輸入框的顏色變化
  3. 判斷條件的設置

任務拆解

抓出要互動的元素

1
2
3
4
5
const form = document.querySelector('#form');
const username = document.querySelector('#username');
const email = document.querySelector('#email');
const password = document.querySelector('#password');
const password2 = document.querySelector('#password2');
  • 獲取物件層次中的父物件
  • 針對指定元素取得或設置指定的class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//show input error message
function showError(input, message) {
const formControl = input.parentElement;
formControl.className = 'form-control error';
}


form.addEventListener('submit', function (e) {
e.preventDefault();
if (username.value === '') {
showError(username, 'Username is required');
} else {
showSuccess(username);
}
})

1
2
const small = formControl.querySelector('small');
small.innerText = 'Username is required';
  • 修改顯示文字

成功輸入

1
2
3
4
5
//show success outline
function showSuccess(input) {
const formControl = input.parentElement;
formControl.className = 'form-control success';
}

複製相同條件判斷

  • 事件為點擊送出之後,會進行條件判斷
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//event listener
form.addEventListener('submit', function (e) {
e.preventDefault();

if (username.value === '') {
showError(username, 'Username is required');
} else {
showSuccess(username);
};

if (email.value === '') {
showError(email, 'Email is required');
} else {
showSuccess(email);
};

if (password.value === '') {
showError(password, 'Password is required');
} else {
showSuccess(password);
};

if (password2.value === '') {
showError(password2, 'Password 2 is required');
} else {
showSuccess(password2);
};
})

設定符合的驗證

  • email的輸入應有規範樣式
  • 使用正則表達式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//check email is valid
//js email regex
//https://ithelp.ithome.com.tw/articles/10094951

function isValiEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}

//
if (email.value === '') {
showError(email, 'Email is required');
} else if (!isValiEmail(email.value)) {
showError(email, 'Email is not valid');
}
else {
showSuccess(email);
};

整合簡化 =>forEach

1
2
3
4
5
6
7
8
9
10
11
//Check required fields
function checkRequired(inputArr) {
inputArr.forEach(function (input) {
if (input.value.trim() === '') {
showError(input, 'is required');
} else {
showSuccess(input);
}
});

}
  • 修改showError,套入名稱
1
2
3
if (input.value.trim() === '') {
//console.log(input.id);//會顯示input的所有id
showError(input, `${input.id}is required`);

將getFieldName() function拉出

  • 製作訊息的第一個字母大寫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Check required fields
function checkRequired(inputArr) {
inputArr.forEach(function (input) {
if (input.value.trim() === '') {
//console.log(input.id);//會顯示input的所有id
showError(input, `${getFieldName(input)} is required`);
} else {
showSuccess(input);
}
});

}


//getFieldName
function getFieldName(input) {
return input.id.charAt(0).toUpperCase() + input.id.slice(1);
//將第一個字charAt(0),變大寫,並再接續後面的字slice(1)
}

長度檢查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// Check input length
function checkLength(input, min, max) {
if (input.value.length < min) {
showError(
input,
`${getFieldName(input)} must be at least ${min} characters`
);
} else if (input.value.length > max) {
showError(
input,
`${getFieldName(input)} must be less than ${max} characters`
);
} else {
showSuccess(input);
}
}

密碼檢查

1
2
3
4
5
6
// check password match
function checkPasswordMatch(input1, input2) {
if (input1.value !== input2.value) {
showError(input2, 'Password do not match')
}
}

參考資料:
JavaScript初學:DOM常用屬性與方法

認識 Web Storage:

HTML5 的 Web Storage 是一種可讓網頁將資料儲存於本地端的技術,其作用如同 cookie。

Cookie 儲存空間很小,最多僅能儲存4KB的資料。
HTML5 Web Storage 的儲存空間則大得多(儲存資料容量至少5MB以上)。

localStorage 與 sessionStorage

Web Storage 提供兩個物件可以將資料存在 client 端,一種是 localStorage,另一種是 sessionStorage

  • localStorage:可以跨瀏覽器分頁(tab)存取,且資料存取沒有期限,除非手動從本地端清除。
  • sessionStorage:生命週期較短,每次分頁或瀏覽器關掉後就會清除。

基本操作:

  • 設置資料:localStorage.setItem(key, value)
  • 取得資料:let storageValue = localStorage.getItem(key, value)
  • 清除資料:localStorage.removeItem(key)
  • 清除全部資料:localStorage.clear()

    只要將localStorage替換sessionStorage即可

注意:

儲存在本地端的資料,格式為 key : value,需注意的是「value 的型態只有 String」!

找到儲存的資料

  • 在 DevTools console

JSON 字串轉換

1
2
3
4
var arr = ['1','2','c'];

console.log( arr + " is " + typeof(arr));
// 1,2,c is object

1.轉換成字串 JSON.stringify

1
var arrToStr = JSON.stringify(arr);

2.轉換回原本格式 JSON.parse

  • 會把一個JSON字串轉換成 JavaScript的數值或是物件
1
var parse = JSON.parse(arrToStr);

參考資料:
[JavaScript] localStorage 的使用
[第七週] 瀏覽器資料儲存 - Cookie、LocalStorage、SessionStorage
[Day16] JavaScript - 前端資料存
HTML5 的 Web Storage- local storage 與 session storage
Day20 localStorage、sessionStorage
[JavaScript] JSON.stringify() and JSON.parse():變 JSON 和變物件

完成後的成品

觀察須製作的功能

  1. 重新整理網頁後,保有過去執行的狀態:localstorage
    • 實作內有使用:
    • localstorage:setItem,getItem
    • jason.stringfy/jason.parse

  1. 在new transation輸入資料後,會將資料傳入history中
    • add transation之後,頁面不會刷新(spa)(e.preventdefault)
  2. 摸到history中的項目,會出現刪除符號,點擊後刪除

  1. 輸入的金額,會顯示於上方income、expense,並且計算總額
    • 有正負值
    • 計算收入、花費

呈現完整語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
$(document).ready(function () {
//預設值的設置,取得存在本地的資料或是空陣列
var transactions = JSON.parse(localStorage.getItem('Transactions')) || [];

//陣列初始
if (transactions.length > 0) {
initHistory(transactions);
}
//點擊按鈕的事件
$('.btn').click(function (e) {
e.preventDefault();
//console.log('click');
//取得表格中的值
const text_val = $('#text').val();
const amount_val = $('#amount').val();
let id = generateID();

addTransactions(id, text_val, amount_val, transactions);

//推入陣列
transactions.push({
id: id,
name: text_val,
amount: amount_val
})


localStorage.setItem('Transactions', JSON.stringify(transactions));


})
updateValue(transactions);

});


//在list中插入
function addTransactions(id, name, amount, transactions) {
console.log('amount:', amount);

const Transaction_str = $('<li></li>').appendTo('#list');

Transaction_str.addClass(amount < 0 ? 'minus' : 'plus');
Transaction_str.html(`${name}<span> ${amount}</span><button class="delete-btn" data-id="${id}">x</button>`);
$('#list').append(Transaction_str);


$('#text').val('');
$('#amount').val('');

//刪除鈕 要交易交出後再綁事件
$('.delete-btn').last().click(function () {
$(this).parent().remove();
let id = $(this).data('id');
//console.log(id);
deleteFromLocalstorage(transactions, id); //要記得傳入id

});

}
//從localstorage刪除
//要記得在參數放入id
function deleteFromLocalstorage(transactions, id) {
transactions.forEach(function (item, index, arr) {
//console.log('item', item);
//console.log('index', index);
//console.log('arr', arr);
if (item.id === id) {
arr.splice(index, 1);
}
});
//迴圈刪除後,要儲存到localStorage才有確實刪去
localStorage.setItem('Transactions', JSON.stringify(transactions));
}


// Generate random ID
function generateID() {
return Math.floor(Math.random() * 100000000);
}


//數值更新計算
function updateValue(transactions) {
const amounts_arr = transactions.map(function (transaction) {
return transaction.amount
})

console.log(amounts_arr);

//計算加總
var total = 0;
$.each(amounts_arr, function () { total += parseFloat((this)) || 0; });
//傳回YOUR BALANCE
$('#balance').text(`$${total}`);
console.log(total);


//*************************** */
//從陣列找出>0的值,放置income
//console.log(amounts)
var income = amounts_arr.filter(
function (item) {
return item > 0
}
)
console.log(income);

var totalIncome = 0;
$.each(income, function () { totalIncome += parseFloat((this)) || 0; });

console.log(totalIncome); //回傳220
//傳回到income
$('#money-plus').text(`$${totalIncome}`);
//***************************** */
//從陣列找出<0的值,放置income
var expense = amounts_arr.filter(
function (item) {
return item < 0
}
)
console.log(expense);

var totalExpense = 0;
$.each(expense, function () { totalExpense += parseFloat((this)) || 0; });

console.log(totalExpense);
//傳回到expense
$('#money-minus').text(`$${totalExpense}`)


}

//init
function initHistory(transactions) {
transactions.forEach(transaction => {
addTransactions(transaction.id, transaction.name, transaction.amount, transactions);
});
updateValue(transactions);

}


各項目拆解:

add transations按下去之後,頁面不刷新,直接將資料傳入紀錄中

1
2
3
4
5
6
$(document).ready(function () {
$('.btn').click(function () {
console.log('click');
})
});
//點擊後,有印出click,但很快的刷新頁面
  • 加入e.preventDefault(); 用來阻止預設動作的發生

讓transation輸入後顯示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(document).ready(function () {
$('.btn').click(function (e) {
e.preventDefault();
console.log('click');
addTransactions('cash', '200');
})
});


function addTransactions(name, amount) {
var Transaction_str = `<li class='plus' >${name}<span> ${amount}</span><button class="delete-btn">x</button></li>`
$('#list').append(Transaction_str);
}

//

  • 提交之後清空
    • 選擇該輸入框,並利用val(),使內部的值清空
1
2
3
$('#text').val('');
$('#amount').val('');

從表格中取得值

  • 製作按鈕的點擊事件,取得表格中的值
  • 將值傳入函式的參數addTransactions(text_val, amount_val);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $(document).ready(function () {
    $('.btn').click(function (e) {
    e.preventDefault();
    console.log('click');

    //取得表格中的值
    const text_val = $('#text').val();
    const amount_val = $('#amount').val();
    addTransactions(text_val, amount_val);
    })

    });

製作刪除紐

  • 原本的撰寫方式,為選到最後一個刪除鈕,並點擊加以刪除
  • 此方式造成,無法確實刪除資料
1
2
3
4
$('.delete-btn').last().click(function () {
console.log('del');
})
}

  • 修改方式,只能綁定一筆transatin或是綁定最後一筆

  • 查看若實現點擊後刪除,是會刪除什麼?

    1
    2
    3
    4
     $('.delete-btn').last().click(function () {
    console.log(this);
    })
    }

  • 要刪除的是整筆資料,所以要去尋找他鄰近的元素

    1
    2
    3
    $('.delete-btn').last().click(function () {
    $(this).parent().remove();
    })

localstorage 執行

筆記補充:LocalStorage介紹

從此實作,可以看到它是一個陣列,裡面是物件

  • 設置一個空陣列
  • 並將物件push進去
1
2
3
4
5
6
7
8
9
10
//預設值的設置,取得存在本地的資料或是空陣列
var transactions = JSON.parse(localStorage.getItem('Transactions')) || [];

//推入陣列
transactions.push({
name: text_val,
amount: amount_val
})
localStorage.setItem('Transactions', JSON.stringify(transactions));

  • 增加初始function
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //init
    function initHistory(transactions) {
    transactions.forEach(transaction => {
    addTransactions(transaction.name, transaction.amount);
    });

    }

    //將此函式放於 $(document).ready()之中

    //陣列初始
    if (transactions.length > 0) {
    initHistory(transactions);
    }

將transation刪除後,並沒有完全從localstorage刪除

運用id來刪除

  • 從此段落:localstorage 執行,圖片中的存取陣列可以看到,沒有設置id

  • id是在addTransactions使用

    1
    2
    3
    4
    5
    6
    7
    8
    // Generate random ID
    function generateID() {
    return Math.floor(Math.random() * 100000000);
    }

    //floor:無條件捨去
    //ceil:無條件進位

  • 在創造的陣列中\物件,都加入id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const id = generateID();
    addTransactions(id, text_val, amount_val);

    //推入陣列
    transactions.push({
    id: id,
    name: text_val,
    amount: amount_val
    })

  • 在addTransactions的函式中也要加入id

    1
    2
    3
    4
    function addTransactions(id, name, amount) {

    console.log(id, name, amount);}

  • 在button加入data-id

和資料有關的時候:data-原來欄位名稱

1
const Transaction_str = `<li class='plus' >${name}<span> ${amount}</span><button class="delete-btn"  data-id="${id}">x</button></li>`

  • 確定點擊刪除鈕的時後,是有抓到該id
    1
    2
    3
    4
    5
    6

    $('.delete-btn').last().click(function () {
    $(this).parent().remove();
    let id = $(this).data('id');
    console.log(id);
    })
  • let id = $(this).data(); 查看id時 取得的是物件
  • 應改為

此時還沒有真正刪除id,只是確定有抓到id :point_up_2:

jquery data(): 自定義屬性 (data attributes),讓我們可以任意讀寫資料在元素上,而且不會影響頁面的 layout
jQuery Data

  • 利用forEach、splice()刪除陣列內的資料

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function deleteFromLocalstorage(transactions, id) {
    transactions.forEach(function (item, index, arr) {
    //console.log('item', item);
    //console.log('index', index);
    //console.log('arr', arr);
    if (item.id === id) {
    arr.splice(index, 1);
    }
    });
    //迴圈刪除後,要儲存到localStorage才有確實刪去
    localStorage.setItem('Transactions', JSON.stringify(transactions));
    }
    ````


    * 查看迴圈的資料
    ![](https://i.imgur.com/FexS5lt.png)


    * 確實刪除存在localstorage的資料
    ```javascript
    localStorage.setItem('Transactions', JSON.stringify(transactions));

判斷輸入的數字大小,並給予class

  • 放在addTransactions()函式之中
1
2
3
4
5
6
const Transaction_str = $('<li></li>').appendTo('#list');

Transaction_str.addClass(amount < 0 ? 'minus' : 'plus');
Transaction_str.html(`${name}<span> ${amount}</span><button class="delete-btn" data-id="${id}">x</button>`);
$('#list').append(Transaction_str);

數值的更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function updateValue(transactions) {
const amounts_arr = transactions.map(function (transaction) {
return transaction.amount
})

console.log(amounts_arr);

//計算加總
var total = 0;
$.each(amounts_arr, function () { total += parseFloat((this)) || 0; });
//傳回YOUR BALANCE
$('#balance').text(`$${total}`);
console.log(total);


//*************************** */
//從陣列找出>0的值,放置income
//console.log(amounts)
var income = amounts_arr.filter(
function (item) {
return item > 0
}
)
console.log(income);
var totalIncome = 0;
$.each(income, function () { totalIncome += parseFloat((this)) || 0; });

//console.log(totalIncome); //回傳220
//傳回到income
$('#money-plus').text(`$${totalIncome}`);
//***************************** */
//從陣列找出<0的值,放置income
var expense = amounts_arr.filter(
function (item) {
return item < 0
}
)
console.log(expense);

var totalExpense = 0;
$.each(expense, function () { totalExpense += parseFloat((this)) || 0; });

console.log(totalExpense);
//傳回到expense
$('#money-minus').text(`$${totalExpense}`)


}
  • 創造一個amount的陣列
1
2
3
4
5
const amounts_arr = transactions.map(function (transaction) {
return transaction.amount
})
console.log(amounts_arr);

  • 計算加總數值
    1
    2
    3
    4
    5
    6
    var total = 0;
    $.each(amounts_arr, function () { total += parseFloat((this)) || 0; });
    //傳回YOUR BALANCE
    $('#balance').text(`$${total}`);
    console.log(total);

  • income 收入加總計算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//從陣列找出>0的值,放置income
//console.log(amounts)
var income = amounts_arr.filter(
function (item) {
return item > 0
}
)
console.log(income);
var totalIncome = 0;
$.each(income, function () { totalIncome += parseFloat((this)) || 0; });

//console.log(totalIncome); //回傳220
//傳回到income
$('#money-plus').text(`$${totalIncome}`);

  • expense支出加總
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//從陣列找出<0的值,放置expense
var expense = amounts_arr.filter(
function (item) {
return item < 0
}
)
console.log(expense);

var totalExpense = 0;
$.each(expense, function () { totalExpense += parseFloat((this)) || 0; });

console.log(totalExpense);
//傳回到expense
$('#money-minus').text(`$${totalExpense}`)


錯誤修正:

  • 調換推入陣列以及addTransactions的順序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//點擊按鈕的事件
$('.btn').click(function (e) {
e.preventDefault();
//console.log('click');
//取得表格中的值
const text_val = $('#text').val();
const amount_val = $('#amount').val();
let id = generateID();

//推入陣列
transactions.push({
id: id,
name: text_val,
amount: amount_val
});

addTransactions(id, text_val, amount_val, transactions);




localStorage.setItem('Transactions', JSON.stringify(transactions));

updateValue(transactions);
})
  • 將updateValue()放入事件中
1
2
3
4
5
6
7
8
//刪除鈕 要交易交出後再綁事件
$('.delete-btn').last().click(function () {
$(this).parent().remove();
let id = $(this).data('id');
//console.log(id);
deleteFromLocalstorage(transactions, id); //要記得傳入id
updateValue(transactions);
});

實作需求分析

  • 抓取api提供的匯率資料:使用jQuery,ajax
  • 選擇不同幣別,進行匯率換算
  • swap按鈕,使幣別交換

完成後的成品

jQuery.ajax 使用

1
2
3
4
5
6
7
8
9
10
11
$.ajax({
method: "get",
url: "https://v6.exchangerate-api.com/v6/06f04b805743079a1966771a/latest/USD",

})
.done(function( some_data ) {
console.log(data);
//成功抓到值的話,就會印出抓取到的資料
}).fail(function(){

})

  • 應用於exchangeRate:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $.ajax({
    url: `https://v6.exchangerate-api.com/v6/06f04b805743079a1966771a/latest/${currency_one}`, //注意符號
    method: 'get',//get,post,put
    dataType: 'json',
    success: function (data) {
    //console.log(data);
    const rate = data.conversion_rates[currency_two];
    //console.log(rate);
    $('#rate').text(`1 ${currency_one} = ${rate} ${currency_two}`);
    $('#amount-two').val((amountOne * rate).toFixed(2));
    }
    })

  • 詳細說明可以參照內文:計算匯率calculate函式

其他參考資料:

[ Alex 宅開發 ] 👨‍💻從 jQuery 入門到認識 JavaScript #5 Ajax 與非同步功能操作與原始碼探索

1
2
3
4
- `url`:來源,api的位置在哪裏
- `type`:api的呼叫方式
- `dataType`:它回給你的資料類型
- `data`:你要傳給它什麼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


$.ajax({
url: data.json,
type: 'get',//get,post,put,path,delete
dataType: 'json', //html,xml,text,jsonp
data: {}

}).then(function (res) {
//response <-> request
console.log(res);
return def.resolve();
}), function (err) {
console.log(err);
return def.reject();
}

計算匯率calculate 函式

  • 此函式為抓取匯率api的資料並帶入計算匯率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function calculate() {

var currency_one = $("#currency-one").val();
var currency_two = $('#currency-two').val();
var amountOne = $('#amount-one').val();
var amountTwo = $('#amount-two').val();

//https://app.exchangerate-api.com/dashboard/confirmed

$.ajax({
url: `https://v6.exchangerate-api.com/v6/06f04b805743079a1966771a/latest/${currency_one}`,//注意符號
method: 'get',//抓取值
dataType: 'json',
success: function (data) {
//console.log(data); //查看抓到的物件

const rate = data.conversion_rates[currency_two];
//console.log(rate);

$('#rate').text(`1 ${currency_one} = ${rate} ${currency_two}`);

//將值帶入
$('#amount-two').val((amountOne * rate).toFixed(2));
}
})


}

函式說明:

1.先將要抓取的值,宣告變數;分別有選擇的國家幣別、兌換的數量

2.串接資料

(1). 抓取資料的位置
url: https://v6.exchangerate-api.com/v6/06f04b805743079a1966771a/latest/${currency_one}

注意網址:
"https://v6.exchangerate-api.com/v6/06f04b805743079a1966771a/latest/USD"
注意網址後面有修改,要抓取會變動的變數: ${currency_one}

(2) 檢查是否有抓到要的資料:

  • 宣告:const rate = data.conversion_rates[currency_two];
  • console.log(rate);

  • 確定抓取的值是否正確:console.log($('#amount-two').val());

  • 將計算匯率帶入
    $('#amount-two').val((amountOne * rate).toFixed(2));

其他輸入值的抓取與連動

  • #currency-one的改變,會連動上面的函式計算結果

1
2
3
4
$("#currency-one").change(function () {
$("#rate").html("");
calculate();
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//抓取amountone
$("#amount-one").on("input", function () {
$("#rate").html("");
calculate();
})

$("#currency-two").change(function () {
$("#rate").html("");
calculate();
})

//抓取amountTwo
$("#amount-two").on("input", function () {
$("#rate").html("");
calculate();
})
  • 發現使用.append(),會出現累積div文字的問題

  • (1) 清除函式.append(),累積div文字呈現的問題
    $("#rate").html("");

  • (2) 將原本使用.append()的地方,修改為.text(), $('#rate').text(`1 ${currency_one} = ${rate} ${currency_two}`);

swap按鈕 (使幣別交換)

  • 宣告一個變數,紀錄交換前的幣別let temp = $("#currency-one").val();
  • 確認是否宣告正確:console.log(temp);

1
2
3
4
5
6
7
8
9
//swap按鈕
$('#swap').click(function () {
let temp = $("#currency-one").val();//設一個變數來存放currency-one
$("#currency-one").val($("#currency-two").val());
$("#currency-two").val(temp);
calculate();
})

calculate(); //放於最後
  • console.log($("#currency-one").val());
  • console.log($("#currency-two").val());


參考資料:
重新認識 JavaScript: Day 16 那些你知道與不知道的事件們

1. DOM 與樹狀結構

  • DOM: document:網頁內部

    KURO 老師:
    DOM 是一個將 HTML 文件以樹狀的結構來表示的模型,而組合起來的樹狀圖,我們稱之為「DOM Tree」。

Huli 老師:瀏覽器提供該橋樑,讓我們用js去改面畫面的東西

API (Application Programming Interface)

  • 應用程式 編程 介面

    document 物件 API 文件

  • getElementsByTagName : 抓取html中某tag的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!DOCTYPE html>
    <html lang="en">
    <body>
    <div>
    hello~

    </div>
    <div>
    yo!
    </div>
    <script>

    const elements = document.getElementsByTagName('div');
    console.log(elements);
    </script>
    </body>
    </html>

  • getElementsByClassName: 抓取html中某class元素(有好幾個相同名稱的class,會一並抓取)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>

<body>
<h1>DOM
</h1>
<div class="block1">
hello~

</div>
<div>
yo!
</div>
<script>

const elements = document.getElementsByClassName('block1');
console.log(elements);
</script>
</body>

</html>

  • getElementById : 注意:getElement 沒有加s!這是用來抓取指定ID

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <body>
    <h1>DOM
    </h1>
    <div class="block1">
    hello~

    </div>
    <div id="myyo">
    yo!
    </div>
    <script>
    const elements = document.getElementById('myyo');
    console.log(elements);
    </script>
    </body>

  • querySelector: 後面接的是css選擇器

  • 只會針對元素的第一筆資料,其他並不會被選入這時候可以使用

    • 選擇tag標籤:(‘div’):注意:使用此方式當html有多個div,他只會選擇到第一個符合的
    • 選擇cass : (‘.calssName’)
    • 選擇id : (‘#idName’)

  • 也可以選區某一tag下的tag

    • querySelector('div > a')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <body>
      <h1>DOM
      </h1>
      <div class="block1">
      hello~

      </div>
      <div id="myyo">
      yo!
      <a href="#">hello</a>
      </div>
      <script>

      const elements = document.querySelector('div > a');
      console.log(elements);
      </script>
      </body>
  • querySelectorAll: 可以選取多個元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<h1>DOM
</h1>
<div class="block1">
hello~

</div>
<div id="myyo">
yo!
<a href="#">hello</a>
</div>
<script>

const elements = document.querySelectorAll('div');
console.log(elements);
</script>

Eloquent JavaScript, 3rd Edition

順利選則元素之後,嘗試做些改變

可以看看,js如何與瀏覽器互動 ->JavaScript 與瀏覽器的溝通

BOM 與 window 物件

  • BOM (Browser Object Model,瀏覽器物件模型),是瀏覽器所有功能的核心,與網頁的內容無關。
    • window是瀏覽器的根物件,也是BOM的瀏覽器模型
  • window.location.hrrf => 可以知道現在網址
  • window.history.back(); => 回到網頁瀏覽的上一頁

參考資料

Day03-深入理解網頁架構:DOM
JS 筆記 - 認識 DOM 文件物件模型
MDN-Document
The HTML DOM Document Object
JavaScript 基礎知識-querySelectorAll
The Document Object Model
JavaScript入門系列:BOM和DOM筆記

程式柴

Function、array都屬於物件

物件的例子

1
2
3
4
5
6
7
8
9
var person = {
name: ['Bob', 'Smith'],
age: 32,
gender: 'male',
interest: ['music', 'movie'],
greeting: function () {
console.log('hi I\'m' + this.name[0] + '.');
}
}

物件的創建

var x={}

  • 有一隻狗,其名字、年齡,函式
    1
    2
    3
    4
    5
    6
    7
    8
    var dog = {
    name: 'tracy',
    age: 20,

    bow: function () {
    console.log('bow!bow!');
    }
    }
  • 用.呼叫屬性,或是dog[‘key’] :point_down:
  • 物件內的函式

添加屬性至物件上

  • 增加值的方式

    • dog.color = 'white';
    • dog['size'] = 'small'; =>較常用
  • 增加函式 => 一樣用.的方式

    • dog.bowbow = function () { console.log('bow!bow!~~') }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var dog = {
name: 'tracy',
age: 7,
bow: function () {
console.log('bow!');
}
}

//增加值的方式
//1
dog.color = 'white';
dog.eyes = 'big';
//2
dog['size'] = 'small';
//增加函式
dog.bowbow = function () {
console.log('bow!bow!~~')
}

陣列

程式柴

  • 陣列用 中括號[ ]

  • 陣列的計算是從零開始(取陣列的索引)

    • 呼應for 迴圈,從0開始,從索引第一個
      1
      2
      3
      4
      arr = [111, 222, 333]
      console.log(arr);
      console.log(arr[1]); //222
      //呼應for 迴圈,從0開始,從索引第一個
  • 陣列中的東西都是相同屬性,比較好操作

    1
    2
    3
    arr2 = ['dog', 'cat', 'sth']
    console.log(arr.length); //3
    arr3 = [111, 'cat', 'sth']

  • 練習:要紀錄10位學生的分數,並加總

    1
    2
    var score = [1, 3, 4, 5, 100];
    console.log(score);

    陣列基本操作 -> poppush 較常用

  • score.push(1000) -> 新增元素到現有陣列中,加入尾端

  • score.unshift(888) -> 加入陣列最前面

  • pop:從最末端抽走元素

1
2
3
4
5
6
7
var myHeros=['孫悟空', '佐助', '女超人', '美國隊長'];
//從最末端抽走元素
myHeros.pop();

//從頭抽走元素
myHeros.shift();
console.log(myHeros);

陣列替換元素

  • 在原本陣列中,選取要取代的該元素,後面加=,並輸入內容

陣列取得最後一個元素

  • 該陣列長度-1,就可以取得最後一個元素
    • 長度為七,最後一個的索引是6 :point_down:

用 indexOf 取得元素的索引

  • Of的o要大寫
1
2
3
4
var myHeros=['孫悟空', '佐助', '女超人', '美國隊長'];
//檢查誰誰誰有在陣列中嗎
console.log(myHeros.indexOf('達爾')); //印出-1,意指此元素不存在陣列中
console.log(myHeros.indexOf('美國隊長')); //印出3(在陣列中第幾個索引

切片 slice 與方法

1
2
3
4
var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
//用索引
console.log(arr.slice(2, 4));//不包含最後一個
console.log(arr.slice(2, 5));

  • 複製陣列
    1
    2
    var shallowCopy = fruits.slice(); // 這就是複製陣列的方式
    // ["Strawberry", "Mango"]

MDN slice
MDN array
JavaScript Array 陣列操作方法大全 ( 含 ES6 )

陣列與物件

  • 例子:被老師要求要記錄,學生的名字,分數,地址…

  • 以陣列的想法,會將各類別分類,放入同屬性的結果

    • 學生的分數,名字個一個陣列
    • 要取得同一位學生的所有資料,要個別從矩陣中拿取
  • 一個資料集合,就能代表一個學生 –>物件

    • 以下面的資料結構來代表學生的一些屬性

補充

  • 物件與陣列,與等號的關係,理解
    • 下方的結果都是false

因為:

  • var obj=[a:1],會將它放在某一個記憶體位置
  • var obj2=[a:1],會將它放在另一個記憶體位置

PS:即便內容,數值一樣,但js判斷的是記憶體位置不一樣

使用函式可以讓我們執行效率提升,不用重複撰寫,只需幾行語法便可以達到快速的運算、或是功能的執行。以下範例為數羊計數,透過函式以及迴圈,以幫助設定計數的起始以及終點,並快速的執行範圍內的計數。

1
2
3
4
5
6
7
8
//為什麼我們需要function
//DRY -> Don't repeat yourself
function echoSheep(index) {
console.log('第' + i + '隻綿羊')
}
for (var i = 1; i < 11; i++) {
echoSheep(i)
}
  • 與過去所學結合

  • 也可以return後面接物件
    • 回傳結果為20

簡單練習題

  • 創建空陣列,並於陣列中放數1,2,3,…10


要放入return,否則會出現undefined

  • 也可以將參數改成兩個數
1
2
3
4
5
6
7
8
function generateArray(a, b) {  //a,b 可以改為 from,to
var result = [];
for (var i = a; i <= b; i++) {
result.push(i);
}
return result;
}
console.log(generateArray(3, 10))

return

  • 函式中一放進return,在函式,return後面的部分就不會運行了

  • 不需要知道結果

  • 需要回傳值

  • 參數:函式後面要傳入的值
  • 引數:真正傳進去的東西

  • Arguments:為類陣列,屬於物件

表達式(Expression)與陳述句(Statement)的差異

  • 表達式:會回傳值
  • 陳述句:不會回傳值 (if…else,switch,等控制流程)

使用函式陳述式(Function Statement)與函式表達式(Function Expression)

  • 函式陳述式(Function Statement):在使用函式之前,放入helloFunctionStatement();是可以執行
1
2
3
4
5
6
7
//helloFunctionStatement();  放在前面沒關係
function helloFunctionStatement() {
console.log('this is Function Statement ');

}

helloFunctionStatement();
  • 函式表達式(Function Expression):反之,要放在宣告之後
    1
    2
    3
    4
    5
    6
    //helloFunctionExpression(); 放前面,會出現錯誤
    var helloFunctionExpression = function () {
    console.log('this is Function Expression ');

    }
    helloFunctionExpression();
    :::warning
    因為hoisting(變數提升)的關係
    :::

JS 原力覺醒 Day07 - 陳述式 表達式

變數能夠影響的範圍作用域( Scope 作用域:變數可以影響的範圍 )

  • 在function中,所宣告的 var a = 50;,範圍就只有涵蓋在函式中,不會影響到外面
1
2
3
4
5
6
7
var a = 100;
function helloWorld() {
var a = 50;
console.log(a);
}
helloWorld();
console.log(a);

  • 如果函式中,宣吿某變數,沒有加var/let,該變數就會影響外部
    1
    2
    3
    4
    5
    6
    7
    var a = 100;
    function helloWorld() {
    a = 50;
    console.log('function:', a);
    }
    helloWorld();
    console.log('外部:', a);

全域變數與區域變數

  • 使用var,let,const,來宣告

  • 全域變數:在瀏覽器中,沒有在fuction裡,直接進行宣告

    • 也可以用window來存取
    • window,是瀏覽器上的根物件
1
2
3
4
5
var y = 1;  //在外面宣告為 全域變數
function abc() {
//var,let,const
var x = 1
}
  • 區域變數:在function中進行宣告

    1
    2
    3
    4
    function abc() {
    //var,let,const
    var x = 1
    }
  • 不是變數 => 全域的屬性

    • z=1;
    • 也可以用window來存取

回呼函數 Callback Function

  • 把函數做為參數傳遞
  • 當檔案讀取完畢時,請來執行這個 function,並且把結果傳進來
  • heyFunc(console.log)->console.log 可以替換別的
1
2
3
4
5
function heyFunc(myFunction) {
myFunction('hi');
myFunction('hi');
}
heyFunc(console.log);

  • 非同步(Asynchronous):回呼常用來延續非同步行動完成後的程式執行

其他介紹 超入門 JavaScript 教學 13 - 回呼函式 callback

  • 以下範例:讓cb接到shout這個函式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function shout() {
    console.log('hello,i am done')
    }
    function countToSeven(cb) {
    for (var i = 1; i <= 7; i++) {
    console.log(i);
    }
    if (typeof cb === 'fuction')
    cb(); //啟動shout函式
    }
    countToSeven(); //會數1-7
    //要數完七之後,說'hello,i am done
    countToSeven(shout);
    countToSeven(1);//1不是函式,所以在if判斷句終止

    //每隔一段時間,幫我做一次
    setInterval(shout, 2 * 1000);//每隔兩秒執行sout
    //在多少時間之後,幫我執行
    setTimeout(shout, 1 * 1000)

    js內建函式

    • 每隔一段時間,幫我做一次
      setInterval(shout, 2 * 1000);//每隔兩秒執行sout
    • 在多少時間之後,幫我執行
      setTimeout(shout, 1 * 1000)

參考資料:

重新認識 JavaScript: Day 18 Callback Function 與 IIFE
JavaScript 中的同步與非同步(上):先成為 callback 大師吧!
JS20min Day — 18 關於回呼生活化 (Callback)

匿名函式

  • heyFunc裡面的function沒有名稱
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function heyFunc(myFunction) {
    myFunction('hi');

    }
    heyFunc(function (message) {
    console.log('message is:', message);
    });
    //message is:hi
    </script>

立即函式(IIFE)

Luka

  • 它是沒有名字的函式,要立即調用
    • 不讓函式內的變數污染到外面的東係
    • 讓jquery的$$變成是jquery使用
      1
      2
      3
      4
      5
      (function (name) {
      var str = 'Hi ' + name + '!';
      console.log(str);
      })('Sam');

  • 一般常見寫法
    1
    2
    3
    4
    5
    var sayHi = function (name) {
    var str = 'Hi ' + name + '!';
    console.log(str);
    }('Peter')//在後面立即執行
    // sayHi("Peter");

[筆記] 談談JavaScript中的IIFEs(Immediately Invoked Functions Expressions)
[筆記] 為什麼我們要用IIFEs(Immediately Invoked Functions Expressions)

hoisting 變數提升淺談

  • js的變數與函式的宣告提升

  • 不管在哪一行使用了變數,都視為第一行宣告

    比較好的流程 By 彭彭

但在js有hoisting 變數提升

  • 就算把變數宣告放在後面,依然可以運作(會將var x提升到最前面)

進一步細節…


電腦解讀如下

  • var x會提升


    結果會印出undefined

在函示fuction的變數提升

  • 先宣告函式,在能呼叫函式執行

  • 在js先呼叫,依然可以運作

其他狀況…(把函式裝到變數中當作資料)

  • 在此情形,它會出現錯誤,回報test不是一個函式

  • 程式的解讀 :point_down:
  • 只有var test被提升

程式柴

  • 沒有宣告

常用的內建函式

Number類型

  • 字串轉數字

  • parseInt 是取整數,若遇到如20.35,就只會取到20

  • 遇到小數點且要保留的時候,使用parseFloat
    • 結果為30.35

  • Math.ceil()=> 無條件進位
  • Math.floor()=> 無條件捨去
  • Math.round() => 四捨五入
  • Math.sqrt() => 開根號
  • Math.pow() => 次方
  • Math.radom() => 產生隨機數(0-1,不包含1)
  • toString() => 數字轉字串
    • 或是加空字串

String類型

  • toUpperCase=> 轉大寫

  • toLowerCase => 轉小寫

  • indexOf => 找出字串中,單字的索引(用於檢查某字是否存在字串中)

  • replace() => 取代

    • 用正規表達式來選擇 某些字

  • split() => 切字串

    1
    2
    3
    4
    //split較常使用的情形
    //當資料為一連串字串,利用逗號切割 變成陣列,比較好運用
    var str = 'data1,data2,data3,data4';
    console.log(str.split(','));

  • trim() =>從一個字符串的两端刪除空白字符

    1
    2
    3
    4
    5
    6
    7
    const greeting = '   Hello world!   ';

    console.log(greeting);
    // expected output: " Hello world! ";

    console.log(greeting.trim());
    // expected output: "Hello world!";

Array 類型

  • Array.prototype.join()
  • 方法會將陣列(或一個類陣列(array-like)物件)中所有的元素連接、合併成一個 ‘字串’,並回傳此字串(在陣列中每個元素間插入設定的東西)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const elements = ['Fire', 'Air', 'Water'];

    console.log(elements.join());
    // expected output: "Fire,Air,Water" //會變成字串

    console.log(elements.join(''));
    // expected output: "FireAirWater"

    console.log(elements.join('-'));
    // expected output: "Fire-Air-Water"
  • Array.prototype.map()
  • 建立一個新的陣列,其內容為原陣列的每一個元素經由回呼函式運算後所回傳的結果之集合。
    1
    2
    3
    4
    5
    6
    var arr = [1, 2, 3];
    console.log(
    arr.map(function (x) {
    return x * -1
    })
    )
1
2
3
4
5
6
7
8
9
10
11
//也可以接著寫下去
var arr = [1, 2, 3];
console.log(
arr
.map(function (x) {
return x * -1
})
.map(function (x) {
return x * 2
})
)

  • Array.prototype.filter()

  • 過濾

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arr = [1, 2, 3, -5, 3, -2];
    console.log(
    arr
    .map(function (x) {
    return x * 2
    })
    .filter(function (x) {
    return x > 0
    })
    //把負數過濾掉,留下正數
    )

  • Array.prototype.slice()

  • Array.prototype.splice()

    • splice 1.插入元素
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const months = ['Jan', 'March', 'April', 'June'];
      months.splice(1, 0, 'Feb');
      // inserts at index 1
      console.log(months);
      // expected output: Array ["Jan", "Feb", "March", "April", "June"]

      months.splice(4, 1, 'May');
      // replaces 1 element at index 4
      console.log(months);
      // expected output: Array ["Jan", "Feb", "March", "April", "May"]

  • splice 2.刪除元素

    1
    2
    3
    4
    5
    var myFish = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon'];
    var removed = myFish.splice(3, 1);

    // removed 為 ["mandarin"]
    // myFish 為 ["angel", "clown", "drum", "sturgeon"]
  • Array.prototype.sort()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const months = ['March', 'Jan', 'Feb', 'Dec'];
    months.sort();
    console.log(months);
    // expected output: Array ["Dec", "Feb", "Jan", "March"] //按照第一個字母順序排列

    const array1 = [1, 30, 4, 21, 100000];
    array1.sort();
    console.log(array1);
    // expected output: Array [1, 100000, 21, 30, 4] //是一字串,以第一個數字來排列

  • 依照數字大小排列

    1
    2
    3
    4
    5
    6
    7
    var arr = [1, 30, 4, 2];
    arr.sort(function (a, b) {
    if (a === b) return 0;
    if (b > a) return -1; //不換
    return 1; //正數,換位置
    })
    console.log(arr);

「回傳」與「印出」的差異

  • 函式可以透過 return 回傳函式運算完的結果

    述程式碼的執行:

1.console.log(add(1, 2)) => 帶入functoin,console.log(a, b); => 印出1,2
2.往下,到return undefined; 然後進入add(1, 2)的回傳值,就會是undefined

1
2
3
4
5
6
7
8
function add(a, b) {
console.log(a, b);
return undefined; //不加return,預設會是undefined
}

console.log(add(1, 2))


  • 以為 add(1, 2) 的回傳值是 3,但不是,那是因為它在裡面 console.log() 所以把結果印出來了。
    1
    2
    3
    4
    function add(a, b) {
    console.log(a + b)
    }
    add(1, 2)
  • 如果真的要有回傳值的話要這樣寫:
    1
    2
    3
    4
    function add(a, b) {
    return a + b
    }
    console.log(add(1, 2))

參考資料: JavaScript 初心者筆記: 函式實際運作 - 回傳值與函式間互相傳遞
,後設鐵人 Day4:請幫我簽個名好嗎?

Immutable 觀念 (不可變)

  • 除了物件、陣列以外,其他都是不可變的
  • 沒辦法改它,就只能回傳新的
    1
    2
    3
    4
    var a = 'hello';
    a.toUpperCase;
    console.log(a);
    //印出:hello,沒有改變原本變數的內容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var a = 'hello';
    a = a.toUpperCase(); //要回傳給a,把原本的a值蓋掉
    console.log(a);

    //或是增設一個新變數
    var a = 'hello';
    var b = a.toUpperCase();
    console.log(b);

簡易控制流程的撰寫

1
2
3
4
5
6
7
8
9
10
var str = '只被當一科';
if (str === 'all pass') {
console.log('帶你去歐洲玩');
} else if (str === '只被當一科') {
console.log('帶你去宜蘭玩');
} else {
console.log('禁足');
}
//帶你去宜蘭玩

使用switch來撰寫(太多如果的話可以改用switch)

  • 使用break,是用來跳出swich迴圈(換句話說,沒有寫break的話會使程式一直往下跑)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //用switch 改寫 if else
    var key = 'all pass';
    switch (key) {
    case 'all pass':
    console.log('帶你去歐洲玩');
    break;
    case '只被當一科':
    console.log('帶你去宜蘭玩');
    break;

    default:
    console.log('禁足');
    break;
    }
    // 帶你去歐洲玩

迴圈基本介紹

loop:一直做一樣的事情
無窮迴圈,發生的情形:沒有設終止條件,或是終止條件設錯 (control+c 終止它)

  • 可在程式最前面,加入debugger

1.迴圈for

彭彭的課程

用 for 迴圈數綿羊數到一百隻,直到睡著

  • var後面宣告的變數,可以替換不同字母
    1
    2
    3
    4
    5
    6
    7
    // for(初始值;終止條件;每次執行的語句)
    for (var i = 0; i < 100; i++) {
    console.log("第" + (i + 1) + "隻綿羊");

    }
    //會印出1~9
    //i++ 意思指 i+=1 ,也是i=i+1

    用 for 迴圈倒著數綿羊

    1
    2
    3
    4
    5
    6
    for (var k = 100; k > 0; k--) {
    console.log("第" + k + "隻綿羊");
    if (k === 1) {
    console.log('睡著了!');
    }
    }

    用 for 迴圈 數到第 31 隻綿羊就睡著了! (break)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (var i = 1; i <= 100; i++) {
    console.log("第" + i + "隻綿羊");
    if (i === 31) {
    console.log('睡著了!');
    break;
    }
    if (i === 10) {
    console.log('覺得想睡了!'); //到10不會停止
    }
    }

    用 for 迴圈 跳著數

  • 奇數 -> 對2取於數為1者

    1
    2
    3
    4
    5
    6
    for (var i = 1; i <= 10; i++) {
    if (i % 2 === 1) {
    console.log("第" + i + "隻綿羊");
    }

    }
  • 印出1,4,7,10

    1
    2
    3
    4
    5
    for (var i = 1; i <= 10; i++) {
    if (i % 3 === 1)
    console.log("第" + i + "隻綿羊");

    }

用 for 迴圈 我只想跳過第四隻綿羊

  • 使用continue:會跳過一次(跳過設定的條件),然後繼續下一次迴圈
  • 注意語法的順序,如果console.log()在if..continue之前,會變成先讀取所有的i,不會跳過指定位置
1
2
3
4
5
6
7
for (var i = 1; i <= 10; i++) {
if (i === 4) {
continue
}
console.log("第" + i + "隻綿羊");

}

for與陣列的搭配

  • i是依照索引來計算,故應該是要0,1,2,3,4 => <5
    • 最後一個索引值是4(第5個沒有值)

2.迴圈while

透過彭彭的課程來認識迴圈while

迴圈思考方式:

彭彭的課程

1
2
3
4
5
6
7
8
//1+2+...+50 的運算流程 
var sum=0;
var n=1;
while (n<=50){
sum=sum+n;
n++
}
alert(sum);

方式:

和迴圈相關的變數追蹤(sum,n)

第0次迴圈:先判斷n是否<=50
sum:0 ,n:1
第1次迴圈:
sum:1,n:2
第2次迴圈:先判斷n是否<=50 (根據上一個n是2)
sum:(1+2)=3,n:3
第3次迴圈:
sum:(3+3)=6,n:4

第50次迴圈:
sum:1275,n:51
第51次迴圈:因大於50,故跳離回圈結束

延續上面的思考,加入continue

1
2
3
4
5
6
var x=0;
for(var i=0;i<100;i++){
if(1%2= = =0){continue}
x++
}
alert(x)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
變數追蹤(x,i)
0:
x:0,i:0
1:
x:0 (i為0,對2取餘數為0,進入continue,然後重新開始回圈,沒有進入x++), i:1
2:
x:1(i為1,對2取餘數不為0,所以沒有執行continue,直接進入x++),i:2

3:
x:1,i:3
4:
x:2,i:4
...
100:
x:50,i:100

用 while 也可以達到相同的屬羊效果

  • 印出0~10
  • 相對for囉唆一點
1
2
3
4
5
let x = 0;
while (x < 10) {
console.log(x);
x++;
}

  • 利用console來debug

    1
    2
    3
    4
    5
    6
    7
    8
    let x = 0;
    while (x < 10) {
    //利用console來debug
    console.log(x)
    x++;
    console.log("第" + x + "隻綿羊");

    }

  • 搭配continue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let x = 0;
    while (x < 10) {
    //利用console來debug
    console.log(x)
    x++;
    if (x == 4)
    continue;
    console.log("第" + x + "隻綿羊");

    }
  • 搭配break

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let k = 0;
    while (k < 10) {

    k++;
    console.log("第" + k + "隻綿羊");
    if (k == 5)
    break;


    }

3.迴圈 do..while

  • ()放條件,{}放區塊
    1
    2
    3
    4
    5
    6
    let i = 0;
    do {
    i++
    console.log('第' + i + '隻綿羊')
    } while (i < 10)

    與while的不同

  • 因為i<10的條件在最後面,所以他會先跑完do,再到while終止
    • i++完之後,跑到while(11>10),跳出迴圈,執行console.log
      1
      2
      3
      4
      5
      6
      let i = 10;
      do {
      i++
      console.log('第' + i + '隻綿羊')
      } while (i < = 10);
      console.log('i=',i); //i=11
  • 另一種寫法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let i = 10;
    do {
    i++
    console.log('第' + i + '隻綿羊')
    if (i>10){
    break //同等於終止條件
    }

    } while (true); //如果沒有if條件,這裡又設true,此狀況下會形成無限迴圈
    console.log('i=',i); //11

  • 同樣放入while回圈,結果就不會顯示,因為一開始k=10,進入比較,就不符合條件
    1
    2
    3
    4
    5
    let k = 10;
    while (k < 10) {
    k++;
    console.log('第' + k + '隻綿羊')
    }

雙層回圈

用 99 乘法表學雙層迴圈

1
2
3
4
5
6
7
8
9
10
11
12
13
//有兩層,需數2次
for (i = 1; i < 10; i++) {

for (k = 1; k < 10; k++) {

console.log("i:" + i, "k:" + k);
// console.log(i + '*' + k + '=' + (i * k));
let result = i * k;
let str = i + "x" + k + "=" + result;
console.log(str);
}

}

參考資料:
MDN continue
重新認識 JavaScript: Day 09 流程判斷與迴圈
JS跳出循环的三种方法(break, return, continue)