Open zuppachu opened 5 years ago
可以在哪裡執行 JS?
// 在 index.html
<script src="./index.js"></script>
// ./ 代表同一個資料夾底下
以上兩個為不同的執行環境,因此語法上有一些差異。
那兩者的差別在哪呢?
有些時候某些語法可能在瀏覽器上面不支援,或是在 node.js 不支援。例如:Node.js 引入模組的`require` 用法在瀏覽器上無法使用。
## DOM 是啥毀?
全名是:Document Object Model。
為 JS 跟瀏覽器之間的橋樑。可以說是瀏覽器提供 DOM,讓程式語言,如:JS 可以操控 HTML 內的元素,用來改變介面(網頁的面貌)。
<!----- DOM 操作範例 ------>
## 如何選到想要的元素
>最好把 `<script></script>` 放在 `<body>` 結束之前,因為瀏覽器是由上而下跑的。如果先放前面的話, DOM 會沒抓取到跑出空白的答案
- getElementsByTagName
```javascript
const elements = document.getElementsByTagName('div')
getElementsByClassName
<body>
<div class="block"> // by classname 所以不用 .
hello
</div>
<script>
const elements = document.getElementsByClassNAme('block')
console.log(elements)
</script>
</body>
getElementById 注意:因為 id 只有一個,所以 element 沒有 s
querrySelector 最推薦的用法。
但需注意:此方法只回傳 document 第一個符合的元素
用法:
#
.
<tag 的名字>
> + ~
<body>
<div id="block">
hello
</div>
<div class="box"></div>
<script>
const elements = document.querrySelector('#block')
cosnt ans = document.querrySelector('.box')
console.log(elements)
</script>
</body>
// 如: id 底下的 <a>
const elements = document.querrySelector('#block > a')
<div class="test">
<p>123</p>
<p>321</p>
</div>
var test = document.querySelectorAll('.test > p');
test[1].style.color = 'red';
// 得 紅色 321
querrySelector
andquerrySelectorAll
是 CSS 的選擇器。
<body>
<div id="block">
hello
</div>
<div class="box"></div>
<script>
const elements = document.querrySelector('#block')
elements.style.paddingTop = '10px';
elements.style['padding-left'] = '20px';
elements.style.background = 'red';
</script>
</body>
但上面這種寫法不方便更改和維護,所以不建議使用這種方式寫 CSS。比較好的方法是另寫一個 class,當事件觸發時,在幫元素加上變動。
<style>
.active {
background: blue;
}
</style>
<body>
<div id="block">hello</div>
<div class="box">Ciao</div>
<script>
const elements = document.querrySelector('#block')
elements.classList.add('active')
</script>
</body>
加上一個名叫 'active' 的 class 在 <div id='block'>
的裡面。
移除的話用: element.classList.remove('active')
還有一種是:toggle。這個很妙,比如你寫:
element.classList.toggle('active')
// 本來沒有 -> 變成新增
// 本來有 -> 變成移除
innerText:
只能寫入存文字
只顯示標籤的內容,不包含標籤
element.innerText = 'icecream'
<body>
<div id="block">
Ciao
<a>hello<a>
</div>
<script>
const element = document.querrySelector('#id')
console.log(element.innerText)
</script>
</body>
// 得 // ciao // hello
- innerHTML:
顯示標籤內的所有內容
與 innerText 不一樣的地方是可以寫入標籤
`element.innerHTML = '<div>icecream</div>'`
```javascript=
<body>
<div id="block">
Ciao
<a>hello<a>
</div>
<script>
const element = document.querrySelector('#id > a')
console.log(element.innerHTML = 'I am a link')
</script>
</body>
// 得 a= I am a link
outerHTML
顯示包含標籤
可以改變標籤內容,包括標籤本身
element.outerHTML = '<div>gelato<div>icecream</div></div>'
<body>
<div id="block">
Ciao
<a>hello<a>
</div>
<script>
const element = document.querrySelector('#id > a')
console.log(element.outerHtml)
</script>
</body>
// 得
## 插入與刪除元素:appendChild 與 removeChild
- 刪掉元素:
首先要先知道他的上一層(parent)是誰 ,才可能刪除元素。
const element = document.querySelector('#block') element.removeChild(document.querySelector('a'))
- 新增元素:
const element = document.querySelector('#block') cosnt item = document.createElement('div') // 建立新 parent element.appendChild(document.querySelector('p'))
need to checkout more api ?
click
點擊
e.target
: 點擊到的元素e.screenX
: 滑鼠離視窗左邊的距離e.screenY
: 滑鼠黎視窗上邊的距離keydown
按下按鍵
e.key
: 按鍵號碼就是選定頁面上的某一元素,然後監測此元素:
而上述行為會發生,是因為有人點擊某元素進而「觸發」此動作。
最經典的栗子是 click 點擊事件,如下。
const element = document.querySelector('#block')
element.addEventListener('click', onClick)
//監聽的事件名稱 = click
//發生的動作是 onClick,它是一個 callback function
function onClick() {
alert('click!')
}
比較常見/偷懶的寫法是直接宣告在裡面:
const element = document.querySelector('#block')
element.addEventListener('click',function() {
alert('click!')
})
這種寫法因為沒有名字又叫做匿名函式,但他跟 callback function 沒有關係喔~
總之,上述程式碼代表:
它的目的就是為了不要讓其他事情被阻塞 (block),而延伸出來的方式。 概念有點像是美食街的呼叫器,『好了後再叫我』!
document.getElementById('btn').addEventListener('click', function() {
...
})
//arrow function 寫法:
document.getElementById('btn').addEventListener(‘click', () => {
})
上面的 function
: 等到 click 事件發生後,才呼叫裡面的函式 = callback function
const element = document.querySelector('#block')
element.addEventListener('click',function(e) {
console.log(e)
})
得 e 是個 MouseEvent。
若要知道我到底在網頁上點(click)了哪個元素可用: e.target
const element = document.querySelector('input')
element.addEventListener('keydown', function(e) {
console.log(e.key)
})
// 在輸入窗內輸入的每個按鍵都會被印出來(keydown)
// 不知道哪個關鍵字的時候查文件: MDN/Web/Events/keydown
實作 event:
<body>
<button class='change-btn'>change</button>
<script>
const element = document.querySelector('.change-btn')
element.addEventListener('click', function(e) {
document.querySelector('body').classList.toggle('active')
})
</script>
</body>
回顧:表單實作筆記
通常用在表單驗證上。
<body>
<form class="form">
<div>
username:<input name='username'/>
password:<input name='password1' type='password' />
password check:<input name='password2'type='password'/>
</div>
<input type='submit'/>
</form>
<script>
const element = document.querySelector('.form')
element.addEventListener('submit', function(e) {
const input1 = document.querySelector('input[name=password1]')
const input2 = document.querySelector('input[name=password2]')
// 驗證密碼是否相同,如果不同的話,防止表單送出
if(input1.value !== input2.value) {
alert('密碼錯誤')
e.preventDefault // 防止預設發生。(預設就是表單送出)
}
})
</script>
</body>
除了上述表單事件外,還有以下幾個適用 preventDefault 的情況:
<a>
:阻止跳網址<input>
的 keypress
事件:阻止輸入按鍵(不懂為什麼用 link 卻要阻止 link 的行為???)
<div class="outer">
outer
<div class="inner">
inner
<a href="#" class="btn">btn</a>
</div>
</div>
<script>
addEvent('.outer')
addEvent('.inner')
addEvent('.btn')
function addEvent(className) {
document.querySelector(className)
.addEventListener('click', function(){
console.log(className)
})
}
</script>
裡面的outer
、inner
、btn
分別監聽 click
事件。
但當我們點擊最內層元素btn
時,連帶上兩層的元素(inner 和 outer)也都被觸發了,這詭異的現象叫「冒泡事件」。
事件傳遞機制有三階段:
// PhaseType
const unsigned short CAPTURING_PHASE = 1; 捕獲
const unsigned short AT_TARGET = 2; 元素本身
const unsigned short BUBBLING_PHASE = 3; 冒泡
當點擊 td 時,這一個點擊的事件會先從 window 開始往下傳,一直傳到 td 為止,到這邊就叫做「捕獲階段」。
接著事件傳遞到 td 本身,這時候叫做「AT_TARGET」。
最後事件會從 td 一路傳回去 window,這時候叫做「冒泡階段」。
但其實只要記住兩個原則:
要改變,需在 .addEventListener
方法加上第 3 個參數,為 boolean 值。
事件傳遞方式是無法改變的,只能改變監聽的時機點。
addEvent('.outer')
addEvent('.inner')
addEvent('.btn')
function addEvent(className) {
document.querySelector(className)
.addEventListener('click', function(e){
console.log(className, '冒泡', e.eventPhase)
}, false) // false : 放在冒泡階段上 (預設)
document.querySelector(className)
.addEventListener('click', function(e){
console.log(className, '捕獲', e.eventPhase)
}, true) // true : 放在捕獲階段上
}
可是噢,當我們重新點擊最內層的 btn
時,我們看到的順序是: 1 > 2 > 3
.outer 捕獲 1
.inner 捕獲 1
.btn 冒泡 2 // 注意
.btn 捕獲 2 // 注意
.inner 冒泡 3
.outer 冒泡 3
btn
會「先冒泡再捕獲」是因為冒泡的綁定寫在最前面。= 阻止事件繼續傳遞
你加在哪裡,事件的傳遞就斷在那邊,不會繼續往下傳遞。
現在我們將前面的例子,改成先「捕獲」再「冒泡」~
// 在冒泡流程上加了 e.stopPropagation()
function addEvent(className) { document.querySelector(className) .addEventListener('click', function (e) { console.log(className); }, true) document.querySelector(className) .addEventListener('click', function (e) { console.log(className); e.stopPropagation(); // 阻止冒泡事件傳遞 }, false) };
接著一樣點擊 `btn` ,會輸出如下:
.outer 1 .inner 1 .btn 2 .btn 2
可以看出來捕獲階段(1) 和 At_Target(2) 繼續傳遞,但在冒泡階段(3)則被阻止了。
- 那再來看看,如果改成「阻止捕獲事件」呢?
```javascript=
function addEvent(className) {
document.querySelector(className)
.addEventListener('click', function (e) {
console.log(className, '捕獲');
e.stopPropagation(); // 阻止捕獲事件傳遞
}, true)
document.querySelector(className)
.addEventListener('click', function (e) {
console.log(className, '冒泡');
}, false)
};
一樣再點擊一次 btn
得到下列結果:
.outer 1
只得到 outer
!Why oh why?
因為事件傳遞的順序是根據 1 > 2 > 3,而我們這次在捕獲階段就阻止了,根本沒法傳到 btn
,所以後續的 2、3 階段就沒有執行下去啦。
這邊指的「事件傳遞被停止」,意思是說不會再把事件傳遞給「下一個節點」,但若是你在同一個節點上有不只一個 listener,還是會被執行到。
若是你想要讓其他同一層級的 listener 也不要被執行,可以改用e.stopImmediatePropagation();
話鋒一轉,若點擊outer
呢?
.outer 2
.outer 2
因為 .outer
上面並沒有元素可以傳遞,所以也沒有捕獲階段 (1),兩次都輸出都是在階段二: target phase。
由此可以觀察到一件有趣的事:
如果該元素是最上層的元素,事件傳遞方式指定為捕獲,那底下所有的元素事件傳遞都會被停止。
阻止之後同一層級的 listener 不要被執行到,可用此法。
<button id="myBtn">Try it</button>
<script>
function myFunction(event) {
alert ("Hello World!");
event.stopImmediatePropagation(); // Try to remove me
}
function someOtherFunction() {
alert ("I will not get to say Hello World");
}
var x = document.getElementById("myBtn");
x.addEventListener("click", myFunction);
x.addEventListener("click", someOtherFunction);
</script>
得到:彈出視窗的Hello World!
,但若刪除了 Try to remove me 那段,會得到兩個彈出視窗:
因此可看出 e.stopImmediatePropagation()
是阻止之後相同層級的 listener。
它的存在是為了減少 DOM 的操作,以提高效率。
假如說我們有 100 li
,若為每個 li
都添加事件,會造成 DOM 抓取次數太多,瀏覽器效能降低。為了不要重複做一樣的事情,我們可用事件代理方式!
而事件代理是利用「事件傳遞的冒泡原理」,將子元素的監聽事件綁在父元素上。
例如:當我們在最外層添加點擊事件時,裡面的 ul
、 li
、a
的點擊事件都會冒泡到最外層的節點上,委託它代理執行事件。
其中好處是:
<!DOCTYPE html>
<html>
<body>
<ul id="list">
<li data-index="1">1</li>
<li data-index="2">2</li>
<li data-index="3">3</li>
<a>點我<a>
</ul>
<script>
document.getElementById('list').addEventListener('click', (e) => {
console.log(e.target.getAttribute('data-index'));
})
</script>
</body>
</html>
DOM 的事件傳遞機制:捕獲與冒泡 該掌握:
e.preventDefault
and e.stopPropagation
的差別請看 codepen
<!DOCTYPE html>
<html>
<head>
<style>
.box {
border-radius: 5px;
width: 40%;
display: flex;
flex-direction: column;
margin: 80px auto;
background: lightblue;
}
label {
text-align: center;
margin-top: 5px;
}
h2 {
text-align: center;
}
button {
width: 30%;
height: 30px;
margin: 5px auto 25px auto;
border-radius: 5px;
background: blueviolet;
font-size:14px;
color: white;
font-size: 15px;
}
.result {
width :55%;
height: 40px;
background: whitesmoke;
margin: 10px auto;
padding: 5px 10px 0px 10px;;
border-radius: 5px;
text-align: center;
text-justify: kashida;
}
.opt {
margin: 30px auto;
}
.opt ~ label {
padding: 5px;
}
</style>
</head>
<body>
<div class="box">
<h2>簡易密碼產生器</h2>
<div class="result"></div>
<div class="opt">
<label><input type="checkbox" name='num'>Numbers</label>
<br>
<label><input type="checkbox" name='upper_en'>Uppercase English</label>
<br>
<label><input type="checkbox" name='lower_en'>Lowercase English</label>
</div>
<button class="btn">產生</button>
</div>
<script>
// 所有事情要在按下「產生」後進行,所以監聽「產生按鈕」
document.querySelector('.btn').addEventListener('click', function(e) {
/* 想做出沒有選擇任何選項無法按下按鈕
if (document.querySelectorAll('.opt') !== document.querySelectorAll('.opt').checked) {
alert('plz choose one option');
e.preventDefault();
}
*/
let avaChar = '';
if (document.querySelector('input[name = num]').checked) {
avaChar += '0123456789'
};
if (document.querySelector('input[name = upper_en]').checked) {
avaChar += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
};
if (document.querySelector('input[name = lower_en]').checked) {
avaChar += 'abcdefghijklmnopqrstuvwxyz'
};
let ans = '';
for (let i = 0; i < 10; i += 1) {
const number = Math.floor(Math.random() * avaChar.length);
ans += avaChar[number];
}
document.querySelector('.result').innerText = ans;
}
)
</script>
</body>
</html>
參考:codepen
<!DOCTYPE html>
<html>
<head>
<style>
input {
border: 1px solid grey;
background: gainsboro;
border-radius: 5px;
}
.container {
margin-left: 40px;
width: 60%;
}
h2 {
margin: 40px auto;
}
.add-btn {
border: 1px solid balck;
margin-left: 0px;
margin-bottom: 20px;
background: white;
border-radius: 5px;
}
.delete {
border: 1px solid black;
background: white;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h2>動態表單通訊錄</h2>
<div>
<button class="add-btn">新增聯絡人</button>
</div>
<div class="contacts">
<div class="row">
Name:<input name="name" />
Mobile:<input name="mobile">
<button class="delete">刪除</button>
</div>
</div>
</div>
<script>
document.querySelector('.add-btn').addEventListener('click', function(e) {
const div = document.createElement('div');
div.classList.add('row');
div.innerHTML = `
Name:<input name="name" />
Mobile:<input name="mobile">
<button class="delete">刪除</button>
`
document.querySelector('.contacts').appendChild(div)
}
)
document.querySelector('.contacts').addEventListener('click', function(e) {
if (e.target.classList.contains('delete')) {
document.querySelector('.contacts').removeChild(e.target.closest('.row'))
}
})
</script>
</body>
</html>
cookie 其實就是個小型文字檔,會自動帶到 server 去。且是個可以存資料的地方。
PS: 在 Network 可以看到。
Set-Cookie
cookie 可以被用來當作身份驗證。(通行證的小故事)
Local Storage application -> localstorage to check
有時候在網頁上不小心回到上一頁,但資訊還在,他們就是用此法儲存的。
只能存字串!
Section Storage 一段期間內的存檔。
不同分頁無法共用同個 storage。只存在一個分頁裡面。可應用的範圍較小。
Client --- request ---> Server
Server --- response ---> Client : HTML or JSON
node.js
瀏覽器上的 js
兩者差距:
名稱 | 被限制住? | 備註 |
---|---|---|
node.js | no | |
瀏覽器上的 JS | yes | 1. 被瀏覽器規則限制住。 2. 瀏覽器會加上一些東西:版本或是額外資訊 |
參考:
<div class="app">
<form method="GET" action="https://google.com">
username:<input name="username">
<input type="submit" />
</form>
</div>
<script>
</script>
dev tool 裡面的 Network -> 勾選 Preserve log
流傳過程:
瀏覽器 --- request ----> Server
Server ---- response ----> 瀏覽器 (render response)
此法跟 js 一點關係都沒有,存粹透過 HTML 元素。
它比較像是:我要到這個頁面,我要帶什麼參數到這頁面?
表單缺點:每次要新的資料時都需換頁。
若只是想要擷取某些資料,可以利用下面的方法。
任何非同步跟伺服器交換資料的 js 都可叫 AJAX。
他不會換頁。
<body>
<div class="app"></div>
<script>
const request = new HMLHttpRequest()
request.onload = function() {
if (requset.status >= 200 && request.status < 400) {
console.log(request.responseText)
} else {
console.log('err')
}
}
request.onerror = function() {
console.log('error')
}
request.open('GET', "送去某網址", true)
// true = 同步,要在餐廳排隊等很久
// false = 非同步,「好了叫我」,可以中途做其他事
request.send();
</script>
</body>
FE102 課程簡介
三個重要面向:介面、事件、資料!