ECMAScript 是 JavaScript 的語言標準規格,後者是前者的實作品
Node.js 是 ECMAScript 在伺服器端的實作品語言
ECMAScript 6 是 2015 年時正式發表的新一代版本標準,有許多新的語法以及功能,又名為 ECMASript 2015
後面還有 ES7 (ES 2016)、ES8 (ES 2017) 在規劃與實作中,慣例將之統稱為 ES6+
簡言之,ES6 就是新版的 JavaScript 規格
各家瀏覽器的支援還不夠完美與普遍,目前仍有部分功能未被實作完成
某些使用者可能永遠也不會更新到支援 ES6+ 的瀏覽器
因此我們需要依賴工具將我們的 ES6+ 程式碼轉譯成 ES5 環境能運行的版本,讓我們能放心無虞的使用 ES6+ 語法或功能來開發
Babel 是目前最主流的 JavaScript Transpiler
可以幫我們處理 ES6+ to ES5、React JSX 轉換等工作
可以單獨運行,也可以配合 Webpack 這種前端打包工具一起使用
var 語法的變數提升 (Hoisting) 特性對程式碼可維護性是有害的,使用 let 與 const 語法來完全取代 var 語法
使用 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
除了單引號與雙引號之外,ES6 新定義的字串種類
使用反引號「`」當作字串開合符號
支援直接輸入換行
支援直接嵌入變數或值
const name = 'Zet';
let str;
str = '我的名字是' + name + ',大家好,\n' +
'這是一個單引號字串,\n' +
'這段字串有三行。';
str = `我的名字是${name},大家好,
這是一個模板字串,
這串字串有三行。`
把陣列或物件展開為個別項目的便捷運算語法
通常用於「合併集合」與「函數的參數填入」
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
將集合進行解構賦值的便捷語法
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"
針對一個 Property 僅填入一個變數時:
以變數的名稱當做 Object key
以變數的值當作 Object Value
const a = 100;
console.log({ a }); // { a: 100 }
console.log({ a: a }); // { a: 100 }
使用 [] 來定義 {} 中的 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 }
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"
使用「...」 符號填入函數參數定義,代表接收剩餘的所有參數,並存入指定名稱的陣列中
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 (箭頭函數) 是一種 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 擁有自動綁定外部 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
});
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 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"
} */
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."
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));
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));
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 是目標取代 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 發送一個 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)代表的是某些程式或功能被封裝成一個封閉狀態的包裹,與外界只能透過事先定義好的接口進行溝通,而不受其他因素而被外界干擾或影響
模組內定義傳入參數的格式、模組的功能與處理過程,以及最後的輸出結果是什麼
模組外則在需要的地方引入這個模組,並根據事先定義好的接口使用之
命名衝突
全域命名空間下容易造成變數命名衝突,尤其是跟第三方套件衝突時更難解決
程式碼間的順序與依賴性維護困難
開發人員必須自行理解並解決不同 JS 檔案之間,以及與第三方套件的相互依賴關係
在大型專案中各種資源難以管理,長期積累的問題導致程式碼庫混亂不堪
JavaScript 這個語言本身長時間以來都沒有設計這樣子的模組機制功能
直到 ES6 中才制訂了原生標準的模組機制規範
AMD
CommonJS
語法簡潔易用
Node.js 採用的模組規範,最初設計給後端使用
必須經過 Browserify 或 Webpack 等工具進行處理之後,才能在前端瀏覽器上正常運行
目前 ES5 的模組規範主流
ES6 原生的模組機制融合了上列兩者的優點,並更加先進易用
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;
};
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 本身是 Node.js(後端)的套件管理系統
因為 Node.js 的逐漸崛起以及 NPM 的普及,前端的 JS 或 CSS 套件也被放上了 NPM,漸漸也成為了前端的套件管理系統主流,通吃前後端
Bower 等純前端套件的管理系統逐漸式微
專案根目錄中的 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 程式
// CommonJS
const $ = require('jquery');
// ES6 Module
import $ from 'jquery'
$('button').click(function(){
alert('clicked.')
});
對前端程式碼或是圖片、字型等 Assets 檔案進行一些轉譯、合併、壓縮、額外加工處理
Wepack 是一個前端模組的整合與打包工具
同時支援 AMD、CommonJS、ES6 Module 等模組規範
能讓一些原本只能在 Node 上使用的 package 在前端中也能使用
高效能的 Bundle 速度
整合樣式表(CSS/SCSS/LESS 等)
能夠處理圖片以及字型檔
豐富的額外插件
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'
};