# [FE102] 前端必備:JavaScript(DOM)
###### tags: `JavaScript`
## JavaScript 的執行環境:
主要是這兩個:
1. node.js
2. 跑在browser上
在不同環境底下,能夠跑的javascript語法不一定都可行
ex:
```javascript=
//這個語法就不行,因為拿到request這個module是在node.js裡面特定的
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
})
```
## console
- 在於將( )的內容輸出至主控台
- 利用javascript請瀏覽器執行某些動作時,能夠以js進行操作的零件,就稱為物件.
- 為了方便操作,每一個物件都有自己的名稱(詳請可以看p2-13)
主控台的名稱是"console"
```javascript=
console.log('鸚鵡學舌')
```
console:物件/log:方法/'鸚鵡學舌':參數
## document
網頁內容的名稱是"document"
### getElementById
```javascript=
document.getElementById('class').textContent =new Date();
//document:物件
//getElementById:方法
```
其餘方法抓取document的方法
### queryselector
```javascript=
document.queryselector('option[value="index.html"]')
```
option[value="index.html"] :屬性選擇器
要取得value為index.html的元素
==注意:只有第一個元素會被選取==
### queryselectorAll
* 利用html的class屬性來取得多個元素,並全部都設定相同的事件
* 資料抓出來會是陣列,可以搭配for迴圈使用
```javascript=
var thumb = document.queryselectorAll('.thumb')
for(var i=0;i<thumb.length;i++){
thumb[i].onclick= function(){
document.getelementbyid('bigimg').src=this.dataset.image
}
}
```
補充說明
```
this:代表發生事件的元素本身(為onclick事件)
data-*:屬性 (此自定為data-image)
讀取此屬性: this.dataset.image(自訂的名稱)
```
[codepen範例](https://codepen.io/norriswu/pen/NJNrOW)
***
### textContent
物件屬性代表該物件目前的狀態,可以執行讀取或是改寫
此段翻成中文為
**將class的內容設為new Date()**
createElement(標籤名稱)
appendChild(想添加的子元素,並往下添加)
```javascript=
for(var i=0;i<todo.length;i++){
var li= document.createElement('li');
li.textContent=todo[i]
document.getElementById('#list').appendChild(li)
}
```
## window
```javascript=
var answer = window.prompt('是否要閱讀遊戲說明')
console.log(answer)
```
這是window物件用的方法,在對話框輸入什麼,控制台就會顯示什麼
## DOM是什麼?
直白: browser提供了一個橋樑,讓我門用javascript去改變畫面
Huli的說法: HTML的階層關係,可以轉換成類似物件的形式,也可以參考如下圖片.

## `<script> `標籤放的位置很重要
因為只要碰到 JavaScript,停下來執行它,所以通常會把 `<script>`放在`</body>`前!因為網頁的渲染是從到上下的,為了確保 DOM tree 建完後, JavaScript 透夠 DOM 進行運作/溝通。
```htmlmixed=
<body>
...
<script src="./main.js"></script>
</body>
```
# 選到想要的元素
## getElementsByTagName()
注意's',因為tag不會只有一個.
> 這比較少用,這年頭哪一個標籤沒有用class的
## getElementsByClassName()
注意前面不用加上'.',因為已經要傳class了,前面加了'.'還是class.
```htmlmixed=
<div class="test">123</div>
<div class="test">123</div>
<div class="test">123</div>
```
```htmlmixed=
var allTest = document.getElementsByClassName('test');
allTest[1].style.color = 'red';
// 得 第二行為紅色 123
// 同時示範改變css的方法(但是比較不常在裡面直接加上style的方法)
```
## getElementById()
不用加上's',因為他是'ID'
> 用法同上
## querySelector('')
注意因為他是選擇器,所以要加上<em>前綴號</em>
只會回傳第一個匹配到的元素
```htmlmixed=
<div class = "test">
<p>123</p>
<p>321</p>
</div>
```
```htmlmixed=
var test = document.querySelector('.test > p')
test.style.color = 'blue';
// 得 藍色 123
```
## querySelectorAll('')
他會選到所有這個元素的節點.
## 動態增加或是移除class (ClassList作法)
> 說真的這個東西我怎麼一點印象都沒有,我有印象的是addClass
但那個是jquery的用法咧...
```htmlmixed=
<style>
.active{
background: red;
}
</style>
<div class = "test">
<p>123</p>
<p>321</p>
</div>
```
```javascript=
const element = document.querySelector('.test')
//注意底下的語法
element.classList.add('active')
element.classList.remove('active')
element.classList.toggle('active')
//再貼一次就又有了
element.classList.toggle('active')
```
## 改變內容
> 我只記得innerHTML, innerText一點印象都沒有
更不用提outerHTML, outerText...
```htmlmixed=
<div id="block">
yo
<a><b>hello</b></a>
</div>
<script>
const element = document.querySelector('#block a')
console.log(element.innerText)//標籤中的字印出來 // hello
console.log(element.innerHTML)//標籤中的東西印出來 // <b>hello~</b>
console.log(element.outerText)//標籤外的字印出來 // hello(少用)
console.log(element.outerHTML)//整段都給出來 // <a><b>hello~</b></a>(少用)
</script>
```
### 補充:
先來看圖

> 其實上面範例就看的出來說text/html的差異,我這邊再補充一點
```javascript=
const element = document.querySelector('#block > a')
element.innerHTML= '<h1>1234</h1>' // 把html內籤在程式碼裡面了
element.innerText= '<h1>1234</h1>' // 他最後會顯示出<h1>1234</h1>
```
## 新增node和刪除node
```htmlmixed=
//刪除元素
<div id="block">
yo
<a>hello~</a>
</div>
<script>
const element = document.querySelector('#block')
element.removeChild(document.querySelector('a'))
</script>
```
```htmlmixed=
//新增節點裡面的文字
<script>
const element = document.querySelector('#block')
const item = document.createTextNode('123')
element.appendChild(item)
</script>
```
```htmlmixed=
//新增節點
<script>
const element = document.querySelector('#block')
const item = document.createElement('div')
element.appendChild(item)
</script>
```
## callback function(回呼函式)
為執行 addEventListener 背後的概念。其目的就是為了不要讓其他事情被阻塞 (block),而延伸出來的方式。概念有點像是美食街的呼叫器,『好了後再叫我』!
```javascript=
document.getElementById('btn').addEventListener('click', function() {
//程式碼
})
ES6 寫法:
document.getElementById('btn').addEventListener(‘click', () => {
})
```
上面的 function: 等到 click 事件發生後,才呼叫裡面的函式 = callback funciton (同時他也是個匿名函式喔)
> 補充 直播課程4-2 @23:09 callback function
```javascript=
//以這種方式居多
function callMe(data){
console.log('done')
}
getData(callMe)
function getData(cb){
...發 request
...response回來
cb(response)
}
=================
const data = getData()
//這一種變數命名方式沒辦法讓getData()這個function
//執行需要過長時間的程式
```
### 綜合練習
1. 如何監測自己按下的鍵?
```javascript=
const element = document.querySelector('input')
element.addEventListener('keydown', function(e){
console.log(e.key)
})
```
<mark>2. 透過按鈕更改網頁的背景顏色</mark>
基本上這題是要能夠點擊按鈕後,切換更改網頁的顏色,一般按按鈕改顏色我就不多做示範了
在html裡面已經寫好這個style了
```htmlmixed=
<style>
.active{
background: red;
}
</style>
```
```javascript=
<script>
const btn = document.querySelector('.btn')
btn.addEventListener('click', ()=> { document.querySelector('body').classList.toggle('active')
})
</script>
```
> 重點是他透過classList增加了active的樣式,在透過toggle屬性做到開闔
讓我想到了jquery,看了下前面的示範好像沒啥了不起的.
## form表單中,停止驗證
> 其實這個以前也學過了,當時我還記得叫做change的作法,現在還真的忘的差不多了
底下範例示範的是當password1 不等於 password2 時的程式碼
```htmlmixed=
<form class="login-form">
<div>
username: <input name="username" />
</div>
<div>
password: <input name="password" type="password"/>
</div>
<div>
password again: <input name="password2" type="password"/>
</div>
<input type="submit">
</form>
<script>
const element = document.querySelector('.login-form')
element.addEventListener('click',function(e){
const input1= document.querySelector('input[name=password]')
const input2= document.querySelector('input[name=password2]')
if(input1.value !== input2.value) {
alert('密碼不同')
e.preventDefault()
}
})
</script>
```
## 先補獲在冒泡
```
addEventListener('click', callback , boolen)
```
> 其實這就是addEventListener最後的布林值,
如果要記住的話,捕獲從上到下比較合理,所以是true
而冒泡由下到上,所以是false(預設)
兩個重點:
<mark>1. 先捕獲,再冒泡</mark>
<mark>2. 當事件傳到 target 本身,沒有分捕獲跟冒泡</mark>

## 取消事件傳遞
> 那如果我要停止冒泡呢? 我犯了錯不想讓大家知道....
> e.stopPropgation
不過,在這邊依然有一個地方要特別注意。
這邊指的「事件傳遞被停止」,意思是說不會再把事件傳遞給「下一個節點」,但若是你在同一個節點上有不只一個 listener,還是會被執行到。
若是你想要讓其他同一層級的 listener 也不要被執行,可以改用e.stopImmediatePropagation();
## 取消預設行為
常常有人搞不清楚e.stopPropagation跟e.preventDefault的差別,前者我們剛剛已經說明了,就是取消事件繼續往下傳遞,而後者則是取消瀏覽器的預設行為。
這一篇真的值得一讀在讀
> [延伸閱讀_DOM 的事件傳遞機制:捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)
## 新手100%會犯的錯
練習1: 要呼叫自己屬性的按鈕
```htmlmixed=
通常要存數字,不會用文字,而是用以data開頭的屬性
<div class="outer">
<button class="btn" data-value="1">1</button>
<button class="btn" data-value="2">2</button>
<button class="btn" data-value="3">3</button>
<button class="btn" data-value="4">4</button>
<button class="btn" data-value="5">5</button>
</div>
```
方法一
```javascript=
//function是在點擊的那一刻觸發的,迴圈跑完之後i的值是5, 所以會變成5+1=6
const el = document.querySelectorAll('.btn')
for(var i=0; i < el.length ;i++) {// 其實改成let i=0 就可以解
el[i].addEventListener('click',function(e){
alert(i+1)
})
}
```
方法二
```javascript=
const el = document.querySelectorAll('.btn')
for(var i=0; i < el.length ;i++) {// 其實改成let i=0 就可以解
el[i].addEventListener('click',function(e){
alert(e.target.getAttribute('data-value'))
})
}
```
### 新增一個新按鈕,按了以後會新增btn
```htmlmixed=
<div class="outer">
<button class="add-btn">add</button>
<button class="btn" data-value="1">1</button>
<button class="btn" data-value="2">2</button>
</div>
```
```javascript=
const el = document.querySelectorAll('.btn')
for(var i=0; i < el.length ;i++) {// 其實改成let i=0 就可以解
el[i].addEventListener('click',function(e){
alert(e.target.getAttribute('data-value'))
})
}
```
```javascript=
//因為num會從3開始,前面1,2已經宣告過了
let num =3
document.querySelector('.add-btn').addEventListener('click',function(){
//宣告新按鈕
const btn = document.createElement('button')
//因為是空的button,所以要繼續往下設定屬性
btn.setAttribute('data-value', num)
//設定完屬性後,裡面應該要再加上內容
btn.innerText= num
//下一次的num就會變成這一次的num + 1
num = num + 1
//最後再把他加入到outer的底下
document.querySelector('.outer').appendChild(btn)
})
```
> 但是還是只有前面兩個有eventListener的監聽,後面新產生的button沒有
所以這時候就會提及下一個章節,事件代理
## 事件代理 event delegation
> 1. 常用,因為有效率,不用浪費那麼多資源處理差不多的事情
> 2. 處理動態新增也可以
```javascript=
document.querySelector('.outer').addEventListener('click',function(e){
//看是否有包含btn這個class
if(e.target.classList.contains('btn')){
alert(e.target.getAttribute('data-value'))
}
})
```
## 綜合練習
### 簡易密碼產生器
```htmlmixed=
<div class="section">
<div><label><input type="checkbox" name="en">英文</label></div>
<div><label><input type="checkbox" name="num">數字</label></div>
<div><label><input type="checkbox" name="sp">特殊符號</label></div>
<button class="btn">密碼產生</button>
<div class="result"></div>
</div>
```
```javascript=
const btn = document.querySelector('.btn')
const result = document.querySelector('.result')
btn.addEventListener('click',function (){
let availableChar = '';//組成的字串
if(document.querySelector('input[name="en"]').checked == true) {
availableChar += 'abcdefghijklmnopqrstuvwxyz' // 26
}
if(document.querySelector('input[name="num"]').checked == true) {
availableChar += '0123456789' // 10
}
if(document.querySelector('input[name="sp"]').checked == true) {
availableChar += '!@#%^&*+' // 8
}
let outcome = ''
//組一個十位數的密碼
for(let i=0; i < 10 ; i++) {
//取一個數字讓他可以在0~9之間,後面乘以長度是因為總共加起來有44個隨機數,
//若是乘以10,則只會出現英文字
let number = Math.floor(Math.random()*availableChar.length)
outcome += availableChar[number]
}
result.innerHTML=outcome
})
```
## hoisting
概念如下:
```javascript=
function test(){
console.log(a)
var a = 10
}
test()// undefined 不是not defined
```
> 可以看成這樣
```javascript=
function test(){
var a;
console.log(a)
a = 10
}
```
> 所以變成變數還沒有宣告
<mark>但是let/const 沒有變數提升的概念這是錯的</mark>
```javascript=
function test(){
console.log(a)
let a = 10
}
test() // not defined
```
隋堂考試
```javascript=
function test(){
let a = 1
function test2(){
console.log(a)
var a = 10
}
test2()
}
test()//undefined.
```
#### function執行有hoisting. 可以先執行在宣告function.
### 出自第十五週網站前後端開發基礎測試Q10:+1:
小明在執行程式的時候出現了一個錯誤:Uncaught TypeError: Cannot read property 'selfId' of undefined,但百思不得其解,不知道是哪裡出了問題,以下是出錯的「部分」程式碼:
```javascript=
const result = list.filter(item =>
item.parent.id === matches[0].parent.id &&
item.parent.name === matches[0].parent.name &&
item.selfId === homeData.selfId
).sort(
(a, b) => a.typeId - b.typeId
);
```
根據你的推理,會出現這個錯誤的原因是什麼?
點我看 Q10 解答
這一題是我覺得最好玩的一題,考驗你對 JS 錯誤訊息的理解。
你可能會認為說:「不對吧,你很多資訊都沒有給齊,這題怎麼解?狀況很多吧!」
讓我來幫你解惑,首先你可能會答說:「homeData 上面可能沒宣告」,但如果是這種狀況,錯誤訊息就會是 ReferenceError: homeData is not defined,所以這種狀況可以排除,代表 homeData 一定有宣告。
同理,matches 也一定有宣告,不然錯誤訊息會不一樣。
再來,錯誤訊息告訴我們說:Cannot read property 'selfId' of undefined,代表我們試圖對一個 undefined 讀取 selfId 這個屬性。
出現 selfId 的是這行:item.selfId === homeData.selfId。
如果 item 是 undefined,前面 item.parent.id 時就會出錯,所以 item 是沒有問題的。
因此,會出現這個錯誤訊息的原因是 homeData 是 undefined。