# javeScript 重要的 https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part3/var_const_naming.html https://ithelp.ithome.com.tw/users/20065504/ironman/1259 六角 z ![](https://i.imgur.com/0FzlHZv.png) ## 通知 https://www.youtube.com/watch?v=Bm0JjR4kP8w&ab_channel=WebDevSimplified ## iife https://www.youtube.com/shorts/WAqvLSgR6_U 應用 ## 剪貼簿 https://www.youtube.com/watch?v=1D5evO70wWY&list=PLSfH3ojgWsQr9bPuWl7kYHYy5kqhzMzNw&index=7&ab_channel=Acadea.io 有原生地可以用 ## 無限滾動效果載入 https://www.youtube.com/watch?v=bc1bld3yuxQ&ab_channel=Acadea.io ## Debounce and Throttle 如果他每次input change打api 那會效能太開 不如抓 一秒後的現在的value去打 ![](https://i.imgur.com/cIfXuXu.png) https://www.youtube.com/watch?v=cjIswDCKgu0&ab_channel=WebDevSimplified ## import() 超重要 https://ithelp.ithome.com.tw/articles/10250847 https://medium.com/unalai/%E8%AA%8D%E8%AD%98-dynamic-import-3a6f75da2fc9 ## 很好的基本介紹 https://fireship.io/courses/javascript/concepts-console/ ## callback 非同步也是callback一種 他會 把你不同步的丟在task queue裡面 圖片裡面的web API 指的是ajax DOM SetTimeout之類的 是瀏覽器提供的 ![](https://i.imgur.com/TJhB8VH.png) ## function 詳細 必看 https://www.youtube.com/watch?v=gigtS_5KOqo ## 子找父指定名稱 DOM操作 https://developer.mozilla.org/en-US/docs/Web/API/Element/closest ## string取第幾個字 'apple' 取他第二個就[2]就可以了 ![](https://i.imgur.com/vSZxdMz.png) ## ES6 中如果希望「陣列(Array)」的元素不會重複,可以使用 Set;如果是希望物件(Object)的鍵不會重複,則可以使用 Map。 https://pjchender.dev/javascript/js-set/ ## object vs array https://stackoverflow.com/questions/17295056/array-vs-object-efficiency-in-javascript 可以看到 短的用array 多的 用object或 裡面很多空的 無排序的 你也可以看演算法two Sun那邊 他也是用object ## ES6模塊 一個寫千行會太大 所以要分開 ![](https://i.imgur.com/AtV43Rh.png) 但這樣全域變數會影響到 所以要用模塊概念 ![](https://i.imgur.com/8y2z1MM.png) ### const let 不過,在JavaScript語言中,常數指是的"不能再次指定值"(not re-assigned),而不是"完全無法改變值"(immutable),這兩個是有差異的概念。在之後的內容中會說明,物件類型的常數內容屬性是可以改變的,而在這個情況下常數會是一個常數指標。基本上,JavaScript語言中並沒有immutable(不可改變的)的特性。 宣告了一個常數,代表這個識別名稱的參照(reference)是唯讀的(read-only),並不代表這個參照指定到的值是不可改變的(immutable)。這是在講什麼?這是在講如果你宣告的常數是一個物件或陣列類型,像這種參照類型的值,裡面的值是可以作改變的。像下面的例子都是合法的使用: ``` const a = [] a[0] = 1 const b = {} b.foo = 123 ``` **所以對於物件、陣列、函式來說,使用const常數來宣告就可以,除非你有需要再指定這個陣列或物件的參照。** 註: 既然可以用const來宣告物件與陣列,用全英文字元全大寫來命名常數,也已經不需要,像一般的正常命名變數就可以了,一般用小駝峰(camelCase)命名法,例如myBook、userName這樣。 ### flasy 與預設 Douglas Crockford是在JavaScript界相當知名的大師級人物,他主張使用"truthy"與"falsy"來描述資料類型的值。也就是說,像上面說講的那些會轉換為布林值的false值的資料類型的值,通稱為"falsy"(字典裡是沒這個字詞,意思是"false的"),你可以把它當成是"false家族成員"。 "falsy"包含了0, -0, null, NaN, undefined, 空白字串(''),當然也一定包含了false值 "falsy"的概念在JavaScript的邏輯運算,以及布林值中都是很重要的概念。之前已經看到一個邏輯運算符 - 邏輯反相(Logic NOT)(!)的運用,還有兩個邏輯運算符,也很常用到: 邏輯與(Logical AND)(&&) 邏輯或(Logical OR)(||) 邏輯與(&&)與邏輯或(||)的運算,正常的使用情況下,並沒有什麼太大的問題,範例如下: ``` console.log(true && false) //false console.log(true || false) //true ``` 只不過,因為JavaScript採用了與其他程式語言不同的邏輯運算的回傳值設計,我們把這兩個運算符稱為"短路求值(Short-circuit)"的運算符。實際上JavaScript中,在經過邏輯與(&&)與邏輯或(||)的運算後,它的回傳值是 - 最後的值(Last value),並不是像在常見的Java、C++、PHP程式語言中的是布林值。 因此,短路求值運算變成JavaScript中一種常被使用的特性,尤其是邏輯或(||)。那這與之前所說的"falsy"特性有何關係?關於邏輯或(||)運算在JavaScript語言中的可以這樣說明: 邏輯或(Logical OR)(||)運算符在運算時,如果當第1個運算子為"falsy"時,則回傳第2個運算子。否則,將會回傳第1個運算子。 也就是說像下面這樣的程式碼,基本上它的回傳值都不是布林值,而是其他的資料類型的值: ``` console.log('foo' || 'bar') // 'foo' console.log(false || 'bar') // 'bar' ``` "falsy"擴大了這種運算的範圍,像下面的程式碼的回傳情況: ``` console.log( 0 || '' || 5 || 'bar') //5 console.log(false || null || '' || 0 || NaN || 'Hello' || undefined) //'Hello' ``` 而出現了一種常見的在程式碼中簡短寫法,這稱為"指定預設值"的寫法,就是使用邏輯或(Logical OR)(||)運算符,範例如下: `let a = value || 5 //5`是預設值 不過,這樣的指定預設值的寫法,並不能完全精確判斷value屬於哪一種資料類型與值的方式,在某些情況下會出現意外,例如你可能認為value=0也是合法的值,但value=0時,a會被短路求值成5。所以,只要當value是"falsy"時,變數a就會被指定為預設值5。所以在使用時還是需要特別注意,不然你會得到出乎意料的結果。 ### const 預設 這個像我們之前說過的,用邏輯或運算符(||)來指定變數/常數預設值的語句。不過如果是單純的判斷某個值是否存在,然後設定它為預設值,用邏輯或運算符(||)是比較好的作法,例如: ``` const foo = a ? a : b //相當於 const foo = a || b ``` ### 轉整數要注意 如果是ES6之後的2、8進位定義格式的字串,需要用Number()方法才能轉為10進位,parseInt是無法正確轉換為整數的。範例如下: ### switch注意 判斷時有多個case情況而執行同一個語句時,會使用像下面這個範例的語法,這個語法結構也是很常見的用法,例如: ``` const fruit = '芒果' switch (fruit) { case '芭樂': case '香蕉': console.log(fruit, '是四季都出產的水果') break case '西瓜': case '荔枝': case '芒果': default: console.log(fruit, '是只有夏季出產的水果') break } ``` ### for in **for廻圈語句完全可以取代for...in語句** for...in語句主要是搭配物件類型使用的,不要使用在陣列資料類型。它是用於迭代物件的可列舉的屬性值(enumerable properties),而且是任意順序的。因為陣列資料在進行迭代時,順序是很重要的,所以陣列資料類型不會用這個語句,而且陣列資料類型本身就有太多好用的迭代方法。 ### for of for...of語句是新的ES6語句,可以用於可迭代物件上,取出其中的值,可迭代物件包含陣列、字串、Map物件、Set物件等等。簡單的範例如下: ``` //用於陣列 let aArray = [1, 2, 3] for (let value of aArray) { console.log(value) } //用於字串 let aString = "abcd"; for (let value of aString) { console.log(value); } ``` ### break與continue break(中斷)與continue(繼續)是用於迴圈區塊中語句的控制,在switch語句中也有看過break這個關鍵字的用法,是一個跳出switch語句的語法。 break是"中斷整個迴圈語句"的意思,可以中斷整個迴圈,也就是"跳出"迴圈區塊的語句,如果是在巢狀迴圈時是跳出最近的一層。break通常會用在迴圈語句中的if判斷語句裡,例如下面這個例子: ``` let count = 0 while (count < 10) { console.log(count) //count的值為6時將會跳出廻圈 if(count === 6) break count++ } ``` continue是"繼續下一次迴圈語句"的意思,它會忽略在continue之下的語句,直接跳到下一次的重覆執行語句的最開頭。因為continue有"隱含的goto到迴圈的最開頭程式碼",它算是一個在JavaScript語言中的壞特性,它會讓你的判斷情況(condition)整個無效化,也可以破壞整體的迴圈語句執行結構,容易被濫用出現不被預期的結果,結論是能不用就不要使用。一個簡單的範例如下: ``` let count = 0 let a = 0 while (count < 10) { console.log('count = ', count) console.log('a = ', a) count++ //count的值大於為6時將會不再遞增a變數的值 if(count > 6) continue a++ } ``` 註: 雖然JavaScript語言中並沒有goto語法,但迴圈語句中的continue搭配labeled語句,相當於其他程式語言中的goto語法。goto語法幾乎在所有的程式語言中,都是惡名昭彰的一種語法結構。 #### for 要注意的語法 用於判斷情況的運算,應該要儘可能避免,先作完運算在迴圈外部或或for迴圈的第一個表達式中,效率可以比較好些。 //較差的寫法 ``` for (let i=0 ; i < aArray.length ; i++) { //statment } //較好的寫法 for (let i=0, len = aArray.length ; i < len; i++) { //statment } //另一種寫法,這種寫法腦袋要很清楚才看得懂 for (let i = aArray.length; i--;){ //statment } //較差的寫法 let i = 0 while (i < aArray.length) { //statment i++ } //較好的寫法 let i = 0 let len = aArray.length while ( i < len) { //statment i++ } ``` 註: 上面有一說法是在陣列資料類型的迴圈中的第三表達式(更新用表達式)中,使用i--會比i++效率更佳。實際上在現在的瀏覽器上這兩種的執行速度是根本一樣,沒什麼差異。 ## 物件傳質 傳參考 https://www.youtube.com/watch?v=y1odVMpi6dU 重點 傳參考的被改 原本的也會被改 因為物件是存在記憶體的 ### 不固定傳入參數(Variadic)與其餘(rest)參數 像下面這個範例中,原先sum函式中,定義了要有三個傳入參數,但如果真正在呼叫函式時傳入的參數值(arguments)並沒有的情況下,或是多出來的時候,會發生什麼情況? 前面有說到,沒有預設值的時候會視為undefined值,而多出來的情況,是會被直接略過。有的時候需要一種能夠"不固定傳入參數"的機制,在各種函式應用時,才能比較方便。 ``` function sum(x, y, z) { return x+y+z } console.log(sum(1, 2, 3)) //6 console.log(sum(1, 2)) //NaN console.log(sum(1, 2, 3, 4)) //6 console.log(sum('1', '2', '3')) //123 console.log(sum('1', '2')) //12undefined console.log(sum('1', '2', '3', '4')) //123 ``` 雖然上一節有說過的,有個隱藏的arguments物件,它可以獲取到所有傳入的參數值,然後用類似陣列的方式來使用,但它的設計相當怪異(有一說是設計錯誤),使用時要注意很多例外的情況,加上使用前根本也不需要定義,很容易造成程式碼閱讀上的困難。另外,在一些測試報告中,使用arguments物件本身比有直接使用具有名稱傳入參數慢了數倍。所以結論是,arguments物件的是不建議使用它的。 那麼,有沒有其他的機制可以讓程式設計師能處理不固定的傳入參數? 在ES6中加入了其餘參數(rest parameters)的新作法,它使用省略符號(ellipsis)(...)加在傳入參數名稱前面,其餘參數的傳入值是一個標準的陣列值,以下是一個範例: ``` function sum(...value) { let total = 0 for (let i = 0 ; i< value.length; i++){ total += value[i] } return total } console.log(sum(1, 2, 3)) //6 console.log(sum(1, 2)) //3 console.log(sum(1, 2, 3, 4)) //10 console.log(sum('1', '2', '3')) //123 console.log(sum('1', '2')) //12 console.log(sum('1', '2', '3', '4')) //1234 ``` 如果要寫得更漂亮、簡潔的的語法,直接使用Array(陣列)本身的好用方法,像下面這樣把原本的範例重寫一下: ``` function sum(...value) { return value.reduce((prev, curr) => prev + curr ) } ``` 註: reduce(歸納)是陣列的方法之一,它可以用來作"累加" 其餘參數只是扮演好參數的角色,代表不確定的其他參數名稱,所以如果一個函式中的參數值有其他的確定傳入參數名稱,其餘參數名稱應該要寫在最後一個位子,而且一個函式只能有一個其餘參數名稱: function(a, b, ...theArgs) { // ... } 其餘參數與arguments物件的幾個簡單比較: 其餘參數只是代表其餘的傳入參數值,而arguments物件是代表所有傳入的參數值 其餘參數的傳入值是一個標準陣列,可以使用所有的陣列方法。而arguments物件是"偽"陣列的物件類型,不能使用陣列的大部份內建方法 其餘參數需要定義才能使用,arguments物件不需要定義即可使用,它是隱藏機制 #### 回調(callback) 回調(callback)是一種特別的函式結構,也因為JavaScript具有"高階函式(Higher-order function)"的特性,意思是說在函式中可以用另一個函式當作傳入參數值,最後也可以回傳函式。 一般而言,函式使用回傳值(return)作為最後的執行語句。但回調並不是,回調結構首先會定義一個函式類型的傳入參數,在此函式的最後執行語句,即是呼叫這個函式傳入參數,這個函式傳入參數,通常我們稱它為回調(callback)函式。回調函式經常使用匿名函式的語法,直接寫在函式的傳入參數中 ``` function showMessage(greeting, name, callback) { console.log('you call showMessage') callback(greeting, name) } showMessage('Hello!', 'Eddy', function(param1, param2) { console.log(param1 + ' ' + param2) }) ``` 由於回調函式是一個函式類型,通常會在使用它的函式中,作一個基本檢查,以免造成程式錯誤,本章節的最前面有說明過了,函式的typeof回傳值是'function',以下為改寫過的程式碼,改寫過後不論是不是有傳入回調函式,都可以正常運作,也就是回調函式變成是一個選項: ``` unction showMessage(greeting, name, callback) { console.log('you call showMessage') if (callback && typeof(callback) === 'function') { callback(greeting, name) } } ``` 回調(callback)提供了使用此函式的開發者一種彈性的機制,讓程式開發者可以自行定義在此函式的最後完成時,要如何進行下一步,這通常是在具有執行流程的數個函式的組合情況。實際上,回調函式實現了JavaScript中非同步(asynchronously)的執行流程,這使得原本只能從頭到底(top-to-bottom)的程式碼,可以在同時間執行多次與多種不同的程式碼。在實際應用情況時,回調結構在JavaScript程式中大量的被使用,它也變成一種很明顯的特色,例如以下的應用中很常見: HTML中的DOM事件 AJAX 動畫 Node.js #### 提升(Hoisting) 簡單的來說,提升是JavaScript語言中的一種執行階段時的特性,也是一種隱性機制。不過,沒先定義與指定值就使用,這絕對是個壞習慣是吧?變數/常數沒指定好就使用,結果一定是不是你要的。 var、let和const會被提升其定義,但指定的值不會一併提升上去,像下面這樣的程式碼: ``` console.log(x) //undefined var x = 5 console.log(y) //undefined let y = 5 ``` 最後的結果出乎意料,竟然只是沒指定值的undefined,而不是程式錯誤。實際上這程式碼裡的變數被提升(Hoisting)了,相當於: ``` var x console.log(x) x = 5 let y console.log(y) y = 5 ``` 函式定義也會被提升,而且它變成可以先呼叫再定義,也就是整個函式定義內容都會被提升到程式碼最前面。不過這對程式設計師來說是合理的,在很多程式語言中都可以這樣作: f ``` oo() //可執行 function foo(){ console.log('Hello') } ``` 不過使用匿名函式的指定值方式(函式表達式, FE),就不會有整個函式定義都被提升的情況,只有變數名稱被提升,這與上面的變數宣告方式的結果一致: ``` foo() //錯誤: foo is not a function let foo = function(){ console.log('Hello') } ``` 結論如下: 所有的定義(var, let, const, function, function*, class)都會被提升 使用函式定義時,在函式區塊中的這些定義也會被提升到該區塊的最前面 當函式與變數/常數同名稱而提升時,函式的優先程度高於變數/常數。 遵守好的風格習慣可以避免掉變數提升的問題 ## 展開運算符與其餘運算符 展開運算符(Spread Operator)與其餘運算符(Rest Operator)是ES6中的其中兩種新特性,雖然這兩種特性的符號是一模一樣的,都是(...)三個點,但使用的情況與意義不同。我們常常在文字敘述或聊天時,這個(...)常用來代表了"無言"、"無窮的想像"或"還有其他更多的"的意思。 簡單摘要一下這個語法的內容: 符號都是三個點(...) 都與陣列有關 一個是展開陣列中的值,一個是集合其餘的值成為陣列 註: 三個點符號(...),比較正式的英文字詞是Ellipsis,翻成中文是"省略"符號的意思,不過它有各種形式(全形或半形),也有超過三個點的情況,所以一般要說得明白只有三個點的情況,會用"three dots"與"dot-dot-dot"反而更為明確。 註: 目前看到的名詞上並沒有統一的但都是指這種語法。在ES6標準上的用語是SpreadElement與rest parameter,並沒有集合成為一個章節,而是內容散落在各章節中。在MDN上用Spread operator與Spread syntax的名詞(標題是syntax,網址列上是operator),以及Rest operator與Rest parameters(標題是parameters,但連結是operator)。 註: 用於物件上的類似語法並不是ES6中的特性,它是正在制定中的新語法標準,不過在React與Redux中很常見,能透過babel編譯,這稱為Object Rest/Spread Properties 展開運算符(Spread Operator) 展開運算符是把一個陣列展開成個別的值的速寫語法,它只會在陣列字面定義與函式呼叫時使用 展開運算符(Spread Operator)是把一個陣列展開(expand)成個別值,這個運算符後面必定接著一個陣列。最常見的是用來組合(連接)陣列,對應的陣列方法是concat,以下是一個簡單的範例: ``` const params = [ "hello", true, 7 ] const other = [ 1, 2, ...params ] // [ 1, 2, "hello", true, 7 ] ``` 展開運算符可以作陣列的淺拷貝,當然陣列的淺拷貝有很多種方式,這是新的一種語法,也是目前語法上最簡單的一種: ``` const arr = [1,2,3] const arr2 = [...arr] ``` `arr2.push(4) //不會影響到arr` 註: 淺拷貝(shallow-copy)對於陣列中的陣列值(多維陣列),或是有複雜的物件值情況時,是只會拷貝參照值而已。 註: 上述的展開運算符在陣列字面中使用時,並沒有限制位置,或是在個數。像const arr = [...a, 1, ...b]這樣的語法都是可以的。 你也可以用來把某個陣列展開,然後傳入函式作為傳入參數值,例如下面這個一個加總函式的範例: ``` function sum(a, b, c) { return a + b + c } const args = [1, 2, 3] sum(…args) // 6 ``` 對照ES5中的相容語法,則是用apply函式,它的第二個參數也是使用陣列,以下是用ES5語法與上面相同結果的範例程式: ``` function sum(a, b, c) { return a + b + c; } var args = [1, 2, 3]; sum.apply(undefined, args) ;// 6 ``` 展開運算符還有一個特別的功能,就是把可迭代(iterable)或與陣列相似(Array-like)的物件轉變為陣列,在JavaScript語言中內建的可迭代(iterable)物件有String、Array、TypedArray、Map與Set物件,而與陣列相似(Array-like)的物件指的是函式中的隱藏物件"arguments"。下面的範例是轉變字串為字元陣列: ``` const aString = "foo" const chars = [ ...aString ] // [ "f", "o", "o" ] ``` 下面的範例是把函式中的隱藏偽物件"arguments"轉成真正的陣列物件: ``` function aFunc(x){ console.log(arguments) console.log(Array.isArray(arguments)) //轉為真正的陣列 const arr = [...arguments] console.log(arr) console.log(Array.isArray(arr)) } aFunc(1) ``` 其餘運算符(Rest Operator) 其餘運算符是收集其餘的(剩餘的)這些值,轉變成一個陣列。它會用在函式定義時的傳入參數識別名定義(其餘參數, Rest parameters),以及解構賦值時 會用其餘運算符(Rest Operator)的主要意義是因為它會用在兩個地方,一個是比較常提及的在函式定義中的傳入參數定義中,稱之為其餘參數(Rest parameters)。另一種情況是用在解構賦值時。 其餘參數(Rest parameters) 就像在電影葉問中的台詞:"我要打十個",其餘參數可以讓你一次打剩下的全部,不過葉問會變成一個陣列。 既然是一個參數的語法,當然就是用在函式的傳入參數定義。其餘參數代表是將"不確定的傳入參數值們"在函式中轉變成為一個陣列來進行運算。例如下面這個加總的範例: ``` function sum(…numbers) { const result = 0 numbers.forEach(function (number) { result += number }) return result } sum(1) // 1 sum(1, 2, 3, 4, 5) // 15 ``` 特別注意: 其餘參數在傳入參數定義中,必定是位於最後一位,並且在參數中只能有一個其餘參數。 其餘參數的值在沒有傳入實際值時,會變為一個空陣列,而不是undefined,以下的範例可以看到這個結果: ``` function aFunc(x, ...y){ console.log('x =', x, ', y = ' , y) } aFunc(1,2,3) //x = 1, y = [2, 3] aFunc() //x = undefined, y = [] ``` 其餘參數的設計有一個很明確的用途,就是要取代函式中那個隱藏"偽陣列"物件arguments,arguments雖然會包含了所有的函式傳入參數,但它是個類似陣列的物件卻沒有大部份陣列方法,它不太像是個隱藏的密技,比較像是隱藏的陷阱,很容易造成誤解或混亂,完全不建議你使用arguments這個東西。 其餘參數的值是一個真正的陣列,而且它需要在傳入參數宣告才能使用,至少在程式碼閱讀性上勝出太多了。 解構賦值(destructuring)時 解構賦值在另一獨立章節會講得更詳細,這裡只是要說明其餘運算符的另一個使用情況。解構賦值也是一個ES6中的新特性。 解構賦值是用在"陣列指定陣列"或是"物件指定物件"的情況下,這個時候會根據陣列原本的結構,以類似"鏡子"對映樣式(pattern)來進行賦值。聽起來很玄但用起來很簡單,這是一種為了讓陣列與物件指定值時更方便所設計的一種語法。例如以下的範例: ``` const [x, y, z] = [1, 2, 3] console.log(x) //1 ``` 像這個例子就是最簡單的陣列解構賦值的範例,x當然會被指定為1,y與z你應該用腳底板也想得到是被指定了什麼值。 當使用其餘運算符之後,就可以用像其餘參數的類似概念來進行解構賦值,例如以下的範例: ``` const [x, ...y] = [1, 2, 3] console.log(x) //1 console.log(y) //[2,3] ``` 當右邊的值與左邊數量不相等時,"鏡子對映的樣式"就會有些沒對到,用了其餘運算符的那個識別名稱,就會變成空陣列。就會像下面這個例子一樣: ``` const [x, y, ...z] = [1] console.log(x) //1 console.log(y) //undefined console.log(z) //[] ``` 在函式傳入參數中作解構賦值,這個例子會的確也是一種解構賦值的語法,而且加了上一節的函式中的其餘參數的用法。例子出自MDN的這裡: ``` function f(...[a, b, c]) { return a + b + c; } f(1) // NaN (b and c are undefined) f(1, 2, 3) // 6 f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured) ``` 你可以回頭再看一下"其餘參數"的使用情況,是不是與解構賦值時很相似。 特別注意: 在使用解構賦值時一樣只能用一個其餘運算符,位置也只能放在最後一個。 ES7+標準的其餘屬性(Rest Properties)與展開屬性(Spread Properties) 上面都只有談到與陣列搭配使用,但你可能會看到在物件上也會使用類似語法與符號(...),尤其是在React與Redux中。這些都是還在制定中的ES7之後草案標準,稱為其餘屬性(Rest Properties)與展開屬性(Spread Properties)。例如下面這樣的範例,來自這裡: ``` // Rest Properties let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 } console.log(x) // 1 console.log(y) // 2 console.log(z) // { a: 3, b: 4 } // Spread Properties let n = { x, y, ...z } console.log(n) // { x: 1, y: 2, a: 3, b: 4 } ``` 有些新式的框架或函式庫中已經開始使用了,babel轉換工具可以支援轉換這些語法,但使用時要注意要額外加裝babel-plugin-transform-object-rest-spread外掛。 撰寫風格建議 不要使用函式中的arguments,總是使用其餘參數語法來取代它。(Airbnb 7.6, Google 5.5.5.2, eslint: prefer-rest-params). 不要在展開運算符與其餘運算符後面有空格,也就是與後面的識別名稱(傳入參數名稱、陣列名稱)之間要緊接著。(Google 5.2.5/5.5.5.2, eslint: rest-spread-spacing) 用展開運算符的語法來作拷貝陣列。(Airbnb 4.3) 用展開運算符的語法來取代函式中的apply的語法,作不定個數傳入參數的函式呼叫。(Airbnb 7.14, eslint: prefer-spread) 用展開運算符的語法來取代slice與concat方法的語法。(Google 5.2.5) 優先使用物件展開運算符(object spread operator)的語法取代Object.assign,來作物件的淺拷貝。(Airbnb 3.8) (ES7+標準) 結論 ## 解構 https://www.youtube.com/watch?v=UgEaJBz3bjY 可用來重新命名 ![](https://i.imgur.com/YRTT4fB.png) 或嵌套 ![](https://i.imgur.com/tOxBUDo.png) 解構賦值 解構賦值(Destructuring Assignment)是一個在ES6的新特性,用於提取(extract)陣列或物件中的資料,這是一種對原本語法在使用上的改進,過去要作這件事可能需要使用迴圈或迭代的語句才行,新語法可以讓程式碼在撰寫時更為簡短與提高閱讀性。 解構賦值的解說只有一小段英文: The destructuring assignment syntax is a JavaScript expression that makes it possible to extract data from arrays or objects using a syntax that mirrors the construction of array and object literals. 這句後面的mirrors the construction of array and object literals,代表這個語法的使用方式 - 如同"鏡子"一般,對映出陣列或物件字面的結構。也就是一種樣式(pattern)對映的語法。 解構賦值如果你能抓得住它的基本概念,就可以很容易理解與使用。不過與ES6其他的特性配合時,會顯得複雜比較難理解。 在使用時有以下幾種常見的情況: 從陣列解構賦值 從物件解構賦值(或是從混用物件或陣列) 非物件或非陣列解構賦值 解構賦值時給定預設值 搭配函式的傳入參數使用 destructuring: 變性、破壞性。使用"解構"是對照de-字頭有"脫離"、"去除"的意思。 assignment: 賦值、指派。賦值通常指的是程式中使用等號(=)運算符的語句。 從陣列解構賦值(Array destructuring) 從陣列解構賦值沒太多學問,唯一比較特別的是可以用其餘運算符(Rest Operator)的語法,既然是其餘運算符,最後就會把其餘的對應值集合成一個陣列之中。下面是幾個幾個範例: //基本用法 const [a, b] = [1, 2] //a=1, b=2 //先宣告後指定值,要用let才行 let a, b [a, b] = [1, 2] // 略過某些值 const [a, , b] = [1, 2, 3] // a=1, b=3 // 其餘運算 const [a, ...b] = [1, 2, 3] //a=1, b=[2,3] // 失敗保護 const [, , , a, b] = [1, 2, 3] // a=undefined, b=undefined // 交換值 const a = 1, b = 2; [b, a] = [a, b] //a=2, b=1 // 多維複雜陣列 const [a, [b, [c, d]]] = [1, [2, [[[3, 4], 5], 6]]] // 字串 const str = "hello"; const [a, b, c, d, e] = str 用法就是這麼簡單,用來賦值的等號符號(=)左邊按照你寫的變數或常數樣式,然後在右邊寫上要對映數值,就像之前說的"鏡子"般的對應。當沒有對應的值時,就會得到undefined。 從物件解構賦值(Object destructuring) 物件除了有使用特別的字面符號,也就是花括號({})來定義,其中也會包含屬性。按照基本的原則,也是用像"鏡子"般的樣式對應,一樣看範例就很容易理解: // 基本用法 const { user: x } = { user: 5 } // x=5 // 失敗保護(Fail-safe) const { user: x } = { user2: 5 } //x=undefined // 賦予新的變數名稱 const { prop: x, prop2: y } = { prop: 5, prop2: 10 } // x=5, y=10 // 屬性賦值語法 const { prop: prop, prop2: prop2 } = { prop: 5, prop2: 10 } //prop = 5, prop2=10 // 相當於上一行的簡短語法(Short-hand syntax) const { prop, prop2 } = { prop: 5, prop2: 10 } //prop = 5, prop2=10 // ES7+的物件屬性其餘運算符 const {a, b, ...rest} = {a:1, b:2, c:3, d:4} //a=1, b=2, rest={c:3, d:4} 下面的語法是個有陷阱的語法,這是錯誤的示範: // 錯誤的示範: let a, b { a, b } = {a: 1, b: 2} 註: 這個語法如果使用const來宣告常數是根本不能使用,只能用let來宣告變數。而且eslint檢查工具一定會回報語法錯誤。 因為在Javascript語言中,雖然使用花括號符號({})是物件的宣告符號,但這個符號用在程式敘述中,也就是前面沒有let、const、var這些宣告字詞時,則是代表程式碼的區塊(block)。在外面再加上括號符號(())就可以改正,括號符號(())有表達式運算的功能,正確的寫法如下: let a, b ({ a, b } = {a: 1, b: 2}) //a=1, b=2 註: 在大部份情況,你應該是在定義常數或變數時,就進行解構賦值。 複雜的物件或混合陣列到物件,如果你能記住之前說的鏡子樣式對映基本原則,其實也很容易就能理解: // 混用物件與陣列 const {prop: x, prop2: [, y]} = {prop: 5, prop2: [10, 100]} console.log(x, y) // => 5 100 // 複雜多層次的物件 const { prop: x, prop2: { prop2: { nested: [ , , b] } } } = { prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}} console.log(x, b) // => Hello c 從非陣列或非物件解構賦值 從其他的資料類型進行陣列或物件解構,一開始是null或undefined這兩種時,你會得到錯誤: const [a] = undefined const {b} = null //TypeError: Invalid attempt to destructure non-iterable instance 如果是從其他的原始資料類型布林、數字、字串等作物件解構,則會得到undefined值。 const {a} = false const {b} = 10 const {c} = 'hello' console.log(a, b, c) // undefined undefined undefined 從其他的原始資料類型布林、數字、字串等作陣列解構的話,只有字串類型可以解構出字元,在上面的例子有看到這種情況,其他也是得到undefined值: const [a] = false const [b] = 10 const [c] = 'hello' //c="h" console.log( a, b, c) 註: 字串資料類型的值只能用在陣列的解構賦值,無法用在物件的解構賦值。 以上會有出現這樣的結果,是當一個值要被進行解構時,它會先被轉成物件(或陣列),因為null或undefined無法轉成物件(或陣列),所以必定產生錯誤,這是第一階段。下一個階段如果這個值轉換的物件(或陣列),沒有附帶對應的迭代器(Iterator)就無法被成功解構賦值,所以最後回傳undefined。 解構賦值時的預設值 在等號左邊的樣式(pattern)中是可以給定預設值的,作為如果沒有賦到值時(對應的值不存在)的預設數值。 const [missing = true] = [] console.log(missing) // true const { message: msg = 'Something went wrong' } = {} console.log(msg) // Something went wrong const { x = 3 } = {} console.log(x) // 3 要作一個簡單的陷阱題滿簡單的,你可以試看看下面這個範例中到底是賦到了什麼值: const { a ='hello' } = 'hello' const [ b ='hello' ] = 'hello' console.log( a, b) 在函式傳入參數定義中使用 在函式傳入參數定義中也可以使用解構賦值,因為函式的傳入參數本身也有自己的預設值設定語法,這也是ES6的一個特性,所以使用上會容易與解構賦值自己的預設值設定搞混。這地方會產生不少陷阱。 一個簡單的解構賦值用在函式的參數裡,這是正常情況的語法: function func({a, b}) { return a + b } func({a: 1, b: 2}) // 3 當你用上了預設值的機制,而且前面的a有預設值,後面的b就沒有,這時候因為沒有賦到值時,都會是undefined值,任何數字加上undefined都會變成NaN,也就是非數字的意思: function func({a = 3, b}) { return a + b } func({a: 1, b: 2}) // 3 func({b: 2}) // 5 func({a: 1}) // NaN func({}) // NaN func() // Cannot read property 'a' of undefined 當a與b兩個都有預設值時,NaN的情況不存在: function func({a = 3, b = 5}) { return a + b } func({a: 1, b: 2}) // 3 func({a: 1}) // 6 func({b: 2}) // 5 func({}) // 8 func() // Cannot read property 'a' of undefined 實際上函式傳入參數它自己也可以加預設值,但這情況會讓最後一種func()呼叫時與func({})相同結果: function func({a = 3, b = 5} = {}) { return a + b } func({a: 1, b: 2}) // 3 func({a: 1}) // 6 func({b: 2}) // 5 func({}) // 8 func() // 8 另一種情況是在函式傳入參數的預設值中給了另一套預設值,這只會在func()時發揮它的作用: function func({a = 3, b = 5} = {a: 7, b: 11}) { return a + b } func({a: 1, b: 2}) // 3 func({a: 1}) // 6 func({b: 2}) // 5 func({}) // 8 func() // 18 你可以觀察一下,當對某個變數賦值時你給他null或void 0,到底是用預設值還是沒有值,這個範例的g()函式是個對照組: function func({a = 1, b = 2} = {a: 1, b: 2}) { return a + b } func({a: 3, b: 5}) // 8 func({a: 3}) // 5 func({b: 5}) // 6 func({a: null}) // 2 func({b: null}) // 1 func({a: void 0}) // 3 func({b: void 0}) // 3 func({}) // 3 func() // 3 function g(a = 1, b = 2) { return a + b } g(3, 5) // 8 g(3) // 5 g(5) // 7 g(void 0, 5) // 6 g(null, 5) // 5 g() // 3 註:所以在函式傳入參數中作解構賦值時,給定null值時會導致預設值無用,請記住這一點。當數字運算時,null相當於0。 實例應用 迭代物件中的屬性值 這個範例用了for...of語法。出自Destructuring assignment: const people = [ { name: 'Mike Smith', family: { mother: 'Jane Smith', father: 'Harry Smith', sister: 'Samantha Smith' }, age: 35 }, { name: 'Tom Jones', family: { mother: 'Norah Jones', father: 'Richard Jones', brother: 'Howard Jones' }, age: 25 } ]; for (let {name: n, family: { father: f } } of people) { console.log('Name: ' + n + ', Father: ' + f) } // "Name: Mike Smith, Father: Harry Smith" // "Name: Tom Jones, Father: Richard Jones" 結合預設值與其餘參數 這個範例混用了一些ES6的語法,出自Several demos and usages for ES6 destructuring.: // 結合其他ES6特性 const ajax = function ({ url = 'localhost', port: p = 80}, ...data) { console.log('Url:', url, 'Port:', p, 'Rest:', data) } ajax({ url: 'someHost' }, 'additional', 'data', 'hello') // => Url: someHost Port: 80 Rest: [ 'additional', 'data', 'hello' ] ajax({ }, 'additional', 'data', 'hello') // => Url: localhost Port: 80 Rest: [ 'additional', 'data', 'hello' ] _.pluck 這個例子相當於Underscore.js函式庫中的_.pluck,把深層的屬性值往上拉出來。 var users = [ { user: "Name1" }, { user: "Name2" }, { user: "Name2" }, { user: "Name3" } ] var names = users.map( ({ user }) => user ) console.log(names) // => [ 'Name1', 'Name2', 'Name2', 'Name3' ] React Native的解構賦值 這個是React Native的一個教學,裡面有用了解構賦值的語法。出自React Native Tutorial: Building Apps with JavaScript: var React = require('react-native') var { StyleSheet, Text, TextInput, View, TouchableHighlight, ActivityIndicatorIOS, Image, Component } = React **loops** ![](https://i.imgur.com/k7OKl6v.png) **變數互換** ![](https://i.imgur.com/inlgH5X.png) **正規表達式** ![](https://i.imgur.com/0sUY5eZ.png) ## ES6 的map https://ithelp.ithome.com.tw/articles/10191607 類似 物件 但key只能string 所以有map 並且限定 key=>value 只能唯一 且沒有順序 Map Map跟物件非常相近,當我們需要鍵值時常會用到物件 但物件有幾個缺點 物件原型特性,可能使我們對應到不需要的東西 沒有簡單的方法查詢一個物件裡有多找東西 鍵必須是字串或符號,無法將物件設為鍵 物件無法保證特性順序 Map能夠解決這些缺點,尤其是能夠將物件設為鍵的特性更是好用 ## ES6 的 set https://ithelp.ithome.com.tw/articles/10191607 Set類似Array, 但其中元素的值都會是唯一不重複的 Set甚至可以搭配Array使用,例如將Array的值unique ## 浮點數問題 以後使用小數點要注意 最好要判斷一下 ![](https://i.imgur.com/Z7G9LO1.png) ## 檢查程式碼速度 console.time console.timeEnd去判斷 ![](https://i.imgur.com/YgwXagR.png) ## 預設值用 ??去取代 || 不能解決0的問題 0是false的意思 所以用 ??解決預設 ![](https://i.imgur.com/9zw0htz.png) ![](https://i.imgur.com/F7skBqR.png) ## 超實用 判斷這個存不存在存在就往下執行 大概像 a?.b 意思a存在就執行a.b 不存在就undefind 巢狀判斷簡短 整個程式碼長這個 ![](https://i.imgur.com/zOuIpkD.png) 原本 ![](https://i.imgur.com/zOuIpkD.png) 之後 用?去判斷前面有沒有 ![](https://i.imgur.com/4XKlOtM.png) 物件取array 注意要 .[]取 ![](https://i.imgur.com/k8LPCQi.png) ## 「傳值」或「傳址」? https://ithelp.ithome.com.tw/articles/10191057 所以我說那個 JavaScript 是「傳值」或「傳址」呢? 在大多數的情況下,基本型別是「傳值」,而物件型別會是「傳址」的方式,但凡事都有例外。 我們來看看下面這個例子: ``` var coin1 = { value: 10 }; function changeValue(obj) { obj = { value: 123 }; } changeValue(coin1); console.log(coin1); // ? ``` 猜猜看,經過` changeValue(coin1)` 操作後的 coin1 會是什麼? 答案仍是` { value: 10 }` 。 剛剛說過,物件型別會是「傳址」的方式來更新資料,那應該會是 `{ value: 123 } `才對,為什麼依然不變? 事實上,JavaScript 不屬於單純的傳值或傳址。 更準確一點來說,JavaScript 應該屬於透過 pass by sharing (還沒找到合適的中文翻譯) 來傳遞資料。 「傳值」或「傳址」對大多數的開發者來說應該都不陌生,那麼「pass by sharing」又是什麼呢? Pass by sharing 「Pass by sharing」的特點在於,當 function 的參數,如 function changeValue(obj){ ... } 中的 obj 被重新賦值的時候,外部變數的內容是不會被影響的。 ``` var coin1 = { value: 10 }; function changeValue(obj) { obj = { value: 123 }; } changeValue(coin1); console.log(coin1); // 此時 coin1 仍是 { value: 10 } ``` 如果不是重新賦值的情況,則又會回到大家所熟悉的狀況: ``` var coin1 = { value: 10 }; function changeValue(obj) { // 僅更新 obj.value,並未重新賦值 obj.value = 123; } changeValue(coin1); console.log(coin1); // 此時 coin1 則會變成 { value: 123 } ``` ## ES6 縮寫 https://wcc723.github.io/javascript/2017/12/23/javascript-short-hand/ 有宣告的變數 帶入 物件 解決重複問題 ![](https://i.imgur.com/yYGoPQx.png) 只要寫一次 會把宣告的key value自動拿來用 ![](https://i.imgur.com/sIp9JNM.png) **物件縮寫** function 這個詞彙如果使用在物件內,也可以省略 :function,省略後的語意是沒有變化的,並沒有轉而使用箭頭函式。 **變數作為物件屬性** 過去變數會使用 xxx. 來定義,而前者本身就是一個字串,無法再轉為變數使用,現在可以直接在宣告變數時使用 [],在 [] 內則是變數,當然也可以搭配 Template String 使用。 ``` let prop = 'Ming'; let value = '小明'; let teamMember = { [prop]: value, [`${prop}_invert`]: value.split("").reverse().join("") } console.log(teamMember); // { Ming: "小明", Ming_invert: "明小" } ```** ## 非常非常重要 script引入時間 ![](https://i.imgur.com/tGxsNvF.png) head加上defer 優點 跑到需要的會去執行 比較好 body下面 缺點 到最下面才會讀取 ``` ## 对象扩展 链判断操作符(?.):是否存在对象属性(不存在返回undefined且不再往下执行) 对象属性:obj?.prop、obj?.[expr] 函数调用:func?.(...args) 空判断操作符(??):是否值为undefined或null,是则使用默认值 作者:JowayYoung 链接:https://juejin.cn/post/6844903959283367950 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ## Even https://ithelp.ithome.com.tw/articles/10192015 **阻擋事件冒泡傳遞 event.stopPropagation()** ``` <label class="lbl"> Label <input type="checkbox" name="chkbox"> </label> ``` label會log兩次 所以要讓它裡面的ckeckbox不能冒泡 當然用this就解決了,這是範例 ``` // label var lbl = document.querySelector('.lbl'); // chkbox var chkbox = document.querySelector('#chkbox'); lbl.addEventListener('click', function (e) { console.log('lbl click'); }, false); // 阻擋 chkbox 的事件冒泡 chkbox.addEventListener('click', function (e) { e.stopPropagation(); }, false); ``` e.target 其實是「觸發事件的元素」,而 this 指的是「觸發事件的目標」元素,也就是 event.currentTarget。 當然,如果在不考慮事件傳遞的情況下,this 實質上就等同於 e.target 了。 ## 事件 https://ithelp.ithome.com.tw/articles/10192175 **load 事件**: 註冊在 window 物件上,指的是網頁資源 (包括CSS、JS、圖片等) 全數載入完畢後觸發。 如果是 img 元素的 load 事件,則表示是此圖片載入完畢後觸發。 **unload** 、 **beforeunload** 事件: 與 load 事件相反,unload 與 beforeunload 事件分別會在離開頁面或重新整理時觸發,而 beforeunload 會跳出對話框詢問使用者是否要離開目前頁面。 **error** 事件: error 事件會在 document 或是圖片載入錯誤時觸發。 值得一提的是,由於維護性的考量,大多事件的註冊我會強烈建議使用「非侵入式 JavaScript」的寫法,另外寫在` <script>` 標記,只有 error 事件最適合以 on-event handler 的寫法來處理: `<img src="image.jpg" onerror="this.src='default.jpg'">` **注音搜尋事件** 請看文章有點難 ## 涵式深入 https://ithelp.ithome.com.tw/articles/10192368 涵式是物件 涵式參數太多情況請用物件去放 不然太多一個一個要對照去填參數 ex `addPerson('Kuro', 'Hsu', '0987654321', 'kurotanshi@gmail.com', 'male', null, 'Taipei City');` 用這種 ``` var people = { firstName: 'Kuro', lastName: 'Hsu', phone: '0987654321', email: 'kurotanshi@gmail.com', gender: 'male', address: 'Taipei City' }; // 最後把 people 物件作為參數傳入 addPerson addPerson(people); ``` **注意 參數傳進去,有更動的話不會影響到外面的,他是會複製一份的概念** **但在使用 function 傳遞參數時,要小心 「Pass by sharing」帶來的誤解(像上面的情況)(不知道pass by sharing去看上面)。** ## callback 可看布魯斯 和 https://ithelp.ithome.com.tw/articles/10192739 主要是涵式再呼叫另外一個涵式用參數的情況呼叫 這樣會波動拳 ## this * this 是 JavaScript 的一個關鍵字。 * this 是 function 執行時,自動生成的一個內部物件。 * 隨著 function 執行場合的不同,this 所指向的值,也會有所不同。 * 在大多數的情況下, this 代表的就是呼叫 function 的物件 (Owner Object of the function)。 ### .bind() ``` var obj = { x: 123 }; var func = function () { console.log(this.x); }; func(); // undefined func.bind(obj)(); // 123 ``` **這裡你可以想像成某個 function 在執行的時候,「暫時」把它掛在某個物件下,以便透過 this 去取得該物件的 Context。** ### .call() 與 .apply() 基本上 .call() 或是 .apply() 都是去呼叫執行這個 function ,並將這個 function 的 context 替換成第一個參數帶入的物件。 換句話說,就是強制指定某個物件作為該 function 執行時的 this。 **差別** ``` function func( arg1, arg2, ... ){ // do something } func.call( context, arg1, arg2, ... ); func.apply( context, [ arg1, arg2, ... ]); ``` **.call() 傳入參數的方式是由「逗點」隔開,而 .apply() 則是傳入整個陣列作為參數,除此之外沒有明顯的差別。** **總結** * 這個 function 的呼叫,是透過 new 進行的嗎? 如果是,那 this 就是被建構出來的物件。 * 這個 function 是以 .call() 或 .apply() 的方式呼叫的嗎? 或是 function 透過 .bind() 指定? 如果是,那 this 就是被指定的物件。 * 這個 function 被呼叫時,是否存在於某個物件? 如果是,那 this 就是那個物件。 * 如果沒有滿足以上條件,則此 function 裡的 this 就一定是全域物件,在嚴格模式下則是 undefined。 ###### tags: `javaScript`