Modern Front-End Workflow
ECMAScript 6
&
Babel
ECMAScript 6
ECMAScript 是 JavaScript 的語言標準規格,後者是前者的實作品
Node.js 是 ECMAScript 在伺服器端的實作品語言
ECMAScript 6 是 2015 年時正式發表的新一代版本標準,有許多新的語法以及功能,又名為 ECMASript 2015
後面還有 ES7 (ES 2016)、ES8 (ES 2017) 在規劃與實作中,慣例將之統稱為 ES6+
簡言之,ES6 就是新版的 JavaScript 規格
ECMAScript 6
-
各家瀏覽器的支援還不夠完美與普遍,目前仍有部分功能未被實作完成
-
某些使用者可能永遠也不會更新到支援 ES6+ 的瀏覽器
-
因此我們需要依賴工具將我們的 ES6+ 程式碼轉譯成 ES5 環境能運行的版本,讓我們能放心無虞的使用 ES6+ 語法或功能來開發
Babel
-
Babel 是目前最主流的 JavaScript Transpiler
-
可以幫我們處理 ES6+ to ES5、React JSX 轉換等工作
-
可以單獨運行,也可以配合 Webpack 這種前端打包工具一起使用
let & const 變數宣告
-
var 語法的變數提升 (Hoisting) 特性對程式碼可維護性是有害的,使用 let 與 const 語法來完全取代 var 語法
-
使用 let 與 const 宣告的變數只在宣告位置的區塊中有效
- 使用 let 來宣告普通的變數,使用 const 來宣告並立即賦值一個不再改變的常數
if (true) {
var a = 10;
}
console.log(a); // 10
if (true) {
let b = 20;
console.log(b); // 20
}
console.log(b); // Uncaught ReferenceError: b is not defined
const c; // SyntaxError: Missing initializer in const declaration
Template String
-
除了單引號與雙引號之外,ES6 新定義的字串種類
-
使用反引號「`」當作字串開合符號
-
支援直接輸入換行
-
支援直接嵌入變數或值
const name = 'Zet';
let str;
str = '我的名字是' + name + ',大家好,\n' +
'這是一個單引號字串,\n' +
'這段字串有三行。';
str = `我的名字是${name},大家好,
這是一個模板字串,
這串字串有三行。`
Spread Operator
-
把陣列或物件展開為個別項目的便捷運算語法
-
通常用於「合併集合」與「函數的參數填入」
const array = [4, 5, 6];
[1, 2, 3, ...array]; // [1, 2, 3, 4, 5, 6];
[1, ...array, 2, 3]; // [1, 4, 5, 6, 2, 3];
[...array, ...array]; // [4, 5, 6, 4, 5, 6];
const object = { b: 2, c: 3 };
{ a: 1, ...object }; // { a: 1, b: 2, c: 3 }
{ b: 100, ...object }; // { b: 2, c: 3 }
{ ...object, b: 100 }; // { b: 100, c: 3 }
function addAll(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(addAll(numbers)); // "1,2,3undefinedundefined"
console.log(addAll(...numbers)); // 6
Destructuring
-
將集合進行解構賦值的便捷語法
const object = { a: 1, b: 2 };
const { a, b } = object;
console.log(a); // 1
console.log(b); // 2
function getPersonInfoString({ name, phone }) {
return `名字:${name},電話:${phone}`;
}
console.log(getPersonInfoString({
name: 'Zet',
phone: '0912345678'
}));
// "名字:Zet,電話:0912345678"
Object 簡寫表示法
-
針對一個 Property 僅填入一個變數時:
-
以變數的名稱當做 Object key
-
以變數的值當作 Object Value
-
const a = 100;
console.log({ a }); // { a: 100 }
console.log({ a: a }); // { a: 100 }
Object 屬性名表達式
-
使用 [] 來定義 {} 中的 Object key,可以填入一個表達式
const propertyName = 'email';
const object = {};
object[propertyName] = 'test@gmail.com';
console.log(object);
// { email: 'test@gmail.com' }
console.log({
[propertyName]: 'test@gmail.com',
['ab' + 'cd']: true
});
// { email: 'test@gmail.com', abcd: true }
Function 參數預設值
-
ES6 允許直接為函數的參數指定預設值
-
可以配合物件解構賦值使用
function hello(name = 'Zet') {
console.log(`Hello ${name}`);
}
hello(); // Hello Zet
hello('DMoon'); // Hello Dmoon
function getPersonInfoString({ name = 'Zet', phone }) {
return `名字:${name},電話:${phone}`;
}
console.log(getPersonInfoString({
phone: 0912345678
}));
// "名字:Zet,電話:0912345678"
Rest Parameters
-
使用「...」 符號填入函數參數定義,代表接收剩餘的所有參數,並存入指定名稱的陣列中
function sum(...numbers) {
let result = 0;
numbers.forEach(function (number) {
result += number;
});
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15
Arrow Function
-
Arrow Function (箭頭函數) 是一種 ES6 新定義的特殊匿名函數,用於簡寫或是適用某些特殊需要的情況
-
參數只有一個值時,可以省略包住參數的小括號
-
表達式只有一行且函數要 return 一個值時,可以省略大括號與 return 語法,但當回傳值是一個物件時,大括號外需再用小括號包住
const printName = (name) => {
console.log(name);
};
printName('Zet'); // "Zet"
const getDouble = number => number * 2;
const getDouble = function (number) {
return number * 2;
}
console.log(getDouble(100)); // 200
const getObject = (key, value) => ({
[key]: value
});
console.log(getObject('name', 'Zet')); // { name: 'Zet' }
Arrow Function
-
Arrow Function 擁有自動綁定外部 this 到函數內部的特性
-
手動使用 .bind() 方法指定綁定的 this 對 Arrow Function 是無效的
$(document).ready(function() {
console.log(this); // document DOM
$('#btn1').click(function() {
console.log($(this).attr('id')); // "btn1"
console.log(this); // #btn1 DOM
});
$('#btn2').click((event) => {
console.log($(this).attr('id')); // undefined
console.log(this); // document DOM
console.log($(event.target).attr('id')); // "btn2"
});
let printThis = () => {
console.log(this);
};
printThis(); // document DOM
printThis = printThis.bind(100);
printThis(); // document DOM
});
Class
- JavaScript 沒有真正的類別
- JavaScript 基於 Prototype 來實現類似物件導向的設計模式
- 傳統寫法是透過 Constructor Function 定義並產生新 Instance
function Macbook(data) {
this.brand = 'Apple';
this.model = data.model;
this.cpu = data.cpu;
this.ram = data.ram;
this.ssd = data.ssd;
}
Macbook.models = {
2014: ['2014 Mid'],
2015: ['2015 Early'],
2016: [
'2016 Late Without TouchBar',
'2016 Late With TouchBar'
]
};
Macbook.getModelsByYear = function(year) {
return Macbook.models[year];
};
Macbook.prototype.reboot = function () {
console.log('do reboot...');
};
var mbpr = new Macbook({
model: '2016 Late Without TouchBar',
cpu: 'i7-6660U',
ram: '16G',
ssd: '512G'
});
console.log(mbpr);
/*
Macbook {
brand: "Apple",
model: "2016 Late Without TouchBar",
cpu: "i7-6660U",
ram: "16G",
ssd: "512G"
} */
mbpr.reboot();
// "do reboot..."
console.log(Macbook.getModelsByYear(2016));
/* [
"2016 Late Without TouchBar",
"2016 Late With TouchBar"
] */
Class
- ES6+ 提供了 Class 語法糖,能使用更貼近物件導向語言的寫法
class Macbook {
brand = 'Apple';
constructor({model, cpu, ram, ssd}) {
this.model = model;
this.cpu = cpu;
this.ram = ram;
this.ssd = ssd;
}
reboot() {
console.log('do reboot...');
}
static models = {
2014: ['2014 Mid'],
2015: ['2015 Early'],
2016: [
'2016 Late Without TouchBar',
'2016 Late With TouchBar'
]
};
static getModelsByYear(year) {
return Macbook.models[year];
}
}
class MacbookPro extends Macbook{
constructor(data) {
super(data);
}
isPro = true;
}
const mbpr = new MacbookPro({
model: '2016 Late Without TouchBar',
cpu: 'i7-6660U',
ram: '16G',
ssd: '512G'
});
console.log(mbpr);
/*
MacbookPro {
brand: "Apple",
model: "2016 Late Without TouchBar",
cpu: "i7-6660U",
isPro: true,
ram: "16G",
ssd: "512G"
} */
Promise
- Promise 是一種非同步程式設計的解決方案,比傳統使用 Callback Function 的方式要更合理與強大
- Promise 是一個容器,裡面保存了某個未來才會結束的事件
- 透過原生的 Promise API 來操作這些非同步事件的流程與資料傳遞
const timeout = (delay) => new Promise((resolve, reject) => {
if (typeof delay == 'number') {
setTimeout(() => {
resolve(`${delay}ms passed away.`)
}, delay);
} else {
reject('input is a non-number value.');
}
});
timeout(3000)
.then(value => {
console.log(value);
}).catch(error => {
console.error(error);
});
// "3000ms passed away."
timeout('abc')
.then(value => {
console.log(value);
}).catch(error => {
console.error(error);
});
// "input is a non-number value."
Promise
- 以下是搭配 jQuery Ajax 的範例情境
const getArticleList = () => new Promise((resolve, reject) => {
$.ajax({
url: '/api/article/list',
dataType: 'json',
success: (result) => resolve(result)
});
});
const getArticle = (id) => new Promise((resolve, reject) => {
$.ajax({
url: `/api/article/detail/${id}`,
dataType: 'json',
success: (result) => resolve(result)
});
});
const getAuthor = (id) => new Promise((resolve, reject) => {
$.ajax({
url: `/api/author/${id}`,
dataType: 'json',
success: (result) => resolve(result)
});
});
getArticleList()
.then(articles => getArticle(articles[0].id))
.then(article => getAuthor(article.authorId))
.then(author => console.log(author))
.catch(error => console.error(error));
Promise
- 平行發起 Promise 處理
- Promise.all():等待所有 promise 皆被 resolve 再一起回傳結果
- Promise.race():有第一個 promise 被 resolve 則就馬上回傳該結果
const getDelayAPI = (delay) => new Promise((resolve, reject) => {
$.ajax({
url: `/api/delay/${delay}`,
dataType: 'json',
success: (result) => resolve(result)
});
});
Promise.all([
getDelayAPI(1000),
getDelayAPI(2000),
getDelayAPI(3000)
]).then(results => console.log(results));
Promise.race([
getDelayAPI(1000),
getDelayAPI(2000),
getDelayAPI(3000)
]).then(result => console.log(result));
Asnyc / Await
- async function 是基於 Promise 的擴充語法
- 能夠讓 Promise 非同步操作更接近同步的語意
- async 關鍵字
- 表示這是一個 asnyc function,該 function 會自動變成一個 會回傳 promise 的 function
- await 關鍵字
- 表示在此處等待這個 promise 的 resolve 或 reject 結果
- async 關鍵字
const getAuthorByArticle = async() => {
try {
const articles = await getArticleList();
const article = await getArticle(articles[0].id);
const author = await getAuthor(article.authorId);
console.log(author);
} catch (error) {
console.error(error);
}
}
Fetch API
Fetch API
-
Fetch 是目標取代 XHR 的新 AJAX 技術標準,已經被部分瀏覽器實作
-
是一個 HTML BOM 的 API,並非來自 ECMAScript 標準
-
Fetch 原生基於 Promise 實現,因此也可以直接搭配 async / await 使用
const getArticleList = async() => {
const response = await fetch('/api/article/list');
return await response.json();
}
const getArticle = async(id) => {
const response = await fetch(`/api/article/detail/${id}`);
return await response.json();
}
const getAuthor = async(id) => {
const response = await fetch(`/api/author/${id}`);
return await response.json();
}
try {
const articles = await getArticleList();
const article = await getArticle(articles[0].id);
const author = await getAuthor(article.authorId);
console.log(author);
} catch (error) {
console.error(error);
}
Fetch API
-
以下是以 Fetch 發送一個 Post Request 的範例,並且將回傳的資料以普通文字解析處理
const fetchPost = async(data) => {
const response = await fetch('/api/post', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return await response.text();
}
Module System
Module System
-
模組(Module)代表的是某些程式或功能被封裝成一個封閉狀態的包裹,與外界只能透過事先定義好的接口進行溝通,而不受其他因素而被外界干擾或影響
-
模組內定義傳入參數的格式、模組的功能與處理過程,以及最後的輸出結果是什麼
-
模組外則在需要的地方引入這個模組,並根據事先定義好的接口使用之
為什麼需要模組機制?
-
命名衝突
-
全域命名空間下容易造成變數命名衝突,尤其是跟第三方套件衝突時更難解決
-
-
程式碼間的順序與依賴性維護困難
-
開發人員必須自行理解並解決不同 JS 檔案之間,以及與第三方套件的相互依賴關係
-
-
在大型專案中各種資源難以管理,長期積累的問題導致程式碼庫混亂不堪
但是...
-
JavaScript 這個語言本身長時間以來都沒有設計這樣子的模組機制功能
-
直到 ES6 中才制訂了原生標準的模組機制規範
社群推廣的模組機制規範
-
AMD
-
CommonJS
-
語法簡潔易用
-
Node.js 採用的模組規範,最初設計給後端使用
-
必須經過 Browserify 或 Webpack 等工具進行處理之後,才能在前端瀏覽器上正常運行
-
目前 ES5 的模組規範主流
-
-
ES6 原生的模組機制融合了上列兩者的優點,並更加先進易用
CommonJS
-
require(模組檔案路徑 或 npm 套件路徑)
-
module.exports = 該模組輸出的結果
// main.js
const num = 10;
const double = require('./double.js');
console.log(double(num)); // 20
// double.js
const num = 100;
module.exports = (n) => {
return n * 2;
};
ES6 Module
-
export 語法主要分為兩種
-
export 一個具名的變數或函數,可以有多個 export
-
export default 一個具名或不具名的值或函數,只能有一個 export default
-
-
import 語法主要分為兩種,可以混著同時寫
-
import 引入後的變數或函數命名 from 'npm套件路徑或模組檔案路徑'
-
引入 export default 的東西
-
-
import { 子模組名稱, 子模組名稱 } from 'npm套件路徑或模組檔案路徑'
-
引入 export 的東西,名稱必須與 export 時的名稱相同,可以一次引入多個
-
-
import str, { number, callback } from './modules.js';
console.log(str); // "default export value"
console.log(number); // 1
console.log(callback); // (msg) => alert(msg)
// module.js
export const number = 1;
export const callback = (msg) => alert(msg);
export default 'default export value';
NPM
NPM ( Node Module Manager )
-
NPM 本身是 Node.js(後端)的套件管理系統
-
因為 Node.js 的逐漸崛起以及 NPM 的普及,前端的 JS 或 CSS 套件也被放上了 NPM,漸漸也成為了前端的套件管理系統主流,通吃前後端
-
Bower 等純前端套件的管理系統逐漸式微
package.json
-
專案根目錄中的 package.json 檔案,負責記錄與 npm 相關的資訊
-
dependencies:相依模組們的名稱與版本號
-
scripts:自定義的快捷指令集
-
// package.json
{
"scripts": {
"start": "node server.js"
},
"dependencies": {
"jquery": "^3.1.1"
}
}
-
npm install
-
依照 package.json 中的 dependencies 資訊完整安裝所有套件
-
-
npm install 套件名稱 --save
-
安裝指定的套件,並寫入 package.json 中紀錄
-
應該每次都記得要加上 --save
-
-
npm install 套件名稱 -g
-
安裝全域型的套件,通常是 CLI 程式
-
透過模組系統引入 NPM Module
// CommonJS
const $ = require('jquery');
// ES6 Module
import $ from 'jquery'
$('button').click(function(){
alert('clicked.')
});
Webpack
打包 ( Bundle ) 這件事
-
對前端程式碼或是圖片、字型等 Assets 檔案進行一些轉譯、合併、壓縮、額外加工處理
Webpack
-
Wepack 是一個前端模組的整合與打包工具
-
同時支援 AMD、CommonJS、ES6 Module 等模組規範
-
能讓一些原本只能在 Node 上使用的 package 在前端中也能使用
-
高效能的 Bundle 速度
-
整合樣式表(CSS/SCSS/LESS 等)
-
能夠處理圖片以及字型檔
-
豐富的額外插件
webpack.config.js
-
Wepack 的設定檔,我們將運行的配置寫在這段程式碼中,然後 Webpack 執行時就會按照該配置來進行 bundle 的動作並產出靜態檔案。
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: [
'babel-polyfill',
'./src/index.js'
],
output: {
path: '/build',
filename: 'bundle.js',
},
module: {
loaders: [{
test: /\.(js)$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['latest', 'stage-0']
}
}]
},
devtool: 'eval-source-map'
};
Reference
- http://es6.ruanyifeng.com/
- http://huli.logdown.com/posts/292655-javascript-promise-generator-async-es6
- https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/ajax_fetch.html
本週作業
- 繳交期限:至 3/10 的中午 12:00,截止後會暫時移除各位的編輯權
- 作業計算量化分數,並在批改完後公開
- 所有學員分別獨立一個 private repo 繳交
- 推到 homework 分支,並與 master 分支比較發起 PR,推錯地方視同作業繳交失敗
-
基本要求
- 範例程式只使用了 ES5 語法以及 $.ajax,請自行依課程內容所教,判斷何處適合使用 ES6 語法並改寫之
- 替換 $.ajax 成為 fetch API (配合 async / await)
- 僅可以更改 src 資料夾中的程式碼
-
額外要求(徵酌加分)
- 程式碼縮排保持一致美觀
- 你可以在程式碼中加上你的註解,說明你為什麼覺得這裡要這樣改或為什麼不改,幫助作業的批改者理解你的思考以及判斷依據
EXMA-Training-ES6+
By tz5514
EXMA-Training-ES6+
- 1,595