# [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語法有哪些成員,其他課程會提到。