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