---
# System prepended metadata

title: '[JS102] 升級你的 JavaScript 技能：ES6 + npm + Jest(筆記)'
tags: ['[JS102] 升級你的 JavaScript 技能：ES6 + npm + Jest', Lidemy學院]

---

# [JS102] 升級你的 JavaScript 技能：ES6 + npm + Jest(筆記)
###### tags:`Lidemy學院` `[JS102] 升級你的 JavaScript 技能：ES6 + npm + Jest`
[[JavaScript] 一次搞懂同步與非同步的一切：待會叫你 — 回呼函式(Callback Function)](https://medium.com/itsems-frontend/javascript-callback-function-993abc2c0b42)
# 模組化與 Library
## 借別人的東西來用：
Module模組化的概念，將每一個功能都模組化(切分成許多小檔案的感覺)，需要時再引入，降低耦合性。

require() : 這函式能將它人的模組引入近來使用，就可以用它人寫好的功能，()裡放檔案位置，因為require會自己去判斷檔案位置，所以通常不用加副檔名(.js)跟路徑(./)。


範例 : 以[node.js](https://nodejs.org/docs/latest-v15.x/api/os.html)提供的模組示範
```
//node.js，提供跟作業系統有關的模組
const os = require('os');

//platform()，輸出作業系統名稱
console.log(os.platform())    //win32
```
___
## 把東西借給別人：export
將自己寫好的函式，用module.exports後，別的檔案就能require()引入使用。

有兩種格式 : 
1. module.exports = ，後面可以放，數字，字串，物件，函式都可以。
2. exports.exports_name = exports_name 可以隨便取，後面放的，在引入後都會變成物件使用，

**module.exports範例**

範例1 : 輸出Function
```
//myModule.js
function double(n){
  return n*2
}

module.exports = double
```
```
//code.js
//const myModule = require('./myModule.js')
var myModule = require('./myModule')

console.log(myModule)       //[Function: double]
console.log(myModule(2))    //4

//終端機 輸入 node code.js
```
範例2 : 輸出陣列
```
//myModule.js

module.exports = [1,2,3]
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //[ 1, 2, 3 ]
```
範例3 : 輸出字串
```
//myModule.js

module.exports = 'hello'
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //hello
```
範例4 : 輸出數字
```
//myModule.js

module.exports = 123
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //123
```
範例5 : 物件，跟下方輸出是一樣的，可以將多個函式放在物件裡
```
//myModule.js
function double(n){
  return n*2
}

module.exports = {
  double: double,
  triple: function (n){
    return n*3
  },
}
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //{ double: [Function: double], triple: [Function: triple] }

console.log(myModule.double(2),myModule.triple(3))    //4 9
```
範例6 : 物件
```
//myModule.js
function double(n){
  return n*2
}

var obj = {
  double: double,
  triple: function (n){
    return n*3
  },
}

module.exports = obj
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //{ double: [Function: double], triple: [Function: triple] }

console.log(myModule.double(2),myModule.triple(3))    //4 9
```
**exports.exports_name範例**

範例1 : 用exports.exports_name輸出，引入後都會是物件
```
//myModule.js
function double(n){
  return n*2
}

exports.double = double

exports.triple = function(n){
  return n*3
}
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //{ double: [Function: double], triple: [Function (anonymous)] }

console.log(myModule.double(2),myModule.triple(3))    //4 9 
```
範例2 : 
```
//myModule.js
function double(n){
  return n*2
}

exports.double = 123
```
```
//code.js
var myModule = require('./myModule')

console.log(myModule)       //{ double: 123 }

console.log(myModule.double)    //123
```

___
# NPM：把你們的力量借給我吧
## npm 是什麼？
[npm](https://www.npmjs.com//) (Node Package Manager) : Node套件管理，透過npm幫我們管理套件(套件，模組，Library，都是指同個東西)，在裝node.js預設就一起裝好了，可以用`npm -v`，查看此軟體版本，npm的版本是跟著node在走的，可以利用npm安裝別人寫好的開原套件。

[補充 NPM ?](https://ithelp.ithome.com.tw/articles/10191682)
___
## npm install：以 left-pad 為例
其實 left-pad 這個 module 發生過一些很有名的事情，因為很多有名的 library 都有用到 left-pad，之前作者把這個 module 從 npm 上面拿掉，造成了其他使用它的 module 都沒辦法安裝，詳情可參考：[如何看待 Azer Koçulu 刪除了自己的所有 npm 庫？](https://www.zhihu.com/question/41694868)或是[抽掉 11 行程式就讓網路大崩塌！一場撞名事件，看開源的威力與權力衝突。](https://www.inside.com.tw/article/6041-how-one-programmer-broke-the-internet-by-deleting-a-tiny-piece-of-code)

補充資訊：

從 npm 5 以後，--save 就已經變成預設的選項了，因此 npm install 不加 --save 也是可以的喔，一樣會把資訊寫到 package.json 裡面去！

==利用left-pad，熟悉npm的使用== [left-pad](https://www.npmjs.com/package/left-pad)
```
//初始化，在要使用此套件的資料夾下，開啟終端機，輸入
npm init
```
輸入後，會新增兩個檔案，package.json，package-lock.json。
package.json 用來描述此專案的一個檔案。
```
//package.json
{
  "name": "test",         //專案名稱                              
  "version": "1.0.0",     //版本
  "description": "",      //專案的敘述
  "main": "index.js",     //入口的檔案是甚麼
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {       //指專案依賴(安裝)那些套件
    "left-pad": "^1.3.0"
  }
}
```
```
//在要使用此套件的資料夾下，開啟終端機，輸入
npm install left-pad
```
輸入後，會新增一個資料夾，node_modules，npm會將安裝的套件放在裡面，==在做版本管理時，請忽略它，package.json的dependencies裡已經紀錄了此專案需要的套件，輸入npm install，就能將node_modules裡的套件安裝回來==
```
// code.js
const leftPad = require('left-pad')

console.log(leftPad('foo', 5)) 
// => "  foo"
 
console.log(leftPad('foobar', 6))
// => "foobar"
 
console.log(leftPad(1, 2, '0'))
// => "01"
 
console.log(leftPad(17, 5, 0))
// => "00017"

//終端機 輸入 node code.js
```

___
## npm scripts
npm scripts的用法 : 可以設定指令，寫好後，可以用 npm run <指令>，操作
```
//package.json
{
  "name": "test",         //專案名稱                              
  "version": "1.0.0",     //版本
  "description": "",      //專案的敘述
  "main": "index.js",     //入口的檔案是甚麼
  "scripts": {            //可以寫些指令
    "start": "node index.js",    //執行進入入口檔案
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {       //指專案依賴(安裝)那些套件
    "left-pad": "^1.3.0"
  }
}
```
```
//執行
npm run start
```
___
## yarn：npm 以外的另一種選擇
[yarn](https://yarnpkg.com/) : Facebook 發布了全新的 JS 套件管理工具 Yarn

[取代 npm 的新利器 Yarn](https://medium.com/@jackypan1989/%E5%8F%96%E4%BB%A3-npm-%E7%9A%84%E6%96%B0%E5%88%A9%E5%99%A8-yarn-7d97f2f409b9)
___
# 來幫你的程式寫測試吧！
## 為什麼要寫測試？
怎麼樣測試你寫的程式是對的還是錯的 ?
方法1 : 用console.log()看輸出後的結果，但測試資料最好想一樣，特別案例，工作上不適用
```
//code.js

function repeat(str, n){
  var result  = ''
  for(var i=0; i<n; i++){
    result += str
  }
  return result
}

console.log(repeat('123', 2))    //123456
console.log(repeat('', 3))       //
console.log(repeat('abc', 0))    //

//node code.js
``` 
方法2 : 用 === 跟應該的輸出結果做判斷，看輸出是否為true，工作上不適用
```
//code.js

function repeat(str, n){
  var result  = ''
  for(var i=0; i<n; i++){
    result += str
  }
  return result
}

console.log(repeat('123', 2) === '123123')    //true
console.log(repeat('', 3) === '')             //true
console.log(repeat('abc', 0) === '')          //true

//node code.js
``` 
方法3 : 用他人寫好的軟體做測試，[Jest](https://jestjs.io/docs/getting-started)
___
## 利用 Jest 來寫你的第一個測試！
[Jest](https://jestjs.io/docs/getting-started)，單元測試
```
//安裝Jest
npm install --save-dev jest
```
```
//範例code.js

function repeat(str, n){
  var result  = ''
  for(var i=0; i<n; i++){
    result += str
  }
  return result
}

module.exports = repeat
```
新增副檔名為.test.js的檔案，放測試資料
```
//code.test.js

const repeat = require('./code');

describe('測試 repeat ', () => {
  test('a 重複5次 會 = aaaaa', () => {
    expect(repeat('a',5)).toBe('aaaaa');
  });
  test('123 重複2次 會 = 123123', () => {
    expect(repeat('123',2)).toBe('123123');
  });
  test(' "" 重複10次 會 = "" ', () => {
    expect(repeat('',10)).toBe('');
  });
});
```
```
//package.json，新增"test": "jest"
{
  "scripts": {
    "test": "jest"
  }
}
```
```
//開始測試
npm run test
```
[十分鐘上手前端單元測試 - 使用 Jest](https://wcc723.github.io/development/2020/02/02/jest-intro/)
___
## 先寫測試再寫程式：TDD
TDD(Test-driven Development) : 測試驅動開發，先寫測試在寫程式
範例 : 
```
//code.test.js
const reverset = require('./code');

describe('測試 reverset ', () => {
  test('123 reverse = 321', () => {
    expect(reverset('123')).toBe('321');
  });
  test('!!! reverse = !!!', () => {
    expect(reverset('!!!')).toBe('!!!');
  });
  test(' "" reverse = "" ', () => {
    expect(reverset('')).toBe('');
  });
});
```
```
//code.js

function reverse(str){
  var result  = ''
  for(var i=str.length-1; i>=0; i--){
    result += str[i]
  }
  return result
}

module.exports = reverse
```
```
//開始測試
npm run test
```
___
# 配備升級：ES6
## ES5？ES6？這些到底是什麼
ECMAScript : 是標準、規範，JavaScript就是根據這個標準來實作的。

ECMAScript 6 在2015年發佈，ES6 又稱 ES2015，這個規範相比ES5，新增與多JavaScript的新的語法，這新的語法，為JavaScript帶來的好處。 
___
## 宣告變數的新選擇：let 與 const
const : (constant)常數，宣告數字跟字串時值無法改變，但陣列跟物件，值可以改變，因為陣列跟物件底層是存記憶體位置，記憶體位置還是不變的。

作用域 (Scpoe) : 指變數的生存範圍

let : 作用域為一個區塊，let與const作用域一樣

var : 作用域為一個 function ， var作用域比 let 大，變數的運行機制，會找近的值，做為內容值，下層可以看到上層有宣告的變數，但上層無法使用下層的變數

範例 :
```
//const 數字
let a = 10
const b = 15
b = 20
console.log(a, b)    //error，const不變


//const 物件
let a = 10
const b = {
  number : 15
}
b.numbrt = 20
console.log(b)    //20
```
範例 :
```
//var  
var a = 10          //上層
function test(){
  console.log(a)    //下層
}
test()    //10

//var
var a = 10          //上層
function test(){
  var a = 20        //會找近的值，做為內容值
  console.log(a)    //下層
}
test()    //20

//var

function test(){
  var a = 20        //會找近的值，做為內容值
  console.log(a)    //下層
}
test()    //20
console.log(a)      //error，a is not defined
```
___
## 再也不需要字串拼接：Template Literals
==在做字串拼接時，多使用 \` \` ，去拼接字串==
1. 解決多行字串拼接的問題
2. 多個變數

```javascript=
//code.js
//es5 多行字串
str = 'hello'+'\n'+
'yo'+'\n'+
'ya'

console.log(str)

//輸出
hello
yo
ya
```
```javascript=
//code.js
//es5 多個變數
function sayHi(name) {
  console.log('hello ' + name + ' now is ' + new Date())
}

sayHi('tony')

//輸出
hello tony now is Thu May 06 2021 11:24:32 GMT+0800 (台北標準時間)
```
```javascript=
//code.js
//es6 多行字串
str = `hello
yo
ya
`

console.log(str)

//輸出
hello
yo
ya
```
```javascript=
//code.js
//es6 多個變數
function sayHi(name) {
  console.log(`hello ${name} now is ${new Date()}`)
}

sayHi('tony')

//輸出
hello tony now is Thu May 06 2021 11:24:32 GMT+0800 (台北標準時間)
```

___
## 聽起來很酷的 Destructuring：解構
可以用在

1. 陣列
2. 物件，解構時名稱要跟要跟物件的屬性同名

解構的好處，如果要將陣列或物件的內容拿出來，可以省些步驟

用法1 : 在陣列，如要宣告某變數等於陣列中，某內容
```
//es5
const arr = [1, 2, 3, 4]

let first = arr[0]
let second = arr[1]
let third = arr[2]
let fourth = arr[3]

console.log(second, third)    //2 3
```
```
//es6 解構
const arr = [1, 2, 3, 4]

let [first, second, third, fourth] = arr

console.log(second, third)    //2 3
```
用法2 : 物件，也是類似的用法，如要宣告某變數等於物件中，某內容
```
//es5
const obj = {
  name: 'tony',
  age: 30,
  address: 'taiwan'
}

let name = obj.name
let age = obj.age
let address = obj.address

console.log(address)    //taiwan
```
```
//es6
const obj = {
  name: 'tony',
  age: 30,
  address: 'taiwan'
}

let {name, age, address} = obj

console.log(address)    //taiwan
```
```
//補充，可以雙重物件，輸出會是最後解構的值
const obj = {
  name: 'tony',
  age: 30,
  address: 'taiwan',
  family: {
    father: 'nick'
  }
}

let {family} = obj

console.log(family)    //{father: 'nick'}

//用法1
let {family} = obj
let {father} = family

console.log(father)    //nick

//用法2
let {family: {father}} = obj
console.log(father)    //nick
```
用法3 : 函式放參數的地方，如是陣列或物件，可以用解構{ }
```
function test({a,b}){
  console.log(a)        //nick
}

test({
  a:1,
  b:2
})
```
___
## 把東西展開：Spread Operator
展開運算子 ... : 用在陣列跟物件，也可以用來複製陣列跟物件的值給新變數。
範例1 :
```
//陣列
let arr = [1, 2, 3]
let arr2 = [4, 5, 6, ...arr]

console.log(arr2)    //[ 4, 5, 6, 1, 2, 3 ]
```
範例2 :
```
function add(a, b, c){
  return a + b + c
}

let arr = [1, 2, 3]
console.log(add(...arr))    //6
```
範例3 :
```
//物件
let obj1 = {
  a: 1,
  b: 2
}

let obj2 ={
  z:1
} 

let obj3 ={
  ...obj1,
  c: 3
}

console.log(obj3)    //{ a: 1, b: 2, c: 3 }
```
範例4 : 複製arr值的作法
```
//陣列
let arr = [1, 2, 3]
let arr2 = [...arr]

console.log(arr2)    //[1, 2, 3]
```
範例5 : 如複製的值是arr，因記憶體，都是指向同一個，所以複製的跟被複製的是相同的
```
let nestedArray = [4]
let arr = [1, 2, 3, nestedArray]
console.log('arr: ',arr)           //arr:  [ 1, 2, 3, [ 4 ] ]

let arr2 = [...arr]
console.log('arr2: ',arr2)         //arr2:  [ 1, 2, 3, [ 4 ] ]

console.log(arr[3] === arr2[3])    //true

```
___
## 「反向」的展開：Rest Parameters
Rest Parameters : 大多用在陣列跟物件，函式，語法是一樣的，但用的時機不一樣，通常是跟解構一起使用，感覺像是「反向」的展開。

範例1 : 變數rest值，只解構後，剩下的arr值，Rest Parameters 在使用上只能放最後面。
```
//陣列
let arr = [1, 2, 3]

let [first, ...rest] = arr

console.log(rest)    //[2, 3]
```
範例2 : 
```
//物件
let obj = {
  a: 1,
  b: 2,
  c: 3
}

let {a, ...rest} = obj

console.log(rest)    //{ b: 2, c: 3 }
```
範例3 :
```
let obj = {
  a: 1,
  b: 2
}

let obj2 = {
  ...obj,                 //展開
  c: 3
}

let {a, ...rest} = obj2  //「反向」的展開

console.log(rest)        //{ b: 2, c: 3 }
```
範例4 : 函式
```
//一般的時候
//function add(a, b){
//  return a + b
//}

//用法1
function add(...args){    
  console.log(args)         //[1, 2]
  return args[0] + args[1]
}

//用法2
function add(a, ...args){    
  console.log(args)         //[2]
  return a + args[0]
}

console.log(add(1, 2))     //3
```
___
## 加上預設值：Default Parameters
Default Parameters (預設值) : 用在 function ，物件，陣列上的，在參數上加預設值，如沒有傳值時，使用預設值
範例1 : 
```
//function
function repeat(str, times = 5){    //用法 = Default
  console.log(times)          
  return str.repeat(times)
}

console.log(repeat('abc', 3)) //abcabcabc

console.log(repeat('abc'))    //abcabcabcabcabc
```
範例2 :
```
let obj = {
  a: 1,
//  b:2
}

let {a, b = 2} = obj  //用法 = Default

console.log(a, b)     //1 2
```
___
## Function 的更新：箭頭函式 Arrow Function
箭頭函式 : 宣告 Function 的新方法

範例1 :
```
//一般的寫法1
function test(n){
  return n
}

//一般的寫法2
let test = function(n){
  return n
}

//箭頭函式
let test = (n) => {
  return n
}

//箭頭函式 ， 如果只有一個參數，可以去掉()
let test = n => {
  return n
}

//箭頭函式 ， 如果只有一個參數，可以去掉() ， 如果只有一個回傳內容 ， 可以去掉{}
let test = n => n

//以上結果都是一樣
```

範例2 :
```
let arr = [1, 2, 3, 4, 5]

console.log(
  arr
    .filter(function(value){
      return value > 1 
    })
    .map(function(value){
      return  value * 2
    })
)                               //[ 4, 6, 8, 10 ]

console.log(
  arr
    .filter(value => value > 1 )
    .map(value => value * 2 )
)                               //[ 4, 6, 8, 10 ]

//以上結果都是一樣
```
___
## Import 與 Export

在node.js中，暫時沒有支持，此語法，要用 npx babel-node ，指令來跑

ES6，Export 有幾種方式：

1. export function add(){}，使用 import {add} 引入
2. export { add }，與上面那種一樣
3. export default function add()，使用 import add 引入，不需要加大括號

如果想要用其他名字，可以用 as 取別名，例如說 export { add as addFunction }

可以用 import * as utils from 'utils' 把全部都 import 進來

範例1: 在es5中的，require 與 Export 用法，成對使用的
```
//utils.js
function add(a, b){
  return a + b
}

module.exports = add

//index.js
let add = require('./utils')

console.log(add(3, 5))    //8
//node index.js
```
範例2: 在es6中的，Import 與 Export 用法，成對使用的
```
//utils.js
export function add(a, b){
  return a + b
}

export const PI = 3.14

//index.js
import {add, PI} from './utils'

console.log(add(3, 5), PI)    //8 3.14 
//npx babel-node index.js
```
範例3: export { add } ，的外一種格式，結果都一樣
```
//utils.js
function add(a, b){
  return a + b
}

const PI = 3.14

export{
  add,
  PI
}

//index.js
import {add, PI} from './utils'

console.log(add(3, 5), PI)    //8 3.14 
//npx babel-node index.js
```
範例4: export中可以使用 as 換名稱
```
//utils.js
function add(a, b){
  return a + b
}

const PI = 3.14

export{
  add as addFunction,
  PI
}

//index.js
import {addFunction, PI} from './utils'

console.log(add(3, 5), PI)    //8 3.14 
//npx babel-node index.js
```
範例5: import中可以使用 as 換名稱
```
//utils.js
function add(a, b){
  return a + b
}

const PI = 3.14

export{
  add as addFunction,
  PI
}

//index.js
import {addFunction as a, PI} from './utils'

console.log(a(3, 5), PI)    //8 3.14 
//npx babel-node index.js
```
範例6 : 可以用 import * as utils from ‘utils’ 把全部都 import 進來
```
//utils.js
function add(a, b){
  return a + b
}

const PI = 3.14

export{
  add as addFunction,
  PI
}

//index.js
import * as utils from './utils'

console.log(utils.addFunction(3, 5), utils.PI)    //8 3.14 
//npx babel-node index.js
```
範例7 : export default function add()，使用 import add 引入，不需要加大括號，default(預設值的意思)
```
//utils.js
export default function add(a, b){
  return a + b
}

export const PI = 3.14

//index.js
import add, {PI} from './utils'

console.log(add(3, 5), PI)    //8 3.14 
//npx babel-node index.js
```
範例8 : default，的原理
```
//utils.js
export default function add(a, b){
  return a + b
}

export const PI = 3.14

//index.js
import {default as add, PI} from './utils'

console.log(add(3, 5), PI)    //8 3.14 
//npx babel-node index.js
```
___
## Babel 簡介與基本使用方法

因前端發展的速度，比瀏覽器還快，某些東西都無法在舊的瀏覽器使用，所以會開發些工具。

大致上就是將，ES6/7/8 => Babel => ES5 ，也不一定是ES5，也能是更舊的語法。

現代前端開發必裝套件之一，Babel，的功能是將新的語法，給編譯程舊的使其能夠支援。去把它轉換成舊的語法。

Babel 的安裝說明：https://babeljs.io/docs/en/next/babel-node.html

設定步驟：

1. 安裝必要套件：npm install --save-dev @babel/core @babel/node @babel/preset-env
2. 新增 .babelrc
3. 填入內容，告訴 babel 要用這個 preset：
{
 "presets": ["@babel/preset-env"]
}

最後使用 npx babel-node index.js 即可
___
## ES6 小結
更多 ES6 新增的語法：https://github.com/DrkSephy/es6-cheatsheet

此課程，並沒有說明，全部新的 ES6 的語法，還有許多，沒說到，例如 Map、Set、Symbol...，算是入門，那如果使用新語法，不知使用在哪裡，可以不一定要是用，並不是一定要用新語法，而是覺得有必要在用，最常用的應該是，宣告變數時使用，let 與 const，var 可以不用使用，只是需要知道有甚麼新語法，如有需要再使用即可。
___
# 結語
此課程目的，模組化，測試，ES6，的觀念，跟新語法。

模組化 : 將常用到的函式，模組化，如需要在引入。

npm : 套件管理工具，很多時候，某些功能已經有人開發過，又是開源套件，不用重新造輪子，這是就會使用到 npm ，在實際開發時，有些小功能可以開發就自己開發，比較麻煩的功能，就可以適當引入些Library去使用。

測試 : 本課程教的是單元測試，單元測試可以想成是對單一個function，做測試，所以只測試這function的input跟output，是否是正確的，實際上前端還有需多種測試，在開發上，還滿常用到單元測試，確認function是否有寫好。

ES6 : 在使用上不一定要使用，但未來一定有需要使用的場合，至少知道有這語法，在JS101跟JS102，都沒提到跟瀏覽器有關的用法，是為了更熟悉JS語法有哪些成員，其他課程會提到。