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); // 2function 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 Dmoonfunction 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));  // 15Arrow 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));  // 200const 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'
};