升級JavaScript
module模組化
將個別功能區分開來,好處在於
(1)在多處被使用的功能,不用重複再寫一次
(2)要更動特定功能時,不會改到其他地方。
nodejs中有許多寫好的
可供使用,以下以os這個模組實作,使用OS.platform()來查詢電腦的作業系統。使用require呼叫os這個模組 ,並存於變數os,便可直接使用。
var os = require('os')
console.log(os.platform())
除了使用nodejs提供的模組,也可以自己寫好模組輸出並引用。
輸出自己的模組
e.g.以myModule.js寫自己的模組功能,並在index.js呼叫使用。
輸出方法一 : module.exports
#myModule.js
function double(n)
{
return n*2
}
module.exports = double
#index.js
var double = require('./myModule.js')
console.log(double)
console.log(double(3))
//[Function:double]
//6
當要輸出的function不只一個
#myModule.js
function double(n)
{
return n*2
}
function triple(n)
{
return n*3
}
var obj = {
double:double,
triple:triple
}
module.exports = obj
var myModule = require('./myModule.js')
console.log(myModule)
console.log(myModule.double(2))
console.log(myModule.triple(2))
//{ double: [Function: double], triple: [Function: triple] }
//4
//6
此方法可以輸出任意資料型態
module.exports = 123
module.exports = '123'
module.exports = [1,2,3]
#這樣僅會輸出最後一行,在此僅用來表示可輸出任意型態
2.輸出方法二:exports.名稱 = 某function
#myModule.js
function double(n)
{
return n*2
}
function triple(n)
{
return n*3
}
exports.double = double
exports.triple = triple
#可以有多行,輸出型態為物件
var myModule = require('./myModule.js')
console.log(myModule)
console.log(myModule.double(2))
console.log(myModule.triple(2))
NPM(Node Package Manager)
管理套件的工具,最常用的是可以從網路上下載別人寫好的package。
npm install
$npm install <package>
安裝套件,以
為例,首先可以$npm -v
先確認自己有沒有安裝好npm及其版本,接著直接以$npm install left-pad
安裝。安裝完畢後,如果是在此資料夾內第一次安裝package,會出現三個新的資料夾
- node_modules : 之後新下載的packages也都會存放在裡面,可以看到裡面多了一個left-pad。
- package.json :此檔案紀錄你的專案使用了哪些packages及其版本。由於所有npm套件都可以從網路上下載,若直接將node_modules裡的套件都放在雲端供人下載,因檔案較大下載時間較久,所以實務上並不會將其放在雲端。要告訴別人你這個專案使用了哪些package,只需要將package.json、package-lock.json放上去即可,使用npm install可以直接下載該專案所需的所有套件。
- package-lock.json:為甚麼又有一個多了lock的檔案呢?這裡記載的是你所下載的套件及其所依賴的套件,包含各種詳細訊息(版本、下載地址),主要有以下兩個作用:
- 當npm install下載該專案所需套件時,可以提升下載速度(因為有詳細記錄套件資訊)
- 下載時鎖定特定版本(因為安裝預設為最新版,然而可能會不相容)
之後可以在Index.js裡使用該套件。
#index.js
var leftPad = require('left-pad')
console.log(leftPad(123,10,'0'))
//0000000123
npm scripts
在專案內執行npm init可以建立關於此專案的相關訊息,訊息會儲存在package.json裡面,其中scripts這個區域可以加入想要執行的檔案,例如"start":"node index.js", 之後便可以在終端機直接以$npm run start
來執行index.js。
yarn
除了npm外,Facebook也開發了新的Package Manager叫做yarn,功能大致相同,但下載速度較快,也解決了一些npm原有的問題。
Home | Yarn - Package Manager (yarnpkg.com)
透過npm install -g yarn 安裝,若需要管理員權限,在前面加上sudo (in Linux)。
使用上若是之前新下載專案要安裝所有套件是用npm install,yarn的部分則是直接打yarn 就可以了。以之前的舉例的專案只安裝left-pad來說,yarn只需要0.34s而npm則是0.576s 。
npm install 則用 yarn add代替,其他大部分都相同。
測試
jset是Facebook出的一款幫助JavaScript寫測試的軟體。unit test單元測試。
安裝
首先在專案裡安裝jest
yarn add --dev jest
使用
在測試檔內的用法如下(官網範例):
const sum = require('./sum'); //引入寫好的模組
test('adds 1 + 2 to equal 3', () => { //描述模組作用
expect(sum(1, 2)).toBe(3);//使用函數、輸入/預期輸出
});
命名
假設現在要測試寫在index.js裡的function,則建立一個index.test.js做為測試檔(加上.test的命名規則是約定成俗的!)
#index.js
function repeat(str,times)
{
var result =''
for (var i=0; i<times; i++)
{
result+=str
}
return result
}
module.exports = repeat
#index.test.js
var repeat = require('./index.js')
describe('測試repeat',()=>{ //用describe包起來不是必要的,但可以看起來比較清楚。
test('a重複五次應等於aaaaa',()=>{
expect(repeat('a',5)).toBe('aaaaa')
})
test('abc重複一次應等於abc',()=>{
expect(repeat('abc',1)).toBe('abc')
})
test('""重複十次應等於""',()=>{
expect(repeat('',10)).toBe('')
})
})
有四種方法可執行jest測試
在package.json中的scripts裡修改成"test":"jest"
//npm run test
//會執行所有為.test的測試檔
在package.json中的scripts裡修改成"test":"jest index.test.js"
// 會執行index.test.js測試檔
在package.json中的scripts裡修改成"start":"jest index.test.js"
//像之前一樣npm run start的用法
在terminal執行 npx jest index.test.js(較新的npm版本才有npx指令)
//因為jest僅安裝在專案底下,因此不能在terminal直接使用jest 要加上npx。
TDD(Test Driven Development)
測試驅動開發,一般邏輯會先做出函式在進行測試,TDD則是先完成測試檔,在進行函式開發。
也就是把前一段落的流程倒過來!先將想好的edge case加入.test,再撰寫主程式,便可以確保該程式可以符合預期結果,不會有例外。
ES6
ES6 (= ES2015)是一個標準規範ECMAScript 的縮寫+版本,JS就是根據此編寫而成。此為涵蓋全部新增功能,其他可參照
基本變動
宣告變數的新選擇
const
constant常數,宣告後就不能再更動。不需要變動的東西盡量使用const。
#這樣就是不行的!
const b=10
b=20
console.log(b)
#這樣是可以變更的!
#因為這樣是儲存該物件的記憶體位置在b,因此可以透過該記憶體位置去變更儲存的值。
const b = {
number: 10
}
b.number=20
console.log(b)let
和var主要差異在於作用域(scope)不同,在js中,var的作用域為fuction內,而let則是{}內,以下面程式碼說明:
#var 作用於function內,因此第二個console會失敗
function test(){
if(true){
var a = 10
}
console.log(a)
}
test()
console.log(a)
#let 作用於{}內,因此兩個console會失敗
註(Scope):
- var 的作用域為function內,非{}
- 若沒宣告var,則會自動在全域中宣告
- let、const的作用域在{}內
Template literals
再也不需要字串拼接,可讀性變高!主要影響有以下兩個:
多行字串
#要輸出
//
//second line
//third line
#ES5以前
var str =''+'\n'+
'second line'+'\n'+
'third line'
console.log(str)
#ES6
var str =`
second line
third line`
console.log(str)
字串+變數拼接
#要輸出
//hello, Wendy now is Sat Oct 02 2021 18:35:41 GMT+0800 (Taipei Standard Time)
#ES5以前
function sayHi(name){
console.log('hello, ' + name + '! now is ' +new Date())
}
sayHi('Wendy')
#ES6
function sayHi(name){
console.log(`hello ${name} now is ${new Date()}`)
}
sayHi('Wendy')
Destructuring解構
#ES5以前
const arr = [1,2,3,4]
var first = arr[0]
var second = arr[1]
var third = arr[2]
var fourth = arr[3]
console.log(second,third)
#ES6
const arr = [1,2,3,4]
var [first,second,third,fourth]=arr
console.log(second,third)
#ES5以前
const obj={
name: 'wendy',
age:21,
address:'Taiwan'
}
var name = obj.name
var age = obj.age
console.log(name,age)
#ES6
const obj={
name: 'wendy',
age:21,
address:'Taiwan'
}
//名稱一定要對到原本的!
var {name,age,address}=obj
console.log(name,age)
#ES6
const obj={
name: 'wendy',
age:21,
address:'Taiwan',
father:{
name:'Jack',
age:55
}
}
var {father:{name}}=obj
console.log(name)
//Jack
var {father}=obj
console.log(father)
//{ name: 'Jack', age: 55 }
#ES5以前
function test(obj){
console.log(obj.a)
}
test({
a:1,
b:2
})
#ES6
function test({a,b}){
console.log(a)
}
Spread Operator
可以用在陣列或物件。用法如下:
#array
var arr = [1,2]
var arr2 = [3,4,5,arr]
console.log(arr2)
//[3,4,5,[1,2]]
var arr = [1,2]
var arr2 = [3,4,5,...arr]
console.log(arr2)
//[3,4,5,1,2]
#object
var obj1 ={
a:1,
b:2
}
var obj2={
obj1,
c:3
}
console.log(obj2)
//{ obj1: { a: 1, b: 2 }, c: 3 }
var obj1 ={
a:1,
b:2
}
var obj2={
...obj1,
c:3
}
console.log(obj2)
//{ a: 1, b: 2, c: 3 }
搞清楚是"複製"還是"指向記憶體位置"
#指向記憶體位置
var arr = [1,2]
var arr2 = arr
console.log(arr === arr2) //true
#複製
var arr = [1,2]
var arr2 = [...arr]
console.log(arr === arr2) //false
Rest Parameters 反向展開
同樣可以用在陣列或物件,還有function。
#在array 或 object
var arr = [1,2,3,4,5]
var [a,b,...rest] = arr
console.log(rest)
//[ 3, 4, 5 ]
#function
function add(a,b){
return a+b
}
console.log(add(1,2))//3
//等同於
function add(...args){
console.log(args)
return args[0]+args[1]
}
console.log(add(1,2))
//等同於
function add(a,...args){
console.log(args)
return a+args[0]
}
console.log(add(1,2))
default parameter加上預設值
可以用在陣列或物件的解構,還有function。
#解構
const obj = {
a:1
}
const{a=123,b='hi'} = obj
console.log(a,b)//1 hi
#function
function repeat(str = 'hello', times = 5){
return str.repeat(times)
}
//abcabc
//abcabcabcabcabc
//hellohellohellohellohello
Arrow Function
const test = function(n){
return n
}
#可以省略function 改成 =>
const test = (n) => {
return n
}
#若只有一個參數 可以省略()
const test = n => {
return n
}
目前最大的差別如下,可讀性較高較簡潔
var arr = [1,2,3,4,5]
console.log(
arr
.filter(function(value){
return value > 1
})
.map(function(value){
return value*2
})
)
console.log(
arr
.filter(value => value > 1)
.map(value => value * 2)
)
Import & Export
ES6新增功能,與ES5做對照。主程式index.js,函式檔utils.js。
#index.js
#ES5
//method 1
var add = require('./utils')
console.log(add(1,2))
//method 2
var utils = require('./utils')
console.log(utils.add(1,2))
#utils.js
#ES5
function add(a,b){
return a+b
}
//method 1
module.exports = add
//method 2
exports.add = add
#index.js
#ES6
//method 1 : 使用import {} from ''
import {add,PI} from './utils'
console.log(add(1,2))
//method 2
import {addFunction,PI} from './utils' //{}內要對應輸出名稱,e.g.addFuction
console.log(addFunction(1,2))
import {addFunction as a,PI} from './utils' //這裡也可以用as
console.log(addFunction(1,2))
//method 3 : 使用import * from '',一次引入全部
import * as utils from '.utils'
//method 4 :export default function add(),使用 import add 引入,不需要加大括號
import add from './utils'
console.log(add(1,2))
#utils.js
#ES6
//method 1 : 直接在欲輸出東西前加上export
export function add(a,b){
return a+b
}
export const PI = 3.14
//method 2 : 最後放export{}把想輸出東西放入
function add(a,b){
return a+b
}
const PI = 3.14
export{
add as addFunction,//如果不用取別名,as ... 可以省略
PI
}
//method 4 :export default function add(),使用 import add 引入,不需要加大括號
export default function add(a,b){
return a+b
}
Babel
新的語法(例如ES6)可以經由Babel轉換為舊版瀏覽器可相容的語法(e.g.ES5)。有許多
,在這裡練習使用$npx babel-node
這個指令(不用於開發,速度太慢),開發可參照網站指示。
安裝步驟 :
安裝必要套件:
$npm install --save-dev @babel/core @babel/node @babel/preset-env
新增 .babelrc
填入內容,告訴 babel 要用這個 preset:
{
"presets": ["@babel/preset-env"]
}
最後使用 $npx babel-node index.js
即可