JS筆記
===
###### tags: `web` `frontend`
## 變數
- JS 的基本型別(Primitives)有 String(文字)、 Number(數字)、 Boolean(true/false)、 undefined、 null
- JS 中變數為寬鬆型別,不用事先宣告型別(type)
- 區分大小寫、不可用保留字、不可用數字開頭
在宣告變數的同時也可以給定初始值,給值的同時也決定了變數的型別(type)
## 區塊和活動範圍
### C語系
1. ``block``就是區塊,是程式中的一塊獨立段落。 C 式語系中,由 ``{`` ``}`` 包起的內容就屬於一個 ``block`` (區塊),而在區塊之中還可以有小區塊。層層區塊組成巢狀結構。
2. 伴隨著區塊而來的還有稱為 ``scope`` 的變數活動範圍、或稱作用域的觀念。
程式語言用變數活動範圍劃分各個變數的可用範圍,讓符號名稱可以在不同的活動範圍中繫結不同的變數,也才有現在的區域變數常識。
3. 在C語言中,一個區塊(block)就等於一個活動範圍(scope)
```c=
int main()
{
int i = 0;
{
int i = 1;
printf("inner scope: %d\n", i);
}
printf("main scope: %d\n", i);
return 0;
}
```
在 main 區塊中,又寫了一對 ``{`` ``}`` 劃出一個小區塊,亦即一個新的活動範圍。所以我可以在這個小區塊中再宣告一次 i 。
符號 i 在這一大一小兩區塊中,分別繫結兩個不同活動範圍的變數。所以小區塊中令 i 為 1 的敘述,並不會影響到大區塊的 i 值。一前一後的兩行輸出指令,也就分別輸出 1 和 0 兩個結果。
如果你把這個 C 語言範例中的第5行和第8行的角括號拿掉的話,編譯器會直接告訴你重複宣告變數。
### JS
#### var
但是同樣的想法,用在 JavaScript 就錯了。先看看下面的 JavaScript 範例:
```javascript=
{
var i = 0;
{
var i = 1;
console.log("inner block: ", i);
}
console.log("host block: %d", i);
}
```
前後兩行都顯示 1 ,也就是在小區塊中第二次宣告 i 並賦值為 1 的敘述,其實作用在第一次宣告的 i 身上。
這表示在 JavaScript 中,**區塊並不等於活動範圍**。一大一小兩區塊的 i 都繫結到同一個變數了。
在 ES6 之前的規範中,對於 ``scope`` 的定義只有兩種
1. 全域活動範圍(global scope)
2. 函數活動範圍(function scope)
你每定義一個函數,就會建立一個屬於這個函數的活動範圍;不在函數內的資源就屬於全域活動範圍。
因為**沒有採用區塊即活動範圍的定義**。所以像 C 語言那樣的區塊用法,在 JavaScript 中就是錯的。
ES6 以前,也只有 var 一種變數宣告方式。它的用途和函數活動範圍有關。
- 在函數內以 var 宣告的變數,僅限函數活動範圍內可用,外部看不到。
- 而沒有用 var 或是在函數外宣告的變數,就屬於全域範圍了。
var 是看函數,而不是區塊。
#### let
let 和 var 不同之處在於它帶來了**以區塊為活動範圍**的定義。
在 ES6 中,你可以在 for 區塊、if 區塊、或者是不帶任何控制目的純區塊中,使用 let 宣告以區塊為活動範圍的變數。
因此若將前述JavaScript 的範例程式碼中以 var 宣告的變數全改為以 let 宣告,就會跑出正確的結果。
- let 禁止在同一活動範圍中再次宣告相同名稱的變數。
- var 會無視第二次宣告,只管指派變數值。但 let 視為重複宣告的語法錯誤。
- let 禁止在宣告變數之前就使用它。
- 在全域範圍以 let 宣告的變數,不會成為全域個體(global object)的屬性。但以 var 宣告的變數同時也會是全域個體的屬性。因此 let 變數是真正的區域變數,你用 module 或其他方式載入的程式碼看不到那些 let 變數。註: 在瀏覽器中運行的 JavaScript 之全域個體一律是 window 。
let 是看區塊,而不是函數。
#### const
凡是用 const 定義的符號,其繫結的內容僅能在定義時設定初值,之後不允許再改變,這就是常數了。
試圖改變 const 常數的敘述,都是語法錯誤。除此之外,const 的語法限制和 let 相同,不允許重複宣告、不允許宣告前使用。
const是看區塊,而不是函數
const 常數還有一點要注意,它可以在定義時計算初值。
所以定義時的初值部份不限定為字面內容,而可以使用變數或函數等運算敘述。
若初值部份用了變數或運算敘述, JavaScript 會將計算結果作為初值。
即使你之後改變了那個變數,也不會影響 const 常數的內容。
```javascript=
const MAX1 = 1;
let i = 100;
const MAX100 = i + 1; // 計算 i+1 之現值後存入,故為 101
i += 20;
console.log(MAX100); // 仍為 101
```
:::warning
若以 const 宣告物件或陣列,還是有辦法更改裡面的值,
像是
```
const myObj = {
url: "http://123.com"
}
myObj.url = "changed";
console.log(myObj.url); // changed
```
要解決這個問題可以用
```
Object.freeze(myObj);
```
:::
### 例子
```javascript=
function test(){
var outside_var = "outside_var";
let outside_let = "outside_let";
for(let i=0; i<1; i++){
var inner_var = "inner_var";
let inner_let = "inner_let";
// outside_var
console.log(outside_var);
// outside_let
console.log(outside_let);
}
// inner_var
console.log(inner_var);
// inner_let is not defined
console.log(inner_let);
}
test();
```
## 函數
### 內建函數
```
parseInt()、parseFloat():轉換指定型態
isNaN():判斷是否非數字
isFinite():判斷是否是無窮數
.toFixed(3):取到小數第3位
```
### Math
輸出x的m次方
```javascript=
Math.pow(x, m);
```
產生min~max間的隨機亂數
```javascript=
/*Math.floor:去小數
* 注意min要加在取整數之後
*/
var x = Math.floor(Math.random()*(max-min+1))+min;
```
### Date
new Date (year, month, date, hours, minutes, seconds, ms)
```javascript=
var today = new Date();
// Jan 31 00:00:00 1978
// 月份為0~11
var birthDay = new Date(1978, 0, 31);
// 取得年份
var day = today.getFullYear();
// 取得月份(0~11)
var day = today.getMonth();
// 取得日期
var day = today.getDate();
// 取得星期
var day = today.getDay();
// 可取得詳細時間
// Sun Apr 23 2017 01:29:23 GMT+0800 (台北標準時間)
Date();
// 可取得1970年到現在的毫秒數
Date.now();
// 轉成 ISO8601
var dt = new Date();
var tmp = dt.toISOString();
// ISO8601轉毫秒
Date.parse("2017-12-12T18:00:00.000Z");
// ISO8601轉當地時間
var tmpp = new Date(tmp).toLocaleString();
```
### URI 編碼/解碼
統一資源標識符(Uniform Resource Identifier,或URI)
是一個用於標識某一網際網路資源名稱的字元串,
而URL (定義位置) 和 URN (定義身份) 則是URI的子集。
在處理網頁(址)資料,常會有遇到中文字或是空白、標點符號等問題(英文、數字通常不會有問題),
此時就可以使用 URI 編碼函數(通常標點轉為十六進位ASCII、中文轉成十六進位統一字元碼),
如需轉回則使用解碼函數。
encodeURIComponent()不編碼符號包括: ~ ! * ( ) '
適合參數編碼,應用範圍廣
```
encodeURIComponent():轉碼
decodeURIComponent():解碼
```
### alert / confirm / prompt
```
//可吃換行\n
alert('text') / confirm('text')
//若取消則回傳 null
prompt('text','使用者未輸入的預設值')
```
三者皆會在瀏覽器顯示對話框 但用處各自不同
其中 confirm('text') 和 alert('text') 類似
都會把參數內的字串顯示在對話視窗中
但confirm()有確定和取消 會回傳 ``true`` 或 ``false``
```javascript=
if(confirm('你想吃飯嗎')){
alert('客人,請進');
console.log('客人,請進');
}else{
alert('掰');
console.log('掰');
}
```
prompt()則是除了顯示文字外 還可以讓使用者輸入數值
```javascript=
if(prompt('請輸入年齡')>=20){
console.log('你成年可以投票囉~~');
}else{
console.log('孩子再等幾年吧!');
}
```
### 自訂函數
#### 定義命名函數
```javascript=
function sum(a,b){
return a+b;
}
```
#### 定義匿名函數
```javascript=
/*函數可以當作變數傳遞*/
var sum=function(a,b){
return a+b;
};
```
### 中斷函數
迴圈可以用 break、continue 來中斷迴圈 ; 而函數的 return 除了用來回傳值,也可用來中斷函數。
作法為 return 一個 value 或是乾脆回傳空值
```javascript=
function test(str = 'working'){
if(str == 'stop'){
return
}
console.log(str);
}
test(); // working
test('stop'); // show nothing
```
## 條件運算子
``if`` ``else``可以簡寫
```javascript=
// <p>The answer is <span id="ans"></span></p>
//
var ans = document.getElementById("ans");
var isTrue = (1+1 == 2);
var isFalse = (1+1 != 2);
ans.innerHTML = (isTrue) ? "Yes" : "No";
// 也可以再串下去
ans.innerHTML = (isFalse) ? "Yes" : (isTrue) ? "No => Yes" : "No => No";
```
## Hoisting
### 函數
一般而言 JavaScript 有 內建函數 和 自定義函數
在 JS 中 函數在被執行之前會被解析(hoisted)
因此它可以在任意的地方都是有宣告的 即便比這個函式還早呼叫
```javascript=
console.log(sum(1,2));
function sum(a,b){
return a+b;
}
console.log(sum(1,2));
```
### 變數
JavaScript 是 function scope
無論你在函數內哪裡宣告變數 var 都會提升到最前面宣告
稱為變數的拉升(Variable Hoisting),
建議把變數的宣告放在函數的最頂端 否則可能導致混亂的情況。
但是使用 let 或 const 須注意,並不會自動拉升,所以在宣告前使用的話會顯示 **is not define**。
```javascript=
function test(){
/*儘管還沒定義但會回傳 undefinded*/
console.log(a);
var a=1;
}
test();
```
上面代碼實際執行狀況是
```javascript=
function test(){
/*程式會將function中全部需要宣告的Local Variable提升到function的第一行來執行*/
var a;
console.log(a);
a=1;
}
test();
```
更精確的說 在我們定義變數的過程中
可以分成宣告(declaration)和給值(initialization)的兩個過程
只有declaration的內容會在逐行執行程式前先被執行並儲存在記憶體(hoisted)
給值的內容則是在hoisted後 逐行執行程式時 才會被執行到
所以程式一開始執行的時候 就已經把var a的宣告存在記憶體中了
但是還沒把值指定進去a這個變數當中 這使得a得到了undefined的結果
## Scope Chain
用 var 所宣告的變數 作用範圍是在當時所在環境(函數內)
而不使用 var 直接指定值而建立的變數 則是全域物件(window)上的一個屬性
也就全域範圍
在 JS 中有稱作 scope chain(範圍鏈)的特性
JS在查找變數時 會循著範圍鏈(Scope Chain)一層一層往外找
若函數內找不到 則往外找
注意:內層函式都可以存取外部函式的變數;但外部不能存取內部
## 自調用函數
自調用函數(立即函數)是一種
不用額外呼叫可自己立刻執行
方便建構自己的生存域
```javascript=
(function(name){
var cat=name;
document.write(cat);
})('momo');
```
## Closure
閉包(Closure)是擁有閒置變數(Free variable)的物件
建立函數不等於建立閉包
如果函數的閒置變數與當時語彙環境綁定
該函數才稱為閉包
**閒置變數**是指對於函式而言,既非區域變數也非參數的變數
```javascript=
function makeFunc(){
var name = "Mozilla";
function displayName(){
alert(name);
}
return displayName;
}
/*closure,理論上 name 在函數執行完就消失
*但由於內部函數 displayName 參考到 name 變數
*所以當 displayName 給定給 myFunc 全域變數時
*生存域突破成全域 記憶了創建函數時的環境變數參考
*所以 name 活下來了
*/
var myFunc = makeFunc();
myFunc();
```
```javascript=
function doSome() {
var x = 10;
function f(y) {
return x + y;
}
return f;
}
var foo = doSome();
console.log(foo(20)); /*30*/
console.log(foo(30)); /*40*/
```
上面doSome的例子中,f建立了一個閉包,如果你單看:
```
function f(y) {
return x + y;
}
```
看來起x似乎沒有定義。實際上,x是從外部函式捕捉而來。
閉包是個捕捉了外部函式變數(或使之繼續存活)的函式。
在上例中,函式f建立了閉包,因為它將變數x關入(close)自己的範圍。
如果形式閉包的函式物件持續存活,被關閉的變數x也會繼續存活。
就像是延續了變數x的生命週期。
由於doSome傳回了函式物件並指定給foo,就doSome而言已經執行完畢。
單看x的話,理應x已結束其生命週期,但由於doSome中建立了閉包並傳回,x被關閉在閉包中,所以x的生命週期就與閉包的生命週期相同了。
如上例所示,呼叫foo(20)結果就是10+20(因為被關閉的x值是10),呼叫foo(30)結果就是10+30。
注意!閉包關閉的是變數,而不是變數所參考的值。
```javascript=
function doOther() {
var x = 10;
function f(y) {
return x + y;
}
x = 100;
return f;
}
var foo = doOther();
console.log(foo(20)); /*120*/
console.log(foo(30)); /*130*/
```
建立閉包時,綁定了x變數,而不是數值10(x變數的值),
也因此doOther之後改變了x變數的值,而後傳回閉包給foo參數後,
範例顯示的結果分別是100+20與100+30。
你可能會有疑問的是,如果閉包關閉了某個變數,使得該變數的生命週期得以延長,那麼這個會怎麼樣?
```javascript=
function doOther() {
var x = 10;
function f() {
x--;
return x;
}
return f;
}
var f1 = doOther();
var f2 = doOther();
console.log(f1()); /*9*/
console.log(f2()); /*9*/
```
像這類的例子,其實結果是很一致的,關閉的是建立閉包時外部範圍下的變數。
以上例來說,第一次呼叫doOther時,建立了x變數,指定值給x變數,而後建立閉包將之關閉。
第二次呼叫doOther時,建立了x變數,指定值給x變數,而後建立閉包將之關閉。
所以f1與f2關閉的根本是不同作用範圍的x變數(也就是該次呼叫doOther時所建立的x變數)。
所以上例中,呼叫f2之後顯示的值仍是9
下面這個也是個例子
```javascript=
function doSome(x) {
return function(a) {
return x + a;
};
}
var f1 = doSome(100);
var f2 = doSome(200);
console.log(f1(10)); /*110*/
console.log(f2(10)); /*210*/
```
### Closure常見應用
1. 實現 private
2. 嵌套 callback 函數非同步處理(Ex. 事件處理)
3. 非同步處理(Ex. 事件處理)
#### Closure 模擬 private
使用閉包來定義公共函數,且其可以訪問私有函數和變數。
這個方式也稱為模組模式(module pattern)
```javascript=
/*自調用函數,共用生存域*/
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
```
```javascript=
/*每次產生都創建一個獨立記憶環境*/
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
```
#### 嵌套 callback 函數
```html=
<a href="#" id="size-10">10</a>
<a href="#" id="size-20">20</a>
<a href="#" id="size-30">30</a>
```
```javascript=
function makeSizer(size){
return function(){
document.body.style.fontSize=size+'px';
};
}
/*每個函數的創建都會有自己獨立的生存環境*/
var size10 = makeSizer(10);
var size20 = makeSizer(20);
var size30 = makeSizer(30);
document.getElementById('size-10').onclick = size10;
document.getElementById('size-20').onclick = size20;
document.getElementById('size-30').onclick = size30;
```
#### 處理非同步問題
```javascript=
for(var i = 0; i < 5; i++){
setTimeout(function () {
console.log(i);
},1000);
}
/* 5
* 5
* 5
* 5
* 5
*/
```
上方全部都顯示 5 是因為 JS 在執行 for 時,會把 setTimeout 放進 Queue 中,所以迴圈跑完才會執行 setTimeout ,但此時的 i 是全域變數,也就是跑完迴圈後的 i = 5,所以 console.log(i) 才會顯示 5。
[關於 JS 中 Stack、Queue、WebApis 更詳細的說明](https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html)
可以使用閉包解決這個問題,讓 i 的值與當時的環境綁定:
```javascript=
function printLog(i){
return function(){
console.log(i);
}
}
for(var i = 0; i < 5; i++){
setTimeout(printLog(i),1000);
}
/*
* 0
* 1
* 2
* 3
* 4
*/
```
其實 ES6 之後有更簡單的方法解決這個問題:
改成 let 即可,這也像閉包的概念,因為迴圈執行完後,i 並不是全域變數,所以會綁定當時生成的環境。
```javascript=
for(let i = 0; i < 5; i++){
setTimeout(function () {
console.log(i);
},1000);
}
/*
* 0
* 1
* 2
* 3
* 4
*/
```
## 箭頭函數
```javascript=
// 寫法
(參數1, 參數2, …, 參數N) => { statements }
(param1, param2, …, paramN) => expression
// expression 等於 { return expression; },
// 若函數內只有 return 值而已,則可省略大括號和 return
// 只有一個參數時,括號才能不加
(singleParam) => { statements }
singleParam => { statements }
// 若無參數,就一定要加括號:
() => { statements }
```
- param(參數):
多個參數以()包住,如不需要參數則以 () 表示,
如果只有一個參數可以省略括弧 (如 foo => 1)。
- statements or expression:
當有多個陳述 (statement) 需用 { } 框住,只有一個陳述則不需要;
其中表達式 (Expression) 最後也會是箭頭函數的返回值。
```javascript=
var sum = function(x, y){
return x + y;
};
console.log(sum(1,2));
```
可以寫成
```javascript=
/* let empty = (參數) => {回傳值}; */
var sum = (x, y) => x + y;
console.log(sum(1,2));
```
## JavaScript 函數式程式設計
### forEach
```javascript=
const numbers = [1,2,3,4];
numbers.forEach(function(item, index, array){
console.log(item);
});
// 1
// 2
// 3
// 4
```
### map 迭代函數
與 **forEach** 相似,但會有回傳值
```javascript=
const numbers = [1,2,3,4];
const newNumbers = numbers.map(function(item, index, array){
return item * 2;
});
console.log("The doubled numbers are", newNumbers);
/* [2,4,6,8] */
```
可是 **map** 不適合拿來過濾
```javascript=
const numbers = [1,2,3,4];
const newNumbers = numbers.map(function(item, index, array){
if(item > 1){
return item * 2;
}
});
console.log("The doubled numbers are", newNumbers);
/* [undefined, 4, 6, 8] */
```
#### 回傳物件
```javascript=
const numbers = [1,2,3,4];
const allPeople = numbers.map(function(item, index, array){
return {
money: item * 100
}
});
console.log(allPeople);
/* [undefined, 4, 6, 8] */
```
### filter 過濾函數
回傳所有結果為 true 的值
```javascript=
// 過濾出所有奇數
const numbers = [1,2,3,4];
const newNumbers = numbers.filter(function(item, index, array){
return(item % 2 !== 0);
})
console.log(newNumbers); // [1, 3]
```
結合 **map**
```javascript=
const numbers = [1,2,3,4];
const newNumbers = numbers.filter(function(item, index, array){
return(item % 2 !== 0);
})
.map(function(item){
return item * 2;
});
console.log("The doubled numbers are",newNumbers);
/* [2,6] */
```
### find
與 **filter** 原理相同,但差別在 **find** 只會回傳第一個為 true 的結果。
```javascript=
// 過濾出所有奇數
const numbers = [1,2,3,4];
const newNumbers = numbers.find(function(item, index, array){
return(item % 2 !== 0);
})
console.log(newNumbers); // 1
```
### every
檢查所有判斷結果,**全為** true 則 return true,有一個 false 則 return false。
```javascript=
// 檢查是否所有數字都為偶數
const numbers = [1,2,3,4];
const allNumbersAreEven = numbers.every(function(item, index, array){
return(item % 2 === 0);
})
console.log(allNumbersAreEven); // false
```
### some
檢查所有判斷結果,**只要有一個** true 則 return true。
```javascript=
// 檢查是否所有數字都為偶數
const numbers = [1,2,3,4];
const someNumbersAreEven = numbers.some(function(item, index, array){
return(item % 2 === 0);
})
console.log(someNumbersAreEven); // false
```
### reduce 累計函數
一樣對陣列進行遍歷操作,但會將每回合 return 的值,傳入下一回合,所以可以用來加總。
```javascript=
var numbers = [1,2,3,4];
// pre 為前一個 reduce return 的值,
// 但第一回合執行時並沒有前一個回傳值,
// 所以在 , 後面加上初始 pre 值
// 而 item 則為當前取出的陣列值
var totalNumber = numbers.reduce(function(pre, item){
return pre + item;
}, 0);
console.log("The total number is " + totalNumber);
/* 10 */
```
## 陣列
### 宣告
```javascript=
var myArray = [];
var myOther = new Array();
myArray[0] = "apple";
myOther = ["dog", "cat", "bird"];
// 宣告同時給定初始值
var arr4 = new Array(1.5, '2009', true);
var arr5 = [1.5, '2009', true];
```
### 查看長度
```javascript=
console.log(myArray.length);
```
### 添加新元素到尾端
```javascript=
myArray.push("banana");
```
### 添加新元素到前端
```javascript=
myArray.unshift("banana");
```
### 刪除尾端元素
```javascript=
myArray.pop();
```
### 反轉陣列
```javascript=
var data = [1, 2, 3, 4, 5];
data.reverse();
console.log(data); // 5 4 3 2 1
```
### 添加/刪除指定元素
```javascript=
/*用法:第一參數為指定位置
* 第二參數為刪除幾個元素
* 第三參數後可以新增元素
*/
/*ex:刪除myArray[1]起(包含)的3個元素*/
myArray.splice(1, 3);
/*ex:新增3個元素到myArray[2]之前*/
myArray.splice(2, 0, "a", "b", "c");
```
### 取出指定範圍的元素
```javascript=
// 第一個參數: 起始 index(包含)
// 第二個參數: 結束 index(不包含)
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var tmp = arr.slice(5,8);
// 5,6,7
```
### 查詢陣列中某元素位置(是否存在)
```javascript=
/*第一參數為要查找的元素值
* 第二參數為指定從哪裡開始找(可不加)
* 找到則回傳元素在陣列中的位置(第一筆)
* 若找不到則回傳-1
*/
myArray.indexOf("apple", fromIndex);
```
兩者結合
```javascript=
myArray.splice(myArray.indexOf("apple"), 1);
```
### 陣列合併
```javascript=
var a = ['a', 'b', 'c'];
var b = ['d', 'e', 'f'];
var c = a.concat(b);
console.log(c) // a~f
```
### 陣列轉字串
```javascript=
var myArr = [1, 2, 3];
var myStr = myArr.join(""); // 123
// 自訂義中間符
var myStr = myArr.join("Y") //1Y2Y3
```
### ES6 延展
依序 return 陣列每個值
```javascript=
var a = [1, 2, 3];
console.log(...a); // 1 2 3
```
故可以使用此種寫法改寫上方的陣列合併
```javascript=
var a = ['a', 'b', 'c'];
var b = ['d', 'e', 'f'];
var c = [...a, ...b];
console.log(c) // a~f
```
#### 深拷貝
和物件一樣,Array 也是傳參考,如果想要實現陣列的深拷貝,可以使用 ES6 延展快速實現,因為它和手動取值一樣,依序取出陣列的值。
```javascript=
var a = [1, 2, 3];
var b = [...a];
b.push(4);
// a: 1~3
// b: 1~4
```
### 類陣列
結構與陣列幾乎一樣,但是能夠使用 ``__proto__`` 中的函數較少。
```javascript=
var doms = document.querySelectorAll("li");
console.log(doms);
```
![](https://i.imgur.com/69O85lF.png)
正常 Array
```javascript=
var test = [1, 2, 3];
console.log(test);
```
![](https://i.imgur.com/POJky5e.png)
套用延展特性,可以使類陣列轉為正常陣列
```javascript=
var doms = document.querySelectorAll("li");
var newDoms = [...doms];
console.log(newDoms);
```
![](https://i.imgur.com/1H9mvv0.png)
#### 實際應用
:::info
JS 函數中,若傳入未定義的參數,實際上還是會透過 ``arguments`` 變數存起來(有定義的參數也會存進)。
:::
```javascript=
function addCash(){
var arg = arguments;
console.log(arg);
}
addCash(100, 200, 300);
// [100, 200, 300]
```
![](https://i.imgur.com/PP9nw9D.png)
而聰明的你一定會想到,沒錯,這個 arguments 也是類陣列,沒有辦法使用太多的方法,像是加總(reduce),那解決方法也是透過上方的延展來轉換。
```javascript=
function updateEasyCard() {
let arg = [...arguments];
let sum = arg.reduce(function (accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log('我有 ' + sum + ' 元');
}
updateEasyCard(10, 50, 100, 50, 5, 1, 1, 1, 500); // 我有 718 元
```
### 其餘參數
:::info
此處介紹的**其餘參數**和上方 ``arguments`` 概念類似,但 ``arguments`` 是將所有傳入函數的參數存起來,不論那些參數有沒有定義,而**其餘參數**則是存放沒有定義的參數而已。
:::
此範例結果乍看之下和 ``arguments`` 差不多,
```javascript=
function moreMoney(...money) {
console.log(money);
}
moreMoney(100, 100, 100);
// [100, 100, 100]
```
但是改成這樣,就發現 Bob 是傳入有定義的參數 name,而其餘未定義的參數則被放入 money 中。
```javascript=
function moreMoney(name, ...money) {
console.log(name, money);
}
moreMoney("Bob", 100, 100, 100);
// Bob, [100, 100, 100]
```
### 解構賦值
#### Case1. 取陣列值
這樣的場景有沒有似曾相似...
```javascript=
var family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
var ming = family[0];
var jay = family[1];
var auntie = family[2];
...
```
現在不用再這麼麻煩了,而且左邊變數數量小於等於右數陣列長度就好。
```javascript=
var family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
var [ming, jay, auntie] = family;
// 小明, 杰倫, 阿姨
```
如果想要跳過某一個值,可以改成這樣
```javascript=
var family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
// 想要跳過的變數直接留空
// 此處即不想設變數接收阿姨的值
var [ming, jay, ,mom] = family;
// 小明, 杰倫, 老媽
```
#### Case2. 兩數交換
蛤,上面的場景你沒見過?
那這個呢...
```javascript=
var a = 123;
var b = 456;
var tmp = a;
a = b;
b = tmp;
```
一樣,可以直接改成簡潔有力的寫法
```javascript=
var a = 123;
var b = 456;
[a, b] = [b, a];
// a: 456
// b: 123
```
#### Q&A
##### 1.
```javascript=
var [ming = '小明', jay = '杰倫'] = ['阿明'];
console.log(ming, jay); // 請問答案是什麼?
// ---------------------------
// Ans:
// 第一個會被覆蓋,第二個會用預設
// ming: "阿明"
// jay: "杰倫"
```
## 字串
### 把字串變成陣列
```javascript=
/*split中放入字串切割分組的關鍵詞 ex: ","
*不放則是切割每個字元
*/
var list = "123abc".split("");
// 像是文章有好幾行,也可以用換行來切割: .split("\n")
/*調用每個元素*/
for(let i = 0; i < list.length; i++){
console.log(list[i]);
}
```
### 替換字元
```javascript=
var string = "abc";
var test = string.replace("c", "zzz");
console.log(test); // abzzz
```
左邊搜尋文字可以放正則式,常用寫法為 ``/搜尋文字/g``
1. 參數 g 為搜尋所有相符字串,若沒有加的話,則是替換第一個搜尋到的結果。
2. 參數 i 為檢查時忽略大小寫
```javascript=
var string = "a c b c d";
var test = string.replace(/c/g, "QQ");
console.log(test); // a QQ b QQ d
```
```javascript=
var string = "acbcd";
var test = string.replace(/C/g, "QQ");
console.log(test); // acbcd
test = string.replace(/C/ig, "QQ");
console.log(test); // aQQbQQd
```
### 取字串中的限定範圍
```javascript=
var str = "hello";
// 第一個參數是起始index,若是負數則從尾數來,
// ex: -1則是最後, -2是倒數第二
// 第二個參數是取多長,不加則是取完剩下字元
// he
console.log(str.substr(0, 2));
```
### 解構賦值
可以到[陣列>解構賦值](#解構賦值)看更多說明
#### Case1. 取出每個字串
```javascript=
var str = "abc";
var [q, w, e] = str;
// a, b, c
```
## 物件
### 格式
```javascript=
var myObject = {
屬性: 設定值,
name: 'Ryan'
};
console.log(myObject.name);
myObject.age = 18;
console.log(myObject.age);
```
也可以這樣寫,當一筆資料有 key 時,不想用陣列儲存,可以用這種方式:
```javascript=
var students = {};
students['Bob'] = {age: 18, sex: "male"};
students['Alice'] = {age: 17, sex: "female"};
console.log(students);
// {
// Bob: {age: 18, sex: "male"},
// Alice: {age: 17, sex: "female"}
// }
```
### 物件 & 陣列 & 函式
```javascript=
var myObject = {
teacher: 'Ken',
student: ['A', 'B', 'C'],
sayHi: function(grade){
alert('Hi');
}
};
// 要call函數要加()
// 沒有的話會回傳sayHi的設定值
myObject.sayHi();
```
### 建構函式(函式 & 物件)
```javascript=
function circle(r){
this.r = r;
this.area = Math.PI * r * r;
this.peri = 2 * Math.PI * r;
this.print = function(){
alert("radius = " + r);
};
}
var ball = new circle(10);
console.log(ball.r);
```
### call by reference(sharing)
除了基本型別(Number、String、Boolean、null、undefined)是 call by value 之外,
Array、Object 為 call by reference(sharing)。
```javascript=
// call by value
// a 和 b 的值存放的記憶體位置不一樣
// 所以改動 b 不會影響到 a
var a = 123;
var b = a;
b = 456;
console.log(a); // 123
console.log(b); // 456
```
```javascript=
// call by reference(sharaing)
// b = a,實際上 b 是指到 a 的記憶體位置
// 所以 a 和 b 指向的是同一個地址,更改 b 的值也會影響到 a 的值
var a = {value: 100};
var b = a;
b.value = 500;
console.log(a.value); // 500
console.log(b.value); // 500
```
```javascript=
// 函數傳遞的參數為物件時也是一樣
function addMoney(tmp){
tmp.money += 100;
}
var cash = {money: 100};
addMoney(cash);
console.log(cash.money); // 200
```
#### 深拷貝
那該怎麼單純的取物件的值呢?
1. 透過此種方式重新賦值
```javascript=
// b={xxx},將會使 b 存放的值,指向一個新的記憶體位置
var a = {value: 123}
var b = a;
b = {value: 456};
console.log(a.value); // 123
console.log(b.value); // 456
```
同樣的範例還有
```javascript=
var a = {name: 'Ted', age: 18};
var b = {name: a.name, age: a.age};
b.name = "changed";
console.log(a.name); // Ted
console.log(b.name); // changed
```
2. Object.assign
懶得像上方手動複製,可以使用此方法
```javascript=
var a = {name: 'Ted'};
var b = Object.assign({}, a);
b.name = "Ray";
// a.name: Ted
// b.name: Ray
```
:::danger
但是沒這麼簡單,以上方法只能深拷貝一層,也就是可能會發生以下狀況
:::
```javascript=
var a = {
name: 'Ted',
language: {
chinese: 'nice',
english: 'nice'
}
};
var b = Object.assign({}, a);
b.name = "Bob";
b.language.chinese = 'bad';
// a.name: Ted
// a.language.chinese: bad
```
注意到了嗎
b 改變 name 並不會影響 a 的 name,但是改變再深一層的物件的值,就會影響到原本 a 的 language.chinese 了!
當初遇到這個問題爬了一下文,才知道原來這是很熱門的問題,也是面試常常會被問到的XD
那該怎麼解決呢?
1. 原生 js 寫個 function,裡面用遞迴或 for 不斷取值,真正實現深拷貝。
```javascript=
// 網路上自己找QAQ
```
2. 轉換成 JSON
```javascript=
var a = {
name: 'Ted',
language: {
chinese: 'nice',
english: 'nice'
}
};
var b = JSON.parse(JSON.stringify(a));
b.name = "Bob";
b.language.chinese = 'bad';
// a.name: Ted
// a.language.chinese: nice
```
3. 使用第三方套件(Jquery、lodash)
- lodash
```javascript
var test = {
childrenKey: 'value',
childrenObject: {
keyA: 'value a',
keyB: 'value b'
}
}
var cloneA = cloneDeep(test);
```
- JQuery
深淺拷貝對應的參數是可選的,為true或false。默認情況是false(淺拷貝),並且false是不能夠顯示的寫出來的。如果想寫,只能寫true(深拷貝)。
```javascript
var a = {};
var b = {
name: 'Ted',
language: {
chinese: 'nice',
english: 'nice'
}
};
// 深拷貝
$.extend(true, a, b);
a.language.chinese = 'bad';
// b.language.chinese: nice
// 至於沒有加上參數就是淺拷貝
$.extend(a, b);
// 也等於
a = Object.assign({}, b);
```
### 解構賦值
#### Case1. 取出物件某值
```javascript=
var family = {
ming: '小明',
jay: '杰倫',
};
var { ming } = family;
console.log(ming); // 小明
```
如果想要將取出的值放到不同名稱的變數上
```javascript=
var family = {
ming: '小明',
jay: '杰倫',
};
var { ming: newMing } = family;
console.log(newMing);
```
#### Q&A
##### 1.
```javascript=
var { ming: newMing, family: [, mom] } = {
ming: '小明',
family: ['阿姨', '老媽', '老爸']
}
console.log(newMing, mom); // 請問答案是什麼?
// ----------------------------
// Ans:
// newMing: 小明
// mom: 老媽
```
##### 2.
```javascript=
var { family: ming = '小明' } = {};
console.log(ming); // 請問答案是什麼?
// -----------
// Ans:
// ming: '小明'
```
#### 縮寫
若屬性與值的名稱一樣,則可省略後者
```javascript=
var a = 123;
var b = {
a: a
};
// 可縮寫為
var b = {
a
}
```
函數也可以縮寫
```javascript=
var a = {
sayHi: function(){
console.log("Hi");
}
}
// 可縮寫為
var a = {
sayHi () {
console.log("Hi");
}
}
```
#### 搭配縮寫與延展的深拷貝
```javascript=
var a = {
age: 18
}
var b = {
...a
}
// b
// { age: 18 }
b.age = 20;
// a.age: 18
// b.age: 20
```
## For 迭代取值
### ``in`` 取 ``key``
```javascript=
var obj = {
name: "HI",
age: 20
};
for(var tmp in obj){
console.log("key: " + tmp + " value: " + obj[tmp]);
}
// "key: name value: HI"
// "key: age value: 20"
// 物件中的物件
var obj2 = {
class1: {
num: 30
},
class2: {
num: 35
}
}
for(var tmp in obj2){
console.log("key: " + tmp + " value: " + obj[tmp].num);
}
```
```javascript=
var arr = [1,2,3];
for(var tmp in arr){
console.log(tmp);
}
// 0
// 1
// 2
```
### ``of`` 取 ``value``
```javascript=
var arr = [1,2,3];
for(var tmp of arr){
console.log(tmp);
}
// 1
// 2
// 3
```
### forEach
如果是陣列的話,也可以用 forEach 個別操作元素
```javascript=
var test = ['a', 'b', 'c'];
test.forEach(function(value, index){
console.log("索引:" + index + "變數值" + value);
})
```
## HTML DOM(Document Object Model)
![](https://i.imgur.com/SaKuY7w.png)
1. 整份文件: document
2. 整個 document 的根元素 document.documentElement
3. 文件為一個由節點組成的樹狀結構
- 節點(node)
- 元素節點(element)
- 屬性節點(attribute)
- 文字節點(text)
### 選擇器
```javascript=
document.getElementById("id");
document.getElementsByTagName("tagname");
document.getElementsByClassName("classname");
document.querySelector("#id .class span");
document.querySelectorAll(".class");
```
### querySelector
```htmlmixed=
<h1 id="test">ID : <em></em></h1>
<h1 class="test">First class: <em></em></h1>
<h1 class="test">Second class: <em></em></h1>
```
```javascript=
document.querySelector("#test em").textContent = "#id > em";
var class_choose = document.querySelectorAll(".test em");
// [em, em]
console.log(class_choose);
for(var i = 0; i < class_choose.length; i++){
class_choose[i].textContent = "第 "+(i+1)+"個 class"
}
```
### 判斷元素是否存在
這邊有一個方法可以快速判斷某 id、class 之元素是否存在
```javascript=
if(document.getElementById("test") == null){
console.log("不存在");
}else if(document.getElementById("test") != null){
console.log("存在");
}
```
### HTML
#### innerHTML
```htmlmixed=
<div id="demo">
<a href="#">myLink</a>
</div>
<div id="demo2"></div>
<div id="demo3"></div>
```
```javascript=
var demo = document.getElementById("demo");
console.log(demo.innerHTML); // <a href="#">myLink</a>
demo.innerHTML = "Hello";
console.log(demo.innerHTML); // Hello
var demo2 = document.getElementById("demo2");
var demo3 = document.getElementById("demo3");
var num = 123;
demo2.innerHTML = "<h1>"+num+"</h1>"; // 123
demo3.innerHTML = `<h1>${num}</h1>`; // 123
```
innerHTML使用上需注意資料來源是否安全
這是一個簡易的留言板,會把用戶輸入的內容顯示出來
```htmlmixed=
<textarea id="content" cols="10" rows="10"></textarea>
<input id="submit" type="button" value="送出">
<div id="showContent"></div>
```
```javascript=
document.getElementById("submit").onclick = function(){
var content = document.getElementById("content").value;
document.getElementById("showContent").innerHTML = content;
};
```
但是如果前後端沒有過濾掉一些非法字元,導致內容直接傳入資料庫,會發生安全問題。
Ex用戶輸入:
```
Hello~~~
<script>alert('Hi')</script>
```
Hello會正常顯示,script不會顯示出來,但是已經被植入到頁面。這筆留言被傳入資料庫後,日後其他用戶到這個頁面時就會執行這段script
#### createElement
```htmlmixed=
<h1 id="testH1"></h1>
```
```javascript=
var newLink = document.createElement('a');
newLink.textContent = "click me";
newLink.setAttribute('href','http://www.google.com');
document.getElementById('testH1').appendChild(newLink);
```
#### removeElement
```javascript=
document.getElementById('testH1').removeChild(newLink);
```
### CSS
遇到屬性有 ``-`` 號的,移除後把後面的字母變大寫。
```javascript=
document.getElementById("demo").style.color = "blue";
// padding-top
document.getElementById("demo").style.paddingTop = "12px";
```
取得屬性值時會包含單位,ex: px、em...等等,若是要運算時需要轉為數字
```javascript=
myDiv.style.width = (parseInt(myDiv.style.width)+50) + "px";
```
### Attribute
```htmlmixed=
<a id="myLink" href="#">Click Me</a>
```
setAttribute
```javascript=
document.getElementById("myLink").setAttribute('href','http://www.google.com');
```
getAttribute
```javascript=
document.getElementById("myLink").getAttribute('href');
```
### Form
![](https://i.imgur.com/05hChP9.jpg)
![](https://i.imgur.com/MK8WKbY.jpg)
![](https://i.imgur.com/1Cxrtdm.jpg)
![](https://i.imgur.com/6KA0xKX.jpg)
![](https://i.imgur.com/NtImX0r.jpg)
![](https://i.imgur.com/XT5aKxA.jpg)
![](https://i.imgur.com/YdOsRIY.jpg)
## Event(事件)
### 1. 寫在HTML標籤上
```htmlmixed=
<h1 onclick="alert('clicked')">inline</h1>
```
### 2. 寫在js中
但是元素無法一次綁兩個一樣的事件。
當事件觸發時,會自動把事件的詳細資訊傳入function中的第一個參數,通常會命名為``e``或``event``
```javascript=
var btn = document.getElementById("btn");
btn.onclick = function(e){
alert('Hi1');
};
btn.onclick = function(e){
alert('Hi2');
};
// Hi2
```
#### 移除
```javascript=
btn.onclick = null;
```
### 3. addEventListener
```javascript=
var btn = document.getElementById("btn");
btn.addEventListener('click',function(e){
alert('clicked');
},false);
```
addEventListener的第三個參數決定事件處理是Event Bubbling(事件氣泡)還是Event Capturing(事件捕捉),一個是由內找到外(false預設);一個是由外到內(true)
舉例:
點click me時,console會先印出``li``,再印出``ul``。
但是改成true時,則會先印出``ul``,再印出``li``
```htmlmixed=
<ul id="ul">
<li id="li">click me</li>
</ul>
```
```javascript=
var ul = document.querySelector("#ul");
var li = document.querySelector("#li");
ul.addEventListener('click',function(e){
console.log('ul clicked');
},false);
li.addEventListener('click',function(e){
console.log('li clecked');
},false);
```
#### 移除
```javascript=
myBox.removeEventListener('click', myFun);
```
### e 事件詳細資訊
#### stopPropagation 中止冒泡事件
上一個例子,點click me時,會先顯示``li``,再往外顯示``ul``。
很多情況會遇到這種元素重疊的問題,當我們只想顯示點擊的``li``時,可以把冒泡中止,不讓它再往外找。
```javascript=
ul.addEventListener('click',function(e){
console.log('ul clicked');
},false);
li.addEventListener('click',function(e){
e.stopPropagation();
console.log('li clecked');
},false);
```
#### preventDefault 取消預設行為
有些元素會有默認行為,像是a連結點它會自動跳轉、submit點擊會自動把表單內容送出。
當我們不想讓它的預設動作執行時,可以取消它,像是前端想先檢查表單內容是否正確再傳到後端,所以要阻止submit點擊後就把內容送出。
```htmlmixed=
<a href="http://www.google.com" id="link">myLink</a>
```
```javascript=
var link = document.querySelector("#link");
link.addEventListener('click',function(e){
e.preventDefault();
console.log('link no jump');
},false);
```
#### target 取得點擊的元素
```htmlmixed=
<div class="header">
<ul style="border: 1px solid black; padding: 10px">
<li><a href="#" id="link">123</a></li>
</ul>
</div>
```
```javascript=
var header = document.querySelector(".header");
header.addEventListener('click',function(e){
// EX: <a href="#" id="link">123</a>
console.log(e.target);
// EX: LI
console.log(e.target.nodeName);
// EX: link
console.log(e.target.id);
},false);
```
#### 優化綁定,由父元素監聽子元素
```htmlmixed=
<div class="header">
<ul style="border: 1px solid black; padding: 10px; list-style: none">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
```
若是想顯示點擊的``li``內容,可以綁定每個``li``再顯示
```javascript=
var li = document.querySelectorAll('.header ul li');
for(var i = 0; i < li.length; i++){
li[i].addEventListener('click',showText);
}
function showText(e){
console.log(e.target.textContent);
// console.log(this.textContent);
}
```
但是這樣需要個別綁定,效率較低,而且動態新增的元素不會被綁定。
較好的做法是綁定``li``的父元素來監聽
```javascript=
var header = document.querySelector(".header");
header.addEventListener('click',function(e){
if(e.target.nodeName == "LI"){
console.log(e.target.textContent);
}
});
```
#### 取得滑鼠座標位置
screen 計算在整個螢幕之位置(算入解析度)
page 計算在瀏覽器中的網頁頁面之位置
client 計算在瀏覽器中的位置
知道滑鼠座標也可以把滑鼠圖示改變(覆蓋)成自訂圖案
```htmlmixed=
<body style="height: 1000px; cursor: none">
<div class="wrap" style="position: fixed; top:0">
<p>
screenX: <span class="screenX"></span>
screenY: <span class="screenY"></span>
</p>
<p>
// ex: 滑到最下面y座標:1000
pageX: <span class="pageX"></span>
pageY: <span class="pageY"></span>
</p>
<p>
// ex: 滑到最下面y座標:578
clientX: <span class="clientX"></span>
clientY: <span class="clientY"></span>
</p>
</div>
<div class="mouseImg">
<img src="http://dl.stickershop.line.naver.jp/products/0/0/9/768/android/main.png" width="50px">
</div>
</body>
```
```javascript=
var screenX = document.querySelector('.screenX');
var screenY = document.querySelector('.screenY');
var pageX = document.querySelector('.pageX');
var pageY = document.querySelector('.pageY');
var clientX = document.querySelector('.clientX');
var clientY = document.querySelector('.clientY');
var mouseImg = document.querySelector('.mouseImg');
var body = document.body;
body.addEventListener('mousemove', getPosition, false);
function getPosition(e){
screenX.textContent = e.screenX;
screenY.textContent = e.screenY;
pageX.textContent = e.pageX;
pageY.textContent = e.pageY;
clientX.textContent = e.clientX;
clientY.textContent = e.clientY;
mouseImg.style.left = e.clientX + 'px';
mouseImg.style.top = e.clientY + 'px';
}
```
### 綁定 document
```javascript=
// 綁定整個 document
document.addEventListener('click',fun);
// 綁定 body
var body = document.body;
body.addEventListener('click',fun);
```
### DOM Event 觸發事件
#### change 表單內容更動觸發
```htmlmixed=
<select id="select">
<option value="1">1</option>
<option value="2">2</option>
</select>
```
```javascript=
function showValue(e){
console.log(e.target.value);
console.log(this.value);
}
var select = document.querySelector("#select");
select.addEventListener('change',showValue,false);
```
#### keydown 按下鍵盤觸發
```htmlmixed=
<input type="text" id="input">
```
```javascript=
function showKey(e){
console.log(e.keyCode);
// enter: 13, space: 32, 1: 49, 2: 50, 3: 51
switch(e.keyCode){
case 49:
console.log('1');
break;
case 50:
console.log('2');
break;
case 51:
console.log('3');
break;
}
}
var input = document.querySelector("#input");
input.addEventListener('keydown',showKey,false);
```
![](https://i.imgur.com/7uZNDaN.jpg)
#### focus / blur 表單進入焦點/離開焦點觸發
```htmlmixed=
<input type="text" id="input">
```
```javascript=
var input = document.getElementById("input");
input.addEventListener('blur',function(e){
var value = this.value;
(value != '')? console.log(`Your text is ${value}`):console.log('You must write something');
});
```
#### mousemove 滑鼠hover(滑動)時觸發
```htmlmixed=
box.addEventListener('mousemove',function(){
console.log('mouse hover');
});
```
#### mouseover 滑鼠進入元素時觸發
```htmlmixed=
<div id="box" style="background-color: orange; width: 150px; height: 150px"></div>
```
```javascript=
document.getElementById('box').addEventListener('mouseover', function(){
console.log('進入');
});
```
#### mouseout 滑鼠離開元素觸發
```htmlmixed=
<div id="box" style="background-color: orange; width: 150px; height: 150px"></div>
```
```javascript=
document.getElementById('box').addEventListener('mouseout', function(){
console.log('離開');
});
```
### copy 複製時觸發
若要更改用戶複製內容可寫
```javascript=
document.addEventListener('copy', function (e) {
e.preventDefault(); // We want our data, not data from any selection, to be written to the clipboard
e.clipboardData.setData('text/plain', "Don't copy!");
e.clipboardData.setData('text/plain', window.getSelection() + "Don't copy!");
// e.clipboardData.setData('text/html', '<b>Hello, world!</b>');
});
```
說明:
- 如果默認事件沒有取消,就複製所選內容(如果有選中內容)到剪貼板;
- 如果取消了默認事件,同時調用 setData()方法:就複製 clipboardData 的內容到剪貼板;
- 如果取消了默認行為,而且沒有調用使用 setData()方法,就沒有任何行為(用戶所選內容也不會複製到剪貼板)。
### paste 貼上時觸發
輸入重要資料時,不希望使用者使用複製貼上的方式可寫
```htmlmixed=
<label>請輸入密碼: <input type="password" id="pass"></label>
```
```javascript=
document.getElementById("pass").addEventListener('paste', function(e){
e.preventDefault();
alert("密碼請勿用貼上的");
});
```
## 表單取值
### radio
```htmlmixed=
<form name="myForm">
<label><input name="language" type="radio" value="繁中" checked>繁中</label>
<label><input name="language" type="radio" value="韓文">韓文</label>
</form>
```
```javascript=
var form = document.getElementById("myForm");
var userLanguage;
// 取得radio的值
for(var i = 0; i < form.language.length; i++){
if(form.language[i].checked){
userLanguage = form.language[i].value;
break;
}
}
```
### select
```htmlmixed=
<form id="myForm">
<select name="country">
<option value="台灣">台灣</option>
<option value="韓國">韓國</option>
<option value="日本">日本</option>
</select>
</form>
```
```javascript=
var form = document.getElementById("myForm");
var userCountry = form.country.value;
```
## localStorage
瀏覽器儲存在本地端的資料,格式為``Key : value``。
**value 的型態只有 String**
### setItem 建立
```javascript=
var name = 'XXX';
localStorage.setItem('userName',name);
```
### getItem 取得
```javascript=
localStorage.getItem('userName');
```
範例
```htmlmixed=
<h2>請輸入你的名字:</h2>
<input type="text" id="userName">
<input type="button" id="submit" value="送出">
<input type="button" id="callName" value="顯示名字">
```
```javascript=
var submit = document.getElementById('submit');
submit.addEventListener('click',function(){
var name = document.getElementById('userName').value;
localStorage.setItem('userName',name);
});
var callName = document.getElementById('callName');
callName.addEventListener('click',function(){
var name = localStorage.getItem('userName');
alert(name);
});
```
### removeItem 移除
```javascript=
localStorage.removeItem('userName');
```
全部移除
```javascript=
localStorage.clear();
```
### JSON字串轉換
由於 value 只能存入 String ,所以要使用某些函數來轉換 String ,好方便存取。
#### 轉換成字串 JSON.stringify
```javascript=
var arr = ['a','b','c'];
// a,b,c is object
console.log(arr + " is " + typeof(arr));
var toStr = JSON.stringify(arr);
// [\"a\",\"b\",\"c\"] is string
console.log(toStr + " is " + typeof(toStr));
```
#### 物件轉 JSON 字串 + 縮排
```javascript=
<pre id="content"></pre>
document.getElementById('content').textContent = JSON.stringify(myJson, null, 3);
```
#### 轉換回原本格式 JSON.parse
```javascript=
var strToArr = JSON.parse(toStr);
// a,b,c is object
console.log(strToArr + " is " + typeof(strToArr));
```
### html屬性data-* 自訂資料
可以存放 value 到 html 屬性中,格式為``data-key="value"``
```htmlmixed=
<div class="test" data-name="Rose" data-age="18">click me</div>
```
存取方法為``.dataset.key``
```javascript=
var test = document.querySelector('.test');
console.log(test.dataset.name);
console.log(test.dataset.age);
```
#### 結合 array 用法
動態新增 array 資料到 ul ,並紀錄 index 到 data-num 中,
點擊 li 時依所點之 num 來刪除 array 中相對應之 index。
```htmlmixed=
<ul id="myList"></ul>
```
```javascript=
var myList = document.getElementById('myList');
var content = [
{
name: 'Molt',
sex: 'male'
}, {
name: 'YaoYao',
sex: 'male'
}, {
name: 'Lisa',
sex: 'famale'
}
];
function updateList(){
var str = "";
for(var i = 0; i < content.length; i++){
str += '<li data-num:' + i + '>' + content[i].name + '</li>';
}
myList.innerHTML = str;
}
updateList();
myList.addEventListener('click', function(e){
if(e.target.nodeName == "LI"){
content.splice(this.dataset.num, 1);
updateList();
}
});
```
## sessionStorage
相似於 localStorage ,不同之處在於 sessionStorage 在瀏覽器關掉後就自動清除。
### 建立
```javascript=
sessionStorage.setItem("key", value);
```
### 刪除
```javascript=
sessionStorage.removeItem("key");
```
## BOM(Browser Object Model)
![](https://i.imgur.com/qmAMIbU.png)
開啟瀏覽器後會開啟 ``window`` 物件,裡面包含了很多資訊,像是 document 中的變數、函式。
### window
列印
```javascript=
window.print();
```
開啟視窗
```javascript=
// 實際還有很多參數可以帶
window.open('http://www.google.com');
```
網頁頁面寬高
```javascript=
window.innerWidth;
window.innerHeight;
```
瀏覽器寬高
```javascript=
window.outerWidth;
window.outerHeight;
```
配合 JS 動態指定圖片大小
```css=
body{
background-image: url('img/bg.png');
background-repeat: no-repeat;
}
```
```javascript=
// 網頁載入時設定背景圖片為當前頁面寬度
var body = document.body;
body.style.backgroundSize = window.innerWidth + "px";
// 監聽使用者改變瀏覽器寬高事件,跟著改變圖片寬度
body.onresize = function(){
body.style.backgroundSize = window.innerWidth + "px";
}
```
#### setInterval 設定重複執行週期任務
```javascript=
test = window.setInterval("show()", 1000);
function show(){
console.log('Do in 1 sec again');
}
```
#### clearInterval 清除週期任務
```javascript=
window.clearInterval(test);
```
#### setTimeOut 設定單次執行任務
```javascript=
// 注意: 若是把 function 寫在裡面,不需要 "" 起來
test = setTimeout(function show(){
console.log('HIHI');
}, 1000);
```
#### clearTimeout
```javascript=
clearTimeOut(test);
```
### history
回到上一頁
```javascript=
window.history.back();
```
下一頁
```javascript=
window.history.forward();
```
### location
顯示當前網址
```javascript=
window.location.href;
```
跳轉到指定網址
```javascript=
window.location.href = 'http://www.google.com';
```
顯示參數
ex: ?a=3&b=4
```javascript=
window.location.search;
```
### navigator
顯示瀏覽器資訊
```javascript=
window.navigator.appVersion;
// "5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
```
顯示用戶當前是否連接網路
```javascript=
window.navigator.onLine; // true、false
```
## Ajax
> AJAX即「Asynchronous JavaScript and XML」(非同步的JavaScript與XML技術),指的是一套綜合了多項技術的瀏覽器端網頁開發技術。 來源:維基百科
```javascript=
var xhr = new XMLHttpRequest();
// true: 非同步 , false: 同步
xhr.open('get', 'http://xxx', true);
xhr.send(null);
```
### XMLHttpRequest 狀態碼(readyState)
![](https://i.imgur.com/F0DosG3.jpg)
- 0
已經產生 XMLHttpRequest ,但還沒連結到要取得的網址。
- 1
用了 open() ,但還沒傳送資料過去
- 2
用了 send()
- 3
loading 資料
- 4
撈回資料了,數據已接收完全
### 實際範例
#### 透過 API 取得 JSON 資料
```javascript=
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://xxx', true);
xhr.send(null);
xhr.onload = function(data) {
console.log(xhr.responseText);
};
```
#### POST(form)
```javascript=
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://xxx', true);
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send('email=test@gmail.com&password=123456');
```
#### POST(Json)
```javascript=
var account = {
email: 'test@gmail.com',
password:'12456'
}
var xhr = new XMLHttpRequest();
xhr.open('post','https://xxx.com',true);
xhr.setRequestHeader('Content-type','application/json');
var data = JSON.stringify(account);
xhr.send(data);
```
<style>
.ui-infobar { display: none; }
</style>