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>