2017年1月25日

[筆記] JavaScript ES6 中的模版字符串(template literals)和標籤模版(tagged template)


在 ES6 中,我們多了一個非常好用的模版字符串(template literal),如果你會在 JS 中「放入 HTML 的內容」、或者有「很長的字串包含換行」、又或者會有「字串連結變數」這樣的需求,模版字符串會是非常方便的作法。

另外,在 ES6 中可以將模版字符串和函式結合使用,形成一個標籤模版(tagged template),可以以此過濾 HTML 字串,避免使用者輸入惡意內容。

模版字符串(template literal)的基本應用


模版字符串的使用非常簡單,就是使用反引號" ` "(鍵盤左上角的~),舉例來說,如果我們會在 JS 的字串中放入 HTML 內容,在過去我們可能需要這樣寫:


let component_es5 = '<header>\n'+
'<div class="banner">\n'+
'<img src="img1.jpg"\n'+
'</div>\n'+
'</header>'


這麼寫相當麻煩,而且不易閱讀。而在 ES6 中我們可以用反引號快速的解決這樣的狀況:


let component_es6 = `
<header>
    <div class='banner'>
        <img src="img1.jpg>
    </div>
</header>
`


這樣的作法,會直接輸出如下的結果:


也就是說,透過反引號包住的內容,會保留所有的換行和空行

在模版字符串中嵌入變數


另外,在模版字符串中,我們還可以透過 ${...} 這樣的方式,嵌入變量或任何的表達式:


let myName = "PJCHENder",
    numA = 4,
    numB = 7;

let content = `Hello, my name is ${myName}, my lucky number is ${2*(numA + numB)}`;

console.log(content);  // "Hello, my name is PJCHENder, my lucky number is 22"


我們可以看到透過 ${...},裡面我們不只可以放入變數,還可以放入表達式(例如${3+4})。

最後,如果在模版字符串中我們又會使用到反引號的話,這時候我們必須使用跳脫字元 \ 來處理,像是這樣:


var greeting = `\`Hello\` World!`;

console.log(greeting); // "`Hello` World!"


進階:標籤模版(tagged template)


接著我們可以來看一下模版字符串的進階用法,這樣的用法非常適合用在前端用來過濾使用者所輸入的訊息。讓我們先來看一下基本的用法。

標籤模版的使用就是直接在函式後面加上模版字符串,如果模版字符串中沒有使用 ${...} 代入任何的變數的話,那麼它其實就和一般將參數代入函式中差不多:


console.log `Hello EveryOne`;    //  Array  ["Hello EveryOne"]
console.log('Hello EveryOne');   //  String "Hello EveryOne"


但是如果在模版字符串中有 ${...} 的話,意義就相當不同:


let myName = "PJCHENder";
let myCountry = "Taiwan";

tag `<p> My name is ${myName} and my coutry is ${myCountry}</p>`;  //  使用標籤模版
tag(["<p> My name is ", " and my coutry is ", "</p>"], "PJCHENder", "Taiwan")  // 等同於上面這段


很大的差別在於若我們使用標籤模版(tagged template),也就是在函式後面直接代入模版字符串(`...`),它等於會先將模版字符串的內容切成多個參數,在放入函式中,切法就是把所有沒有被放在 ${...} 中的內容都組成一個陣列(就像上例中["<p> My name is ", " and my coutry is ", "</p>"]),當作這個函式的第一個參數;接著將 ${...}  裡面的內容,依次當做後面的第二個、第三個、...參數(看你使用了幾次${...} )。

因此,如果我們將 tag 這個 function 寫成這樣:

let myName = "PJCHENder";
let myCountry = "Taiwan";

tag `<p> My name is ${myName} and my coutry is ${myCountry}</p>`;
// tag(["<p> My name is ", " and my coutry is ", "</p>"], "PJCHENder", "Taiwan")  // 等同於上面這一段

function tag(template){
  console.log(template);
  console.log(arguments);
}


這個 function 做的事情很簡單,就是把 template 呼叫出來,還有把 arguments 呼叫出來(arguments 是 JS 中內建的關鍵字,若不清楚可參考:[筆記] 談談JavaScript中函式的參數(parameter),arguments和展開運算子(spread))。輸出的結果如下,其中上面是 template,下面是 arguments:


在下圖中下面的部分,我們可以看到它實際代入了三個參數,第一個參數是一個陣列,裡面包含所有除了${...} 內的內容,後面的兩個參數,則分別放入 ${...} 的內容作為參數。

結合其餘運算子(rest operator)做使用:因為 ${...}  的數量可能是不固定的,因此我們可以搭配其餘運算子(...),將這些參數組成陣列來使用(若不清楚其餘運算子,可參考:[筆記] JavaScript ES6 中的展開運算子(spread operator)和其餘運算子(rest operator)),像是這樣:


let myName = "PJCHENder";
let myCountry = "Taiwan";

tag `<p> My name is ${myName} and my coutry is ${myCountry}</p>`;
// tag(["<p> My name is ", " and my coutry is ", "</p>"], "PJCHENder", "Taiwan")  // 等同於上面這一段

function tag(template, ...values){
  console.log(template);  //  ["<p> My name is ", " and my coutry is ", "</p>"]
  console.log(values);  //  ["PJCHENder", "Taiwan"]
}


等於把不在${...}內的,和在${...},拆成兩個陣列來做使用。

這樣的標籤模版有一個好處,就是我們可以把使用者輸入的內容${...},和我們網頁原本的內容分開做處理,以達到過濾 HTML 字符串,防止用戶輸入惡意內容的效果:

截圖自:阮一峰-ECMAScript 6 入門

2017年1月24日

[筆記] JavaScript ES6 中的 for ... of(處理陣列的好幫手)


過去我們可以使用 for, while, do while, for...in 等內在的函式來處理資料,而在 ES6 中我們多了 for...of 這個簡易的用法來處理這些疊代型的資料(iterable objects),包含陣列、字串、map、set、等等...。

陣列中 for...of 的基本用法


for...of 的使用非常簡單,以陣列為例:


let arr = [10, 20, 30]

for(let value of arr){
  console.log(value);  // 10, 20, 30
}


只要用這樣的方式,就可以把陣列的值一個個取出,不用像過去寫一大串像是 for(let i = 0; i < arr.length; i++){...} 是不是方便許多呢

for...of 的其它用法


for...of 除了用在陣列之外,也可以用在其他的資料型態,像是字串、map、set 等等...,舉字串為例:


let string = "ES6";

for(let value of string){
  console.log(value);  // "E", "S", "6"
}


是不是非常方便呢?


2017年1月21日

[筆記] JS30系列:監聽按鍵事件及撥放音效(Day1)


此課程內容來自 Wes Bos ,文章內容為整理課程內容學習之筆記,如果直接看筆記的話可能會覺得有點跳,不適合直接當成單篇文章閱讀,因此建議可以先看過該堂課程內容後,需要複習的時候在回來看筆記。關於此課程的上課說明可參考 JavaScript30 課程說明

第一堂課完成的頁面會像這個網站所顯示的樣子,當按下畫面上的所對應的按鍵時會有對應的動畫效果和音效。

思考分析


1. 瀏覽器能夠辨認使用者在鍵盤上所按下的按鍵
2. 當鍵盤按下指定的按鍵後能夠觸發音效
3. 當鍵盤按下指定的按鍵後能夠觸發網頁上的元件產生相對應的變化
4. 網頁上元件的變化要能夠回覆成原本的狀態
5. 播放的音效要能夠停止

辨認使用者在鍵盤上所按下的按鍵


KeyCode:每一個按鍵其實都有對應的鍵碼(keycode),而我們可以透過使用者所按下的鍵碼來得知使用者是按了鍵盤上的哪一個按鈕。在 keycode.info 這個網站中,你就可以得到每一個按鍵所對應到的鍵碼。

keycode @ keycode.info

addEventListener:在 JS 中,我們要監聽事件的話通常會用到 addEventListener,在這裡也不例外,為了要監聽使用者按了那個按鍵,我們會寫這樣子:


// 監聽鍵盤按鍵事件,並回傳所按的按鍵為何
  window.addEventListener('keydown', function(e){
    console.log(e);
  });


這裡我們用的是 keydown 事件,後面可以帶函式 function(e){...}),函式的參數中會預設回傳一個屬於該事件引發後的物件,在這裡我們用 console.log 來看看它會回傳什麼事件。

假設我在網頁上點選 "A",這時候它會回傳一個物件長這樣,其中包含了像是我們按了哪個按鍵的屬性(key),也包含這個按鍵的鍵碼(keyCode)等等:


在這裡 keyCode 會是很重要的屬性,有了它我們就知道使用者按了鍵盤上的哪個鍵,然後我們針對是個按鍵提供網頁上相對應要產生的效果。

鍵盤按下指定按鈕後能夠觸發音效


在來你會看到課程的 HTML 文件中,不論是 button 或 audio 標籤都加入了 data-* attribute,這裡它用的是 data-key 像是這樣:


data-* attribute:簡單說明一下什麼是 data-* attribute,有時我們會需要使用到某些自定義的屬性,但是為了要避免大家在 HTML 中隨意的添加屬性,於是在 HTML5 中就多了 data-* attribte 這個屬性,其中的 * 就是一個可以自定義的名稱,例如:data-key='83' 或者是 data-item='1',關於 data-* attribute 屬性如果想要有更多的瞭解,可以參考([教學] 什麼是 HTML 5 中的資料屬性)。

有了 data attribute 我們就可以選擇到和按鍵所對應的音效,並使他播放。但我們要做的是要先選到和按鍵所對應的音效元素,這時候我們會需要用到 JavaScript 中的 querySelector

querySelector:這是 JS 中原生的 CSS 選擇器,裡面可以像 jQuery 一樣去選擇想要選擇到的元素,有一些要注意的地方像是,若找不到相對性的元素會回傳 null,否則會回傳第一個符合的元素,因此,若你是多個屬性具有相同的 class 名稱時,它只會選到第一個符合的元素。如果你是想要選擇多個具有相同 class 名稱的屬性時,就要用 querySelectorAll
瞭解更多關於 querySelectorquerySelectorAll

下面這段程式碼的意思,就是當我按了鍵盤上的按鍵後,幫我選擇相對應的 keyCode 元素,否則的話就會回傳 null 。


//  選擇鍵盤按鍵所對應到的網頁元素
window.addEventListener('keydown',function(e){
    const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
    console.log(audio);
})


在下面這段程式碼中還有一個可以留意的是在 querySelector( ) 裡面,使用了 ES6 中的模版語言,它是使用 ` 符號(鍵盤左上角的~)來將,在模版字串中如果有需要引入變數,則可以是用${ },來代入變數(若不清楚模版字符串的使用,可參考[筆記] JavaScript ES6 中的模版字符串和標籤模版)。

當我們按鍵按到了相對應 keyCode 時就可以選到該元素,否則就會回傳null


當我們能夠找到指定的元素時,我們就可以透過 JS 叫它播放,要讓它播放的方式很簡單,只要在找到的這個元素後面寫 .play() 就可以了。

另外,為了避免當我們沒有按到頁面上所指定的按鍵時,會回傳錯誤結果,我們多一行 if(!audio) return,意思是如果沒有按到指定的按鍵,則透過 return 來停止這個函式,像是這樣:


//  播放元素音效檔
window.addEventListener('keydown',function(e){
    const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
    if(!audio) return;  //  如果沒有按到對應的按鍵,則停止此函式
    audio.play();       //  播放元素的音效
})


做到這裡當你下所對應的按鍵時,就會發出音效了。但我們會碰到另一個問題,就是當我們快速連續點擊同一個按鍵時,原本我們預期音效應該會快速重複,但實際上卻沒有這樣的效果。

之所以會有這個問題,是因為一個音效檔的時間長度可能是 3 秒,當我們使用 .play( ) 來觸發音效時,在音效檔還沒撥到結束的時候,它不會再重複執行一次。

解決這個問題的方法,就是讓函式每次執行的時候,當讓音效檔的時間點回到一開始,這樣它才會重新播放,寫法是利用到 currentTime 這個屬性,它可以使用在 HTML 中 audio 或是 video 標籤(參考:W3Schools),寫法像是下面這樣:


window.addEventListener('keydown',function(e){
    const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
    if(!audio) return;      //  如果沒有按到對應的按鍵,則停止此函式
    audio.currentTime = 0;  //  讓每次回到音效檔的起始點
    audio.play();           //  播放元素的音效
})


到這裡如果沒問題的話,你按下對應的鍵應該就會發出對應的音效了。

鍵盤按下指定按鈕後能夠網頁動畫效果


接下來我們要做的,就是讓鍵盤按下指定的按鈕後,能夠讓網頁產生動畫效果,而這個動畫效果之所以會產生,是因為在指定的元素上,加了特定的 class 之後再移除所造成的。

所以和剛剛一樣,我們要先監聽使用者鍵盤按下的按鍵,接著取的他所按下按鍵所對應到的網頁元素:


//  監聽使用者按鍵事件,並取得對應的元素
window.addEventListener('keydown', function(e){
    const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
})


我們可以看到,在作者的 CSS 程式碼中,原本按鍵只套用 .key ,當我們按下指定的按鍵時,則會多套用 .playing 這個 class 來達到動態的效果。

另外,這裡也有用到 CSS 中的 transition 屬性,除了讓動畫的改變更滑順外,我們後面也會利用它的功能,來自動的拿掉 .playing 這個屬性。



classList 物件:接下來我們要做的就是讓按下按鍵之後,能夠在該元素上添加指定的 class 以產生動態的效果,可以利用 JS 中內建的 element.classList.add( ) 這個方法:


window.addEventListener('keydown', function(e){
    const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);    //  取得按鍵對應的元素
    key.classList.add('playing');       ///為該元素增加 class
})


現在當我們按下按鍵後,他就會自動加上 .playing 這個 class,進一步的問題是,要怎麼讓它自動移除這個 class ,否則這個動畫產生的效果不會消失。

transitionend event:這個 transitionend 事件主要是對應到 CSS 中 transition 的動畫效果,當這個 transition 效果執行結束的時候會引發事件。

所以我們能做的就是監控每一個按鍵的 transitionend event ,一旦我們偵測到這個動畫效果(transition)已經結束,那麼我們就可以把原本添加的 .playing 這個 class 拿掉。

querySelectorAll:為了監聽每一個按鍵的事件,這裡我們就會用到 querySelectorAll 這個方法,它會選出所有具有該 class 的元素,並回傳為一 NodeList

forEach( ):接著為了要重複的讀出這個 NodeList 的內容,我們會用到 forEach( ) 這個方法,forEach( ) 這個方法裡面可以放入 callback function,預設的參數包含該 NodeList 的屬性值和屬性名稱。因此我們可以寫出如下的語法:


const keys = document.querySelectorAll('.key'); //  選出所有具有 .key 的元素,回傳成陣列
keys.forEach((key) => {key.addEventListener('transitionend', removeTransition)});   //  利用 forEach 方法讀取 NodeList 並為每一個元素加上 transitionend 事件,事件執行時會促發 removeTransition 


接著我們就要來撰寫 removeTransition 的函式,這個 removeTransition 就是 addEventListener( ) 當中的函式,所以它一樣可以回傳一個屬於該事件的物件,這裡我們把它稱做 e:


let removeTransition = function(e){
    console.log(e);
}


我們來看一下當我們按下按鍵後,會回傳什麼內容:


這落落長的到底是什麼東西呢?我們可以在圖片中看到 propertyName 的地方分別有 border-*-color, border-*-width, 另外還有一個 transform,這些東西是當 transition 效果結束之後會回傳的內容,在這裡我們就拿 propertyName = transform 這個來當指標,當transform 出來(也就是 transition 結束,transitionend 被觸發時),把 .playing 這個 class 移除。


let removeTransition = function(e){
    if(e.propertyName !== 'transform') return; //    省略掉其他 propertyName 不是 transform 的物件
    this.classList.remove('playing');   //  把 playing 這個 class 移除
}


如果你不清楚 this 是什麼可以參考這篇:[筆記] 談談JavaScript中的"this"和它的bug,或者直接透過 console.log(this) 把它呼叫出來看看就會更明白了。

到這裡我們就把所有需要用到的效果都寫完拉!

看一下我們學到了什麼


  • 監聽事件(addEventListener(event, function))--- 在 function 中代入參數會回傳和該事件有關的物件
  • 按鍵事件(keydown
  • 透過 CSS 選擇元素(querySelector, querySelectorAll)
  • 音效相關(element.play( ), element.currentTime)
  • 資料屬性(data-* attribute
  • CSS相關(element.classList.add( ), element.classList.remove( )
  • 動畫結束事件 --- transitionEnd
  • forEach( )
  • 若不符合則退出函式:function( ){if(...) return}

如果想要看一下簡單的 DEMO 的話,可以到 CodePen 看看喔!



資料來源:

2017年1月20日

[技術分享] 什麼是 HTML 5 中的資料屬性(data-* attribute)

img
在接觸 HTML5 一段時間,認識大部分的屬性名稱像是 src, img, type, ... 後,一定會看到像是 data-item, data-key, data-...這類的屬性名稱,但是如果你直接去 google "data-item" 或 "data-key" 你可能又查不到這個屬性,到底這是哪種屬性呢?又可以如何應用呢?

HTML5 中的 data-* attribute 屬性

之所以會有 data-* attribute 的出現,是因為在製作網頁的過程中,我們常常會添加一些自己需要用到的屬性名稱,以方便自己容易理解,但總不能每個人在自己的網頁裡面都定義自己的屬性名稱,於是為了要避免大家在 HTML 結構中隨意的添加屬性,在 HTML5 中就多了 data-* attribte 這個屬性,其中的 * 就是一個可以自定義的名稱,例如:data-key='83' 或者是 data-item='1',這也就是為什麼如果你去 google "data-key" 或 "data-item" 會找不到東西的原因了!因為那是網頁工程師自己定義出來的字詞。
在使用 data-* attribute 時,* 字號的地方不能包含大寫字母,也就是屬性名稱不能包含大寫字母,而屬性值則可以是任何的字串。例如說我們可以這樣用:
<div id="slider" data-type="slideShow">
  <img class='photo' data-item="1" data-size="xs" src="http://fakeimg.pl/350x200/?text=Hello" />
  <img class='photo' data-item="2" data-size="lg" src="http://fakeimg.pl/550x200/?text=Welcome" />
</div>

使用 JS 取得 data-* attribute 的屬性值

當我們要取得 data-* attribute 的屬性值時,我們可以簡單利用 JavaScript 中的 dataset 物件,就可以取得了,以上面的 HTML 為例,可以寫成這樣:
let slider = document.getElementById('slider');
console.log(slider.dataset.type);   //  "slideShow

let photo = document.querySelectorAll('.photo');
console.log(photo[0].dataset.item);   //  "1"
console.log(photo[0].dataset.size);   //  "xs"
如果你習慣使用的是 jQuery ,也可以用內建的 .data( ) 這個方法來獲取屬性值:
console.log($('#slider').data('type')); //  "slideShow"

透過 CSS 取得或選取 data-*attribute

在 CSS 當中,我們一樣可以取得這個 data-* attribute 的內容,例如:
<article data-content="Hello Everyone">
</article>
如果我希望在 article 這個空的區塊中顯示 data-content 內容,只需要輸入在 CSS 的地方寫入:
article::before{
  content: attr(data-content);
}
如果我是希望選取具有某個屬性和屬性值的元素,我們也可以搭配使用 CSS 中的屬性選取器
#slider img[data-size="xs"] {
  width: 400px;
}
#slider img[data-size="lg"] {
  width: 800px;
}

程式範例

程式範例 @ CodePen
只要善用 HTML5 中的 data-* attribute 可以幫助我們在網頁開發的時候更有效率或做出更多不同的效果。

參考

2017年1月19日

[筆記] JS30 系列 Day0 - 課程說明



你可能已經有了一些 JavaScript 的基礎,做過了 Codecademy, FreeCodecamp 當中有關 JS 的練習,甚至在 Code SchoolTree House 上付費學習 ,然而卻總是不知道實際上可以怎麼應用,那麼由 Wes Bos 所開設的這堂 JavaScript30 相信會非常適合你!

這堂課程是完全免費搭配英文字幕,在課程中由 Wes Bos 帶你製作 30 個精緻的東西,就讓我們一起來學習吧!

個人的課程筆記會另外放在這個目錄底下:[學習筆記目錄] JS30 系列文章

註冊方式


註冊方式很簡單,只要到 JavaScript30 的網站填入 Email,接著就會收到一封通知信,告知你可以開始課程了:


課程檔案下載


這門課程檔案全部都放在 github 上可以自行下載,進入的方法是點選他回信給你的連結,會帶你到課程導覽頁,裡面有個 "Starter Files" 可以點選,點下去之後就會連到作者的 github 了。

如果你還是找不到這顆按扭,那也可以直接透過這個連結進入。


進入 github 之後,如果你之前就有使用 git 的經驗,可以直接把整個資料夾 clone 下來,如果沒有的話也不用擔心,網頁的右手邊有一個 "clone or download",點下去之後在按下 Download ZIP 就可以把整個課程用的檔案下載下來了!


我聽到 JavaScript 就怕,這門課適合我嗎?

以下是我個人的意見,這門課雖然主要在講的是網頁前端三劍客中的 JavaScript,但如果你完全沒有 HTML 和 CSS 的基礎,上起來的確會顯得比較吃力。

因此,我認為如果你已經有基本的 HTML、CSS 和 JS 能力 (例如,已經把 codecademy 上的基本練習都做過或掌握了),然後覺得學了這些語言後卻又好像不知道該怎麼應用的話,那麼我認為這麼課會非常適合你,你將會更熟悉 JavaScript 這個語言。

但因為課程中有許多部分老師是直接實際操作而沒有做太多觀念上的講解,因此有些部分不理解是正常的,對於不理解的部分你可以:

(1)先跳過,先抓住自己理解的部分,把成品做出來,因為如果你一直卡在不懂的地方,你可能會沒辦法享受這門課程。這就好像在讀英文文章的時候,如果你碰到每一個不懂的單字就停下來查字典,那麼你可能很難享受這篇文章(有許是文章太難了,也許可以先試著讀懂文章的大意就好)。
(2)google 你覺得不清楚的部分。
(3)問人,Facebook 上有許多關於前端網頁的社群,試著自己先理解看看,如果還是不懂的話,問人會是很有效率的方式,當然也可以發訊息到PJCHENder網頁前端資源站

準備好了嗎?一起開始來學習 JavaScript30 吧!PJ 將整理每堂課中的重點,幫助大家(自己)學習整理。

如果課程中有任何問題,也歡迎到 PJCHENder網頁前端資源站,一起來學習討論吧!

課程筆記

個人的課程筆記會另外放在這個目錄底下:[學習筆記目錄] JS30 系列文章

資料來源

資料來源:

[筆記] JavaScript ES6 中的展開運算子(spread operator)和其餘運算子(rest operator)


在 ES6 中,新增了一個 "..." 的關鍵字,這個關鍵字在不同時間點有不同的效果,有些時候它會被當作展開運算子(spread operator)使用,有些時候則是被當作其餘運算子(rest operator)使用。

其餘運算子(rest operator)


假設現在我想要寫一個函式,它可以把所有陣列的值相加後取平均,在過去如果我們要在函式中放入陣列的資料,一般我們會這樣寫:


let arr = [1,2,3,4,5];

let avg = function(arr){
  let sum = 0;
  for(let i = 0; i < arr.length; i++){
    sum += arr[i];
  }
  return sum / arr.length;
}

console.log(avg(arr));  //  3


但是在使用函式的時候,有些時候我們並不清楚我們會放入多少參數數目,假設我們把輸入的參數改成許多數值的話,最後會回傳 NaN 這樣的結果:


let avg = function(arr){
  let sum = 0;
  for(let i = 0; i < arr.length; i++){
    sum += arr[i];
  }
  return sum / arr.length;
}

console.log(avg(1,3,5,7,9));  // NaN


這時候就可以用到其餘運算字的概念(...),其餘運算字會幫助我們把輸入函式中的參數值變成陣列的形式,這時候我們就可以像這樣子撰寫程式:


let avg = function(...arr){
  console.log(arr)  // [1,3,5,7,9]
  let sum = 0;
  for(let i = 0; i < arr.length; i++){
    sum += arr[i];
  }
  return sum / arr.length;
}

console.log(avg(1,3,5,7,9));  // 5


我們可以看到在上面程式碼第二行的地方,從原本的 function(arr) 改成 function(...arr),透過在參數的前面加上 "..." 便可以把所輸入多個參數轉成陣列(我們可以從上面程式碼中把參數 arr 呼叫出來看)。

展開運算子(spread operator)


展開運算子和其餘運算子一樣都是 "..." ,但是在應用的效果上是完全相反的,其餘運算子是把許多的參數轉換成一個陣列,而展開運算子則是可以把陣列中的元素取出

假設我們想用 Math.max( ) 這個函式來找出最大值,但是我們輸入的參數卻是陣列:


let number = [1,2,3,4,5];

console.log(Math.max(number));  //  NaN


這時候因為帶入的參數是陣列的關係,所以我們會得到 NaN 的結果。但如果我們能夠適時的應用展開運算子,我們就可以把這個陣列展開成許多數值:


let number = [1,2,3,4,5];

console.log(Math.max(...number));  //  5

console.log(...number);  // 1,2,3,4,5


這時候我們在陣列的前面加上 "..." ,它就會把陣列從 [1,2,3,4,5] 轉換成 1,2,3,4,5,如此就可以正確的取得最大值。



2017年1月18日

[筆記] 親手打造屬於你自己的 JavaScript Framework/Library(下)




在上一篇 [筆記] 親手打造屬於你自己的 JavaScript Framework/Library(中),我們初步建立了好了自己的 framework ,並且可以成功運用它。最後,我們要來試著讓我們所做的 Framework   和 jQuery 做更多的結合,並且開始應用它。

我們的目標是希望能夠利用我們所做的這個 library 做一個簡單的登入呈現頁面,現在就讓我開始吧!

HTML部分


首先 HTML 的部分我們先寫一個很簡單的登入視窗,像是這樣:


<body>
 <div id="app">
  <div id="login">
   <select id='Lang'>
    <option value="en" selected>English</option>
    <option value="zh">中文</option>
   </select>
   <button>登入</button>
  </div>
  <h1 id="logMsg"></h1>
 </div>
 
    <script src="jquery-3.1.1.js"></script>
    <script src="greetr.js"></script>
    <script src="app.js"></script>
</body>


JS 部分(greetr.js)


接下來,我們要在之前做的 Library 中加入 jQuery 的方法,一樣放在 Greetr.prototype 裡面,我們加入一個方式叫做 greetHTML ,這個方法可以直接幫我們把 greet 輸出的文字,寫入 HTML 當中:


greetHTML: function(selector, formal) {
    if (!$) throw "jQuery not loaded";

    if (!selector) throw "no seletor";

    let msg = "";

    if (formal) {
        msg = this.formalGreeting();
    } else {
        msg = this.greeting();
    };

    $(selector).html(msg);

    return this;

}


於是我們就做好我們的 Library 了,順便把完整的 Library Code (greetr.js) 放一份在下面:
;
(function(global, $) {

    var Greetr = function(firstname, lastname, language) {
        return new Greetr.init(firstname, lastname, language);
    }

    let supportedLangs = ['en', 'zh'];

    let greetings = {
        en: 'Hello',
        zh: '你好'
    };

    let formalGreetings = {
        en: 'Greetings',
        zh: '您好'
    };

    let logMessages = {
        en: 'Logged',
        zh: '登入'
    };

    Greetr.prototype = {
        fullName: function() {
            return this.firstname + ' ' + this.lastname;
        },
        validate: function() {
            if (supportedLangs.indexOf(this.language) === -1) {
                throw 'Invalid Language';
            }
        },
        greeting: function() {
            return greetings[this.language] + ' ' + this.firstname;
        },
        formalGreeting: function() {
            return formalGreetings[this.language] + ', ' + this.fullName();
        },
        greet: function(formal) {

            let msg;

            // if undefined or null, it will coerced to 'false'
            if (formal) {
                msg = this.formalGreeting();
            } else {
                msg = this.greeting();
            }

            if (console) {
                console.log(msg);
            }

            // 'this' refers to the calling object at execution time
            // makes method chainable
            return this;

        },
        log: function() {
            if (console) {
                console.log(logMessages[this.language] + ' ' + this.fullName());
            }
            return this;
        },
        setLang: function(lang) {
            this.language = lang;
            this.validate();
            return this;
        },
        greetHTML: function(selector, formal) {
            if (!$) throw "jQuery not loaded";

            if (!selector) throw "no seletor";

            let msg = "";

            if (formal) {
                msg = this.formalGreeting();
            } else {
                msg = this.greeting();
            };

            $(selector).html(msg);

            return this;

        }
    };

    Greetr.init = function(firstname, lastname, language) {
        let self = this;
        self.firstname = firstname || '';
        self.lastname = lastname || '';
        self.language = language || 'zh';
        self.validate();
    }

    Greetr.init.prototype = Greetr.prototype;

    global.Greetr = global.G$ = Greetr;


}(window, jQuery))

接著,我們就可以實際應用我們的 Library 了。

Library 應用(app.js)


實際應用的時候,我們會寫在另外一支 app.js 中,現在我們希望當我按下登入之後,就會在網頁上出現打招呼,因此,我們可以在 app.js 中這樣寫:


$('#login').on('click', 'button', function() {
    let pj = G$("Po-Jung", "Chen");
    pj.setLang($("#Lang").val()).greetHTML('#logMsg', true);
})


透過這樣的方式,當我們按下按鈕之後,就會出現相對應的打招呼文字:


看起來是相當簡單的原理,但只要再加上一些 CSS 的修飾,就可以是一個相當出色的登入頁面,可以參考看看這個頁面





→回到此系列文章目錄



2017年1月17日

[筆記] JavaScript ES6 中的物件的擴展(object literal extension)


在 ES6 中,對於物件的使用有了更彈性的應用,除了撰寫物件的方式變得更為精簡之外,更允許將屬性名稱指定為變數,以達到動態賦予屬性名稱的效果,讓我們來看看在 ES6 中物件的擴展吧。

物件的擴展(object literal extension)


在 ES6 中允許在物件中直接給變量,這時候物件的屬性名稱為變數的名稱,物件的屬性值為變數的值,讓我們來看一下這個例子:


let website = "pjchender";
let obj = {website};
console.log(obj);    //[Object]{website: "pjchender"}


這時候變數的名稱(website)會變成物件的屬性名稱,變數的值("pjchener")會變成物件的屬性值。實際上,let obj = {website} 也是一種縮寫,完整的寫法會是 let obj = {website:website} 它實際上對應的關係如下圖所示:


也因此,如果我們輸入如下的程式碼:


let website = "pjchender";
let obj = {abc:website};
console.log(obj); // [object]{abc: "pjchender}


建立出來的物件,他的屬性名稱就會是 "abc" 而不是 "website"。

我們可以用同樣的方式為多個物件的屬性賦值:


let website = "pjchender";
let country = "Taiwan";

let obj = {
  website,
  country
};
console.log(obj); // [object]{country: "Taiwan", website: "pjchender"}


在 object literal 中所賦予的值,會覆蓋掉在更上 let 的宣告:


let name = "PJCHEN";
let country = "Taiwan";

let obj_es6 = {
  name: "Aaron",
  country,
}

console.log(obj_es6); // [Object]{name: "Aaron", country: "Taiwan"}


從這裡我們可以看到最後 name 這個屬性值會是 Aaron 而不是 PJCHEN。

物件中的方法也可以簡寫


除了物件中的屬性可以簡寫外,物件中的方法也可以透過這樣的方式簡寫(第8-10行),例如:


let name = "PJCHEN";
let country = "Taiwan";

let obj = {
  name,
  country,
  location(){
    console.log(this.name + ' lives in ' + this.country);
  }
}

obj.location();  // PJCHEN lives in Taiwan


這樣方法的簡寫,原本的寫法如下:

let obj = {
  name: name,
  country: country,
  location: function(){
    console.log(this.name + ' lives in ' + this.country);
  }
}


另外,還有一點需要注意的是,這樣簡寫的方法,預設的屬性名稱會是字串的型態,也因此原本的簡寫 location( ){...} 等同於 'location'( ){...}

在 ES6 中允許將表達式作為屬性名稱,以達到動態賦值的效果


在 ES6 中,允許將表達式作為屬性的名稱,只需要使用 [ ] 就可以了,方法如下:


let obj_es = {
  ["web"+"site"]: "pjchender"
}
console.log(obj_es); // [Object]{website: "pjchender"}


透過這樣的方式,我們更可以去動態賦予屬性名稱:



let websiteName = "pjchender";
let a = 2;
let b = 3;

let obj_es = {
  [websiteName]: "welcome",
  [a+b]: "sumNumber"
}
console.log(obj_es);  // [Object]{5: "sumNumber", pjchender: "welcome"}



透過 [ ] 的方式,我們的屬性名稱就可以放入變數,以達到動態給予屬性名稱的效果。

2017年1月16日

[筆記] JavaScript ES6 中的物件解構賦值(object destructuring)


在上一篇文章中我們說明了 ES6 當中如何使用陣列解構賦值([筆記] ES6 中的陣列解構賦值(array destructuring)),這篇我們一樣著重在解構賦值的部分,進一步說明如何以物件解構賦值(object destructuring)。

物件解構賦值的基本使用


物件解構賦值的基本使用方法如下:


let obj = {
  website: "pjchender",
  country: "Taiwan"
}

let {website, country} = obj;
console.log(website);  // pjchender
console.log(country);  // Taiwan


在這當中有一個很重要的點,陣列的解構賦值強調的順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值。

物件解構賦值的寫法看似簡單,但實際上,在上面這段程式碼中 let {website, country} = obj ,實際上完整的寫法應該是像這樣子(也就是說上面那段程式碼是簡寫):


let {website:website, country:country} = obj;


它會根據前面的屬性名稱來對應要給的值,但值其實是給冒號(:)後面的變數,用圖來看像是這樣子:


所以真正被建立和賦值的 let{ } 當中,冒號(:)後的變數。

我們可以透過另一個例子更容易了解這個概念:


let obj = {
  website: "pjchender",
  country: "Taiwan"
}

let {website:wb, country:ct} = obj;

console.log(website, country);   //  Error:website in not defined
console.log(wb, ct)  // "pjchender", "Taiwan"



我們把冒號後的內容改寫成 wb 和 ct,接著我們會發現,當我們呼叫原本的變數 website 和 country 時,會回報 error;但是當我們呼叫 wb 和 ct 這兩個變數時,則能夠正確回傳變數值,也就是說,在物件解構賦值中,冒號前是用來對應物件的屬性名稱,冒號後才是真正建立的變數名稱和被賦值的對象

相對的,當冒號前的屬性名稱對應不到物件中的屬性名稱時,則會出現 undefined:


// website 和 wb 並沒有相對應
let{website} = {wb: "pjchender"};
console.log(website);    // undefined


物件解構賦值是以屬性名稱做對應


和陣列解構賦值不同的是,在陣列解構賦值中,我們可以接受下面這樣的寫法:


let [a, , c] = [1, 2, 3];
console.log(a, c); // 1, 3


但是在物件解構賦值中,我們不能像上面這樣寫:


let{a, ,c} = {a:1, b:2, c:3};
console.log(a,c); // Error


之所以會回報錯誤,主要的原因是在陣列的解構賦值強調的是順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值

物件解構賦值同樣能給予預設值


和陣列的解構賦值一樣,物件的解構賦值也能賦予預設值,方法如下:

此範例擷取自阮一峰-ECMAScript 6 入門

物件解構賦值的用途


物件解構賦值的用途相當多(可參考阮一峰-ECMAScript 6 入門),其中在提取 JSON 數據時相當方便:


let data_JSON = {
  id: 74,
  website: "pjchender",
  country: "Taiwan",
  detail:{
    add: "Tainan",
    phone: "0933333333"
  }
}

let {id, website, country, detail} = data_JSON;
console.log(id, website, country, detail);


如此就能夠快速提領出 JSON 物件的屬性名和屬性值。

2017年1月15日

[筆記] JavaScript ES6 中的陣列解構賦值(array destructuring)

在 ES6 中過去的陣列和物件可以透過解構(destructuring)的方式來賦值。這篇文章中,我們會先說明如何透過陣列的方式來賦值。

陣列解構賦值的方法(array destructuring)

過去陣列內的元素在賦值的時候,只能透過直接給值的方式,像是下面這樣:
let numbers = [1, 2, 3];
let a = numbers[0];
let b = numbers[1];
let c = numbers[2];
console.log(a,b,c);   // 1, 2, 3

一般用法

然而在 ES6 中可以直接透過解構的方式賦值,像是下面這樣子:
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1, 2, 3
如此變數 a = 1, b = 2, c = 3,這是最基本的陣列解構賦值方式。

當輸入的變數多於所給的值

當變數的數量多於賦予的值時,多出來的那個變數會被賦予 undefined 的值(d = undefined):
// 當變數多於所給的值
let [a, b, c, d] = [1, 2, 3];
console.log(a, b, c, d); // 1, 2, 3, undefined

當輸入的變數少於所給的值

當輸入的變數少於所給的值的時候,只有被指定到的變數會有值,少掉的變數可以直接空過去,這時候 g = 1, i = 3:
// 當變數少於給的值
let [g, , i] = [1,2,3];
console.log(g, i);  // 1, 3

在陣列解構中賦予預設值

我們也可以在陣列解構中賦予預設值,像是下面這樣:
let [a, b, c = 4, d = 'Hello'] = [1, 2, 3];
console.log(a, b, c, d); // 1, 2, 3, "Hello"
如此就可以將變數 c 賦予 4,d 賦予 Hello 的預設值,輸出的結果 c 因為後面有給值,所以依然是 3 ,而 d 在後面沒有給值,就直接帶入了預設值,得到 “Hello"。

防雷須知

如果你使用的是 standardJS 當作你的 code style,那麼你應該很習慣在結尾不加分號,但是在使用陣列的結構賦值時,這麼做 有可能會噴錯誤。
例如這樣:
let x = 0
let y = 0
console.log(x, y)
[x, y] = [1, 2]
console.log(x, y)
這時候會得到 "TypeError: Cannot set property '0' of undefined 的錯誤。首先簡單說明一下之所以可以不用在結尾加分號是因為在 多數情況下,
在語句或一段代碼敘述後,加了Enter鍵(\n)後,JS剖析器會在執行期間自動幫你插入分號 @ eddyChang
上面提到是多數情況,但是在 某些情況下 JS 引擎是不會幫你加上分號的,其中像是這裡的開頭以 [ 開頭的語句。因此在這裡,請記得在 [ 的前面加上分號,寫起來會像是這樣 ;[x, y] = [1, 2]
更多關於分號的使用請參考 JavaScript裡的語句用分號結尾是個選項嗎 @ eddyChang

資料來源

[筆記] JavaScript ES6 中的函數預設值(default value)


在過去如過要在函式中建立預設值常常要利用到 JS 中強制轉換型別(coercion)的這種特性,但在 ES6 中則可以用相當簡易的方式就可以設定函式的預設值(default value),寫起來更簡潔方便,讓我們來看一下可以怎麼樣使用。

函式預設值的使用


函式預設值的使用非常簡單,只需要在函式中給予參數的地方用等號賦值就可以了,方式如下:

function add(x = 3, y = 5){
  console.log(x+y);
}

add();  // 8

也就是說,只要在 add 這個函式的參數位置寫 add(x =3, y=5) 就可以直接帶入預設值,因此雖然我在執行 add( ) 的時候沒有帶入任何的參數值,但它不會報錯,而是回傳 8 。

使用預設值的基本觀念


丟入的參數值會由前往後代入

假設我只給予後面的 y 預設值,如下:

function add(x, y = 5){
  console.log(x+y);
}

add(2);   // 7
add(2,8); // 10


這時候我可以只輸入一個參數是沒有問題的,這個值會先被丟到 x 這個參數。然而,如果我只給予前面的 x 預設值,如下:

function add(x = 3, y){
  console.log(x+y);
}

add(2);   // NaN
add(2,8); // 10


這時候當我輸入 add(2) 來執行這個函式時,因為 2 會先被帶入 x ,所以 x = 2;而 y 沒有被賦值,因此 y = undefined,這也使得最後回傳的結果會是 NaN。


2017年1月14日

[筆記] JavaScript ES6 中的箭頭函數(arrow function)及對 this 的影響


在這篇文章中要來說明一下在 ES6 中相當常見的箭頭函數(arrow function),讓我們來看一下可以怎麼樣使用。

什麼是箭頭函數(arrow function)


首先,我們來看一下過去我們撰寫函數的方法:



在 ES6 中,我們可以把它改成箭頭函數的寫法,它會變成下面這樣:


沒有參數的時候要記得加上空括號

要特別留意的地方是,在箭頭函數中如果沒有帶入參數時,一樣要加上空括號。



如果只是要回傳某個值,可以省略 return

如果我們的函式本身只是要回傳某個值的話,可以把 return 這個字省略掉:


箭頭函數帶入參數值


兩個以上的參數,需要使用括號

當我們的函式擁有兩個以上的參數時,我們一樣要使用括號來帶入參數,寫法像是下面這樣子:


當函數只有一個參數時,不需要使用括號

從上面的例子我們可以知道,當函數沒有參數或有兩個以上的參數時,我們都要加上括號( ),但是當函數只有一個參數時,可以省略括號不寫,因此,當我們的函數只有一個參數時,我們的函數長得像這樣:



箭頭函數當中的 this 是定義時的對象,而不是使用時的對象


在使用箭頭函數時,有一點要注意的是,在箭頭函數中,this 指稱的對象在所定義時就固定了,而不會隨著使用時的脈絡而改變。

讓我們來看一下這個例子:

在這個範例中,不論我們使用的是原本 function 的寫法或 ES6中的箭頭函式,都會回傳得到最外層的 window 物件(如果不清楚 this 的話可以先參考 [筆記] 談談JavaScript中的"this"和它的bug),這樣看起來似乎兩者沒有太大的差別。



然而,換個例子的情況就不一樣,讓我們來看看下面兩個不同的例子:

例子一(參考自 阮一峰 - ECMAScript 6 入門

我們分別用原本的寫法和箭頭函示的寫法建立了兩個 function:

// 原本的 function
let fn = function(){
  console.log(this.constructor.name);  // Object(data)
  setTimeout(function(){
    console.log(this.constructor.name) // Window
  },100);
}

// 箭頭函式 Arrow function
let fn_arr = function(){
  console.log(this.constructor.name);  // Object(data)
  setTimeout(() => {
    console.log(this.constructor.name) // Object(data)
  },100);
}

let id = 21;
let data = {
  id: 21
}

fn.call(data);     
fn_arr.call(data); 


setTimeout:裡面都分別帶入 setTimeout 的函式,我們知道 setTimeout 執行的時間會在整個 JS execution context 都被執行完後才會執行(如果對這概念不清楚的話可參考:[筆記] 談談JavaScript中的asynchronous和event queue),因此函式建立的時間和實際執行的時間是不同的,因此這也創造了兩個不同時間點的 this 所指稱的對象。

call:另一個要了解的是 call 的用法,在 call 當中,我們會帶入後面所指定的物件當作所指稱的 this 對象(對這個概念不清楚的話可參考:[筆記] 了解function borrowing和function currying ─ bind(), call(), apply() 的應用)。

this.constructor.name:這樣子的寫法只是避免在回傳出物件的時候把整個物件內容給傳出來,而是指示傳出該物件的名稱(參考自:stackoverflow)。

綜合上述,我們可以知道,因為有用 call(data) 的緣故,因此不論是使用傳統函式寫法(fn)或箭頭函式(fn_arr)時,在一開始函式裡面的 this 都指稱的是 data 這個物件。

然而不同的地方會在執行 setTimeout 中的函式,在使用傳統函式的寫法時,使用 fn.call(data) 時,因為它執行的時間點是在整個 JS execution context 執行完才執行,而當時的環境會變成是 global environment,因此使用傳統函式時,這個 this 指稱的對象會轉變成 window object 。但是,如果是使用新的箭頭函式(arrow function),這個 this 所指稱的對象則不會改變,依然是 data 這個 object

範例:https://jsbin.com/wodegu/edit?js,console

例子二(參考自 Accelerated ES6 JavaScript Training

第二個例子是使用 addEventListener 來達到示範,首先我們在 HTML 中建立一個 button element,然後利用 JS 來抓取這個 button,接著 JS 部分則如下所示:

var button = document.querySelector('button');
var fn_arr = () => {
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行function時 this 指 Window
};
var fn = function(){
  // 建立 function 時 this 指 Window
  console.log(this.constructor.name)  // 執行function時 this 指 HTMLButtonElement

button.addEventListener('click', fn_arr);


和例子一中的 setTimeout 類似,我們使用的 addEventListener ,也會在整個 execution context 執行結束後,在網頁觸發事件時才執行。

因此不論在傳統的函式寫法(fn)或箭頭函式(fn_arr)的寫法,一開始建立 function 的時候 this 所指稱的都是  window 這個物件,然而,如果是使用傳統的寫法,在觸發這個事件時所指稱的對象會從原本的 window 變成 HTMLButtonElement;若使用的是箭頭函式,則會固定所指稱的對象,因此 this 依然指稱的是 window 這個物件。

範例:https://jsbin.com/kotetu/edit?js,output

2017年1月4日

[筆記] JavaScript ES6 中使用 const 宣告常數


在 ES6 中可以透過 const 來宣告變數,究竟 const 有什麼特別的地方呢?

首先 const 的意思是 constant ,也就是常數的意思,當我們宣告它之後,它是不能在被改變的,但實際上在使用時仍然有一些需要注意的地方,讓我們先來看一下下面的例子:

當我們使用 const 來宣告變數時,就像和使用 let 一樣,都可以得到 27 的結果:


可是當我對這個 const 重新指派值時,就會出現錯誤訊息:


也就是說 age 它在這裡其實是被設定成一個常數,而不是一個變數。 透過 const 我們可以宣告常數

因此,為了方便區分哪些是常數那寫是變數,我們可以把常數在宣告的時候用大寫來表示,像是這樣:


使用 const 在宣告陣列或物件時需要留意的地方


這麼看起來 const 似乎很容易理解,但是有幾種狀況必須非常小心,當 const 使用在陣列(array)或物件(object)的時候需要特別留意,讓我們來看一下使用在陣列的情況:

當我們已經使用 const 宣告好一個常數,接著在用 push 去推入一個陣列的值時,並不會有錯誤的情形產生。


之所以不會有錯誤,是因為在 JS 中陣列(array)和物件(object)都是屬於 reference type,因此實際上我們並沒有把這個常數指向(pointer)另一個東西,它仍然指稱到的是同一個記憶體位置。

如果不清楚 by reference 的意思,可以參考:[筆記] 談談JavaScript中by reference和by value的重要觀念

同樣的道理,如果我們使用的是物件(object),一樣不會有錯誤的情形產生:


然而,還有一點需要提醒的是,如果你是使用 object literal 的方式修改物件的內容,那對於 JS 引擎來說,就等於是建立了一個新的物件,也就是它會將這個物件存到另一個記憶體位置,意思就是這個常數的值改變了,如此的話,同樣會出現錯誤的訊息(陣列也是一樣的道理):


const 和 let 一樣都是block-scoped


最後一點是 const 和 let 一樣,所宣告的變數都是僅在代碼區塊內有效(block-scoped),也就是僅在限定的{ }內有效:





2017年1月3日

[筆記] JavaScript ES6 中使用block-scoped 的 let 宣告變數



最近開始在 Udemy 上學習和另一個與 JavaScript 有關的課程ㄧ Accelerated ES6 JavaScript Training,主要的內容是針對 ES6(ECMAScript 6)加以學習,因此接下來會整理這門課程當中的內容還有其他網頁資源作為自己的學習筆記,有想要一同學習的夥伴們也可以持續 follow ,另外,因為我也是在持續學習的過程中,所以如果有任何觀念或語法上的錯誤,也非常希望比較有經驗的大大們可以協助改正。

使用 let 宣告變數


在 ES6 中有一個新的關鍵字 let ,let 的用法和過去使用的 var 非常相似,都可以用來宣告變數:

在這裡我們可以看出,不論是用 var 或用 let 都能得到相同的結果。


然而,var 和 let 最主要的差別在於 let 所宣告的變項只有在代碼塊區域(block scope)內有效,讓我們繼續看下去:

當我們使用 var 來宣告變數時,因為它不會受限於在代碼區塊內,所以一樣可以輸出變數內容。


但是當我們使用的是 let 的時候,一切就不同了,因為 let 所宣告的變數只能做用到代碼塊區域(block scope)中,所以當我們在 { } 外面要呼叫用 let 所定義的 author 這個變數時,就會出現 error:


簡單一句話來說:用 let 所定義的變數只能作用在所屬的 { } 中有效