# javascript learning note ## [First book(javascript-start-from-es6)](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/) * falsy指會轉換成false的資料類型的值 * 0 * -0 * null * NaN * undefined * 空白字串('') * false * || 除了OR之外的用法 ```javascript= console.log('foo' || 'bar') // print 'foo' console.log(false || 'bar') // print 'bar' ``` * 需要注意0,-0屬於falsy所以也會當成false * undefine神奇的特性 ```javascript= let a console.log(a) //undefined console.log(typeof a) //undefined console.log(typeof b) //undefined //注意,此行會因錯誤中斷之後的程式碼 console.log(b) //Error: b is not defined ``` * 儘量使用空值(null)來進行運算後判斷,而未定義(undefined)就留給JavaScript系統用。 * 如果是單純的判斷某個值是否存在,然後設定它為預設值,運算符(||)是比三元運算符更好的方法 ```javascript= //相當於 const foo = a ? a : b const foo = a || b ``` * 抽象相等比較演算法((==)與(!=)符號的比較)的規則如下 * Type(x)與Type(y)的類型相同時,則進行嚴格相等比較 * 數字與字串比較時,字串會自動轉成數字再進行比較 * 其中有布林值時,true轉為1,==false轉為+0== * 當物件與數字或字串比較時,物件會先轉為預設值以及轉成原始資料類型的值,再作比較 * switch的case語句中的值會以一致相等運算(===)來比較,所以資料類型也要完全相等才行 * for...in 語句主要是搭配物件類型,是任意順序,不要使用在陣列資料類型 * for...of 用於可迭代物件上,取出其中的值,可迭代物件包含陣列、字串、Map物件、Set物件等等,如: ```javascript= //用於陣列 let aArray = [1, 2, 3] for (let value of aArray) { console.log(value) } //用於字串 let aString = "abcd"; for (let value of aString) { console.log(value); } ``` * 在迴圈中區塊語句裡,定義變數/常數是個壞習慣,應該是要定義在迴圈外面或for迴圈的第一個表達式中 * 用於判斷情況的運算,應該要儘可能避免,先作完運算在迴圈外部或或for迴圈的第一個表達式中,效率可以比較好些,用於判斷情況的運算,應該要儘可能避免,先作完運算在迴圈外部或或for迴圈的第一個表達式中,效率可以比較好些,如: ```javascript= //較差的寫法 for (let i=0 ; i < aArray.length ; i++) { //statment } //較好的寫法 for (let i=0, len = aArray.length ; i < len; i++) { //statment } ``` * 函式的基本語法結構如下: ```javascript= //匿名函式 function() {} //有名稱的函式 function foo() {} ``` * 定義方式: ```javascript= //函式定義 - 使用有名稱的函式 function sum(a, b){ return a+b } //函式表達式 - 常數指定為匿名函式 const sum = function(a, b) { return a+b } //也等於 in es6 const sum = (a, b) => a + b ``` * 在ES6中加入了函式傳入參數的預設值指定語法,現在可以直接在傳入參數時就定義這些參數的預設值,這個作法是建議的用法: ```javascript= const link = function (point = 10, url = 'http://google.com') { ... } ``` * 當要定義傳入參數將會是個函式時,通常會用fn或func作為傳入參數名稱 * ~~unnamed arguments,實際上對於傳入的參數值是有一個隱藏在背後的物件,名稱為arguments,它會對傳入參數實際值進行保存,可以直接在函式內的語句中直接取用,如:~~ ```javascript= function sum() { return arguments[0]+arguments[1] } console.log(sum(1, 100)) ``` * 這東西很詭異 * 在ES6中加入了其餘參數(rest parameters)的新作法,它使用省略符號(ellipsis)(...)加在傳入參數名稱前面,其餘參數的傳入值是一個標準的陣列值,以下是一個範例: ```javascript= 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 ``` * 上面程式碼更簡潔得方法 ```javascript= function sum(...value) { return value.reduce((prev, curr) => prev + curr ) } ``` * 一個函式只能有一個其餘參數名稱 * 內部函式可以獲取到外部函式所包含的環境值,而內部函式又可以成為外部函式的回傳值,所以當內部函式接收到外部函式的環境值,又被回傳出去,內部函式間接變成一種可以讓函式對外曝露包含在函式內部環境值的溝通管道,這種結構稱之為"閉包(closure)" * var是函式作用範圍(function scope),let與const是區塊作用範圍(block scope),if、for、switch、while等等也可以界定作用範圍 * Immediately-invoked function expressions(IIFE) ```javascript= (function () { … })() (function () { … }()) ``` * IIFE在執行環境一讀取到定義時,就會立即執行,而不像一般的函式需要呼叫才會執行,這也是它的名稱的由來 - 立即呼叫,唯一的例外當然是它如果在一個函式的內部中,那只有呼叫到那個函式才會執行 * 可形成Module Pattern(模組模式) * 不過,模組模式的結構存在於JavaScript已有很久一段時間,算是前一代主要的設計模式與程式碼組織方式 * 陣列定義推薦使用 ```javascrpt= const aArray = [] ``` * Array 可以儲存多種資料類型 * 拷貝(copy)陣列 * ```javascript= const aArray = [1, 2, 3] const bArray = aArray //這裡的aArray跟bArray是指向同一個參照(reference) ``` * es6後推薦使用 ```javascript= const aArray = [1, 2, 3] const copyArray = [...aArray] //它也可以用來組合陣列 const aArray = [1, 2, 3] const bArray = [5, 6, ...aArray, 8, 9] ``` * 判別是否為陣列使用isArray(一般使用) * es6加入forEach ```javascript= const aArray = [1, 2, 3, 4, 5] aArray.forEach(function(value, index, array){ // 對陣列元素作某些事 console.log(index, value) }) ``` * forEach的回調函式(callback)共可使用三個參數: * value 目前成員的值 * index 目前成員的索引 * array 要進行尋遍的陣列 * forEach雖然可以與for迴圈語句相互替代,但他們兩個設計原理不同,也有一些明顯的差異,在選擇時你需要考慮這些。不同之處在於以下幾點: * forEach無法提早結束或中斷 * forEach被呼叫時,this會傳遞到回調函式(callback)裡 forEach方法具有副作用 * es6還新增了 map(映射) 相比 forEach 會更好用 ```javascript= var aArray = [1, 2, 3, 4]; var newArray = aArray.map(function (value, index, array) { return value + 100 }) //原陣列不會被修改 console.log(aArray) // [1, 2, 3, 4] console.log(newArray) // [101, 102, 103, 104] ``` * 與forEach不同的地方是它會回傳一個新的陣列,也因為它可以回傳新陣列,所以map(映射)可以用於串接(chain)語法結構 * 在ES6標準時,現在的JavaScript中的物件導向特性,並不是真的是以類別為基礎(class-based)的,這是骨子裡還是以原型為基礎(prototype-based)的物件導向特性語法糖,寫法: ```javascript class Player { constructor(fullName, age, gender, hairColor) { this.fullName = fullName this.age = age this.gender = gender this.hairColor = hairColor } toString() { return 'Name: '+this.fullName+', Age:'+this.age } } const inori = new Player('Inori', 16, 'girl', 'pink') console.log(inori.toString()) console.log(inori.fullName) ``` * 類別名稱命名時要使用大駝峰(ClassName)的寫法 * this變數是JavaScript的一個特性,它是隱藏的內部變數之一,當函式呼叫或物件實體化時,都會以這個this變數的指向對象,作為執行期間的依據 * 比較簡單常見的區分方式,就是在私有成員(或方法)的名稱前面,加上下底線符號(_)前綴字,用於區分這是私有的(private)成員 * 在類別定義中可以使用get與set關鍵字,作為類別方法的修飾字如: ```javascript class Option { constructor(key, value, autoLoad = false) { if (typeof key != 'undefined') { this['_' + key] = value; } this.autoLoad = autoLoad; } get color() { if (this._color !== undefined) { return this._color } else { return 'no color prop' } } set color(value) { this._color = value } } const op1 = new Option('color', 'red') op1.color = 'yellow' const op2 = new Option('action', 'run') op2.color = 'yellow' //no color prop ``` * 靜態(Static)成員指的是屬於類別的屬性或方法,也就是不論是哪一個被實體化的物件,都共享這個方法或屬性,JavaScript中只有靜態方法,沒有靜態屬性,使用的是static作為方法的修飾字詞(es7好像有) * extends關鍵字可以作類別的繼承,而在建構式中會多呼叫一個super()方法,用於執行上層父母類別的建構式之用。super也可以用於指向上層父母類別,呼叫其中的方法或存取屬性 ```javascript= class Point { constructor(x, y) { this.x = x this.y = y } toString() { return '(' + this.x + ', ' + this.y + ')' } } class ColorPoint extends Point { constructor(x, y, color) { super(x, y) this.color = color } toString() { return super.toString() + ' in ' + this.color } } ``` * 繼承的子類別中的建構式,super()需要放在第一行,這是標準的呼叫方式 * 繼承的子類別中的屬性與方法,都會覆蓋掉原有的在父母類別中的同名稱屬性或方法,要區為不同的屬性或方法要用super關鍵字來存取父母類別中的屬性或方法 * (es6)物件的拷貝推薦使用Object.assign()它除了拷貝之外,也可以作物件的合併,合併時成員有重覆名稱以愈後面的物件中的成員為主進行覆蓋 ```javascript= //物件拷貝 const aObj = { a: 1, b: 'Test' } const copy = Object.assign({}, aObj) console.log(copy) // {a: 1, b: "Test"} //物件合併 const bObj = { a: 2, c: 'boo' } const newObj = Object.assign(aObj, bObj) console.log(newObj) //{a: 2, b: "Test", c: "boo"} ``` * 直接存取物件中不存在的屬性,會直接回傳undefined時,這是最直接的判斷物件屬性是否存在的方式,也是最快的方式。不過它有一個缺點,就是當這個屬性本身就是undefined時,這個判斷方法就失效了,如果你本來要的值本來就絕對不是undefined,所以可以這樣用 ```javascript= //判斷鍵是否存在 typeof obj.key !== 'undefined' //判斷值是否存在 obj.key !== undefined obj['key'] !== undefined ``` * Error物件是JavaScript中內建的用於處理錯誤的物件,其中共分成6個種類,也就延伸出來6種不同的物件,每個不同的種類名稱即是這些物件的name屬性,可以用於不同的的錯誤情況,以下為這些種類的說明: * ~~EvalError: 配合eval()方法使用所產生的錯誤,但因eval是一個不建議使用的全域方法,幾乎不會用到。~~ * ~~SyntaxError: 這個也是配合eval()方法使用所產生的錯誤,幾乎不會用到。其他的語法錯誤將由瀏覽器直接回報。~~ * RangeError: 超出範圍所產生的的錯誤 * ReferenceError: 參照錯誤 * TypeError: 資料型態錯誤 * URIError: 配合encodeURI()或decodeURI()方法使用的錯誤 * 自訂的Error物件可以使用Error物件的建構式實體化,它只需要一個訊息字串作為傳入參數,==Error物件也是一個必定要使用new運算符進行實體化的內建物件==,通常會配合throw語句使用,例如以下的範例: ```javascript try { throw new Error('Whoops!') } catch (e) { console.log(e.name + ': ' + e.message) } ``` * try...catch語句也是一種控制流程的語句。意思是如果所有位於try區塊中的語句都沒問題的話,就執行try其中的語句,當發生例外時會將控制權轉到catch區塊,執行catch區塊中的語句。catch可以在例外發生時,自動捕捉所有的例外情況,這在程式撰寫時相當方便。簡單的範例如下: ```javascript try{ document.getElementById('test').innerHTML = 'test' } catch(e) { console.log(e) //TypeError: Cannot set property 'innerHTML' of null(…) } ``` * try...catch語句最後還可以額外加上finally區塊,它是不論如何都會執行的語句區塊,例如你在try語句中開啟了一個檔案要進行處理,不論有沒有發生例外,最後需要把這個檔案進行關閉,這就是寫在finally區塊中 * 在伺服器端的Node.js上,只能在必要的時候才會使用。在瀏覽器上的應用程式則是不需要考量那麼多。有幾個明顯的原因: * 它是高消費的語句: 在有重大效能考量或迴圈的語句,不建議使用try...catch語句 * 它是同步的語句: 如果是重度使用callback(回調)或promise樣式的異步程式,不需要使用它。==此外,Promise語句可以完全取代它,而且是異步的語句== * 不需要: 如果可以使用if...else的簡單程式碼中,將不會看到它的存在。另外,在一些對外取得資源的功能例如Ajax,我們一般都會使用額外函式庫來協助處理,這些函式庫都有考慮到比你想得到還完整的各種例外情況,所以也不需要由你親自來作例外處理 * 通常會搭配會在例外發生時,直接丟出例外的方法上,這些方法有可能是JavaScript內建的,也可能是程式設計師自己設計的。最常見的是JSON.parse這個內建的用於解析JSON格式字串為對應物件的方法 * 用於使用者輸入檢查的情況,這也是會用到try...catch語句的常見情況,這種通常稱之為錯誤中心(Error Center)的方式,用於集中很多不同的例外為一個語句中 * throw語句會"丟出"程式設計師自訂的例外,throw語句會中斷執行,所以在下面的語句並不會再被執行,然後把控制權轉交給呼叫堆疊(call stack)中第一個的catch區塊,如果沒有catch區塊則會立即停止應用程式。 * 每個函式中都會有prototype屬性,指向一個prototype物件。例如MyFunc函式的prototype屬性,會指向對應的MyFunc.prototype物件 * 每個函式的prototype物件,會有一個constructor屬性,指回到這個函式。例如MyFunc.prototype物件的constructor屬性,會指向MyFunc函式。 * 每個物件都有一個__proto__內部屬性,指向它的繼承而來的原型prototype物件 * 由__proto__指向連接起來的結構,稱之為原型鏈(prototype chain),也就是原型繼承的整個連接結構 * 原型可以很容易的擴充屬性與方法,而且是動態的,可以在物件實體後繼續擴充其中的成員,這也是JavaScript中常用來擴充內建物件的方式。==當使用Player.prototype來進行擴充時,這些擴充出來的屬性與方法,是所有物件共享的== * 物件的建立有以下這三種方式: ```javascript= //物件字面定義,相等於new Object() const newObject = {} //使用Object.create方法 const newObject = Object.create( proto ) //ES6類別定義,或是建構函式。通常稱為建構式樣式。 const newObject = new ConstructorFunc() const newObject = new ClassName() ``` * 物件字面定義語法有一些問題,它只會有一個物件實體。它沒辦法直接複製出其他的物件實體,所以如果是要指"可建立多個物件"的語法,這個並不是可以這樣使用的,它需要寫成一個像下面這樣的函式(better) ```javascript= function PlayerFactory(name, age) { return { name : name, age: age, toString : function() { return 'Name: '+ this.name +' Age:'+ this.age } } } ``` * call(呼叫): 以個別提供的this值與傳入參數值來呼叫函式 * bind(綁定): 建立一個新的函式,這個新函式在呼叫時,會以提供的this值與一連串的傳入參數值來進行呼叫(從左到右) ```javascript= function funcA(param1, param2){ console.log(this, param1, param2) } const objB = { a: 1, b: 2 } funcA() //undefined undefined undefined const funcB = funcA.bind(objB, objB.a) funcB() //Object {a: 1, b: 2} 1 undefined funcB(objB.b) //Object {a: 1, b: 2} 1 2 ``` * "函式定義是定義,呼叫是呼叫",實際上在物件定義的所謂方法,你可以把它當作,只是讓程式設計師方便集中管理的函式定義而已 ```javascript= const objA = {a:1} const objB = { a: 10, methodB(){ console.log(this) } } const funcA = objB.methodB objB.methodB() //objB funcA() //undefined,也就是全域物件window objB.methodB.call(objA) //objA ``` * this值會遵守ECMAScript標準中所定義的一些基本規則,大概摘要如下,函式中的this值按順序一一檢視,只會符合其一種結果(if...else語句): 1. 使用strict code(嚴格模式程式碼)時,直接設定為call方法裡面的thisArg(this參數值) 2. 當thisArg(this參數值)是null或undefined時,會綁定為全域(global)物件 3. 當thisArg(this參數值)的類型不是物件類型時,會綁定為轉為物件類型的值 4. 都不是以上的情況時,綁定為thisArg(this參數值) * 箭頭函式(Arrow Function)除了作為以函式表達式(FE)的方式來宣告函式之外,它還有一個特別的作用,即是可以作綁定(bind)this值的功能。這特性在一些應用情況下非常有用,可以讓程式碼有更高的可閱讀性,例如以上一節的例子中,有使用到bind方法的,都可以用箭頭函式來取代,以下為改寫過的範例,你可以比對一下: ```javascript= const obj = {a:1} function func(){ setTimeout( () => { console.log(this) }, 2000) } func.call(obj) ``` * 並非所有的使用callbacks(回調)函式的API都是異步執行的,但CPS的確是一種可以確保異步回調執行流程的風格要讓開發者自訂的callbacks(回調)的執行轉變為異步,有以下幾種方式: * 使用計時器(timer)函式: `setTimeout`, `setInterval` * 特殊的函式: `nextTick`, `setImmediate` * 執行I/O: 監聽網路、資料庫查詢或讀寫外部資源 * 訂閱事件 * 使用了計時器APIsetTimeout會把傳入的回調函式進行異步執行,也就是先移到工作佇列中,等執行主執行緒的呼叫堆疊空了,在某個時間回到主執行緒再執行。所以即使它的時間設定為0秒,裡面的回調函式並不是立即執行,而是會暫緩(延時)執行的一種回調函式,一般稱為異步回調函式 * 閉包是完全複製(copy)了這些值?還是參照(refer)指向這些值而已?==答案是"參照(refer)而非複製"== * js是單執行緒執行,所以異步很重要 * 在W3C標準的定義中,this會相等於event.currentTarget,也就是說this永遠會指向目前的事件目標對象,就像地震的傳播一樣,地震中心點先震完了,開始傳播到別的地區,event.currentTarget會跟著改變 * 但不管是事件冒泡(bubbling)或事件捕捉(capturing),event.target永遠指向觸發事件的那個元素,也就是地震的發生源 * 最容易搞混的是下面這個例子,有花括號({})與沒有是兩碼子事: ```javascript= const funcA = x => x + 1 const funcB = x => { x + 1 } funcA(1) //2 funcB(1) //undefined ``` * 當沒用花括號({})時,代表會自動加return,也只能在一行的語句的時候使用。使用花括號({})則是可以加入多行的語句,不過return不會自動加 * 不可使用箭頭函式的情況 * 使用字面文字定義物件時,其中的方法 ```javascript= const calculate = { array: [1, 2, 3], sum: () => { return this.array.reduce((result, item) => result + item) } } //TypeError: Cannot read property 'array' of undefined calculate.sum() ``` * 物件的prototype屬性中定義方法時 ```javascript= function MyCat(name) { this.catName = name } MyCat.prototype.sayCatName = () => { return this.catName } cat = new MyCat('Mew') // ReferenceError: cat is not defined cat.sayCatName() ``` * DOM事件處理的監聽者(事件處理函式) ```javascript= const button = document.getElementById('myButton') button.addEventListener('click', () => { this.innerHTML = 'Clicked button' }) //TypeError: Cannot set property 'innerHTML' of undefined ``` * 建構函式 ```javascript= const Message = (text) => { this.text = text; } // Throws "TypeError: Message is not a constructor" const helloMessage = new Message('Hello World!'); ``` * 函式物件中的call、apply、bind三個方法,無法"覆蓋"箭頭函式中的this值。 * 箭頭函式無法用於建構式(constructor),使用new會產生錯誤。 * 展開運算符(Spread Operator)是把一個陣列展開(expand)成個別值,這個運算符後面必定接著一個陣列。最常見的是用來組合(連接)陣列,對應的陣列方法是concat,以下是一個簡單的範例: ```javascipt= const params = [ "hello", true, 7 ] const other = [ 1, 2, ...params ] // [ 1, 2, "hello", true, 7 ] ``` * 展開運算符可以作陣列的淺拷貝,當然陣列的淺拷貝有很多種方式,這是新的一種語法,也是目前語法上最簡單的一種 * 展開運算符還有一個特別的功能,就是把可迭代(iterable)或與陣列相似(Array-like)的物件轉變為陣列,在JavaScript語言中內建的可迭代(iterable)物件有String、Array、TypedArray、Map與Set物件,而與陣列相似(Array-like)的物件指的是函式中的隱藏物件"arguments"。下面的範例是轉變字串為字元陣列: ```javascript= const aString = "foo" const chars = [ ...aString ] // [ "f", "o", "o" ] ``` * 其餘參數代表是將"不確定的傳入參數值們"在函式中轉變成為一個陣列來進行運算。例如下面這個加總的範例: ```javascript= function sum(…numbers) { const result = 0 numbers.forEach(function (number) { result += number }) return result } sum(1) // 1 sum(1, 2, 3, 4, 5) // 15 ``` * ES7+標準的其餘屬性(Rest Properties)與展開屬性(Spread Properties),可以用在物件上: ```javascript= // 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 } ``` * fetch()方法是一個位於全域window物件的方法,它會被用來執行送出Request(要求)的工作,如果成功得到回應的話,它會回傳一個帶有Response(回應)物件的已實現Promise物件。fetch()的語法結構完全是Promise的語法,十分清楚容易閱讀,也很類似於jQuery的語法: ```javascript= fetch('http://abc.com/', {method: 'get'}) .then(function(response) { //處理 response }).catch(function(err) { // Error :( }) ``` * 要注意的是fetch在只要在伺服器有回應的情況下,都會回傳已實現的Promise物件狀態(只要不是網路連線問題,或是伺服器失連等等),在這其中也會包含狀態碼為錯誤碼(404, 500...)的情況,所以在使用時你還需要加一下檢查 * 從陣列解構賦值,基本用法: ```javascript= //基本用法 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 ``` * 從物件解構賦值: ```javascript= // 基本用法 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} ``` * 這是錯誤的示範: ```javascript= // 錯誤的示範: let a, b { a, b } = {a: 1, b: 2} ``` * 正確寫法: ```javascript= let a, b ({ a, b } = {a: 1, b: 2}) //a=1, b=2 ``` * 在函式傳入參數定義中使用: ```javascript= function func({a, b}) { return a + b } func({a: 1, b: 2}) // 3 ``` * 在函式傳入參數中作解構賦值時,給定null值時會導致預設值無用,請記住這一點。當數字運算時,null相當於0。