[ 學習筆記系列 ] 很基礎的 JavaScript 入門 (二) - ES6 語法與 Module


Posted by ClayGao on 2020-03-20


前言

上一篇筆記了很基本的 JavaScript 常識,這一篇要繼續整理剩下的部分,如 ES6 語法,與模組等等,最後講一下 npm 與 yarn


Module ( 模組 )

概念

將常用的函式獨立成一個模組,以後有需求可以直接引入這個模組來使用。Node.js 其中就有不少內建模組可以使用 ( 內建模組前面不用加 ./ 等檔案路徑 )。  

require ( 拉來用 )

以 Node.js 內建的模組 os 為例,我可以要求使用,並使用裡面的其中一個名為 platform 的方法,官方文件表示這個函式會回傳一個字串來表示你的作業系統平台

var os = require('os')

console.log(os.platform()) // Win32 ( 乾買不起 MAC )

通常變數名稱可以自己改,但習慣上會使用你要引入的該 Module,這樣比較好辨別   

export (借出去用)

我們當然也可以自己建立一個 Module 供別人使用

假設我建立一個檔名叫做 threeTimes.js,我在裡面放一個函式,你可以取用這個模組內的函式功能 - 將帶入的值乘以 3  

第一種輸出方法 ( 好理解 )

// threeTimes.js
function triple(x) {
    return x*3
}

module.export = triple

如此一個可以供人使用的 Module 就完成了,這時我要如何引入 threeTimes.js 呢 ? 

var threeTimes = require('./threeTimes') // 加上檔案路徑,可以不加副檔名 js

console.log(triple(3)) // 9

 

第二種輸出方法

如果你一次要輸出多個函式,不只一個 triple,那你可以用物件把各函式打包送出

// threeTimes.js
function triple(x) {
    return x*3
}

function triple2(x) {
    return (x*3)*2
}

module.exports = {
    fun1 : triple,
    fun2 : triple2
}

你也可以建立一個變數,假設叫做 obj,然後將函式放入物件之中,最後使 module.exports = obj

但是要注意的是在 require 這一端的變數 threeTimes 也會是一個物件,所以使用方法上就會不同,需要使用物件的方式存取

var threeTimes = require('./threeTimes') // 加上檔案路徑,可以不加副檔名 js

console.log(threeTimes.fun1(3), threeTimes.fun2(3)) // 6 18

 

第三種輸出方法

此外,你也可以換一種輸出形式,這樣的輸出也是同上面的物件形式
exports 本身就是一個空的物件,後面所接的就像是 { } 內中的屬性

// threeTimes.js
function triple(x) {
    return x*3
}

function triple2(x) {
    return (x*3)*2
}

exports.fun1 = triple
exports.fun2 = triple2

第一種輸出方法的好處是,module.exports = 之後可以輸出你想輸出的任何東西,如一個字串或陣列,取用端可以直接引用,就如同在同一個檔案底下那樣,相當好理解,你輸出什麼,require 就是什麼。

第二種和第三種方法都是輸出一個物件,相對於取用端來說,也要用取用物件的方式做引用。  

第四種輸出方法 ( ES6 新增 )

也就是 exportimport{}export 你想輸出的東西

輸出端 threeTimes.js 如下

// threeTimes.js
export function triple(x) {
    return x*3
}

export const A = '123'

而取用端可以這樣寫 import {} from './路徑'

import {triple, A} from './threeTimes'

console.log(triple(5)) // 15
console.log(A) // '123'

 

第五種輸出方法

ES6 新增,概念上與第二種方法類似,差別在於這樣的用法也不會使輸出的東西為物件

其實就是第四種輸出方法的另一種形式,將你要輸出的東西用 { } 打包,但它並不是物件

// threeTimes.js
function triple(x) {
    return x*3
}

const A = '123'

export = {
    triple,
    A
}

如果你想使輸出的東西以你想要的新名稱輸出出去,你可以使用 as 為其取別名

// threeTimes.js
function triple(x) {
    return x*3
}

const A = '123'

export = {
    triple as fun1,
    A as str
}

或者你也可以將 as 用在輸入端

import { triple as fun1, A as str } from './threeTimes'

console.log(triple(5))

接著,如果你想引入該 Module 全部有輸出的東西,可以使用 import * as from '<./Module>'

如果這樣做,那麼輸出的部分都會被包成一個名為 的模組,所以如果要存取,必須在前面加上模組名稱

import * as allFunction from './threeTimes'

console.log(allFunction.triple(5)) // 需要先加上 allFunciton 這個模組名稱

 

第六種輸出方法 export default

// threeTimes.js
export default function triple(x) {
    return x*3
}

那取用端就可以這樣寫

import triple from './threeTimes'

console.log(triple(5))

有點像第一種使用方法,可以直接做取用,但更好的理解是,這樣的輸出方法與第四種的差別在於,import 時不用加大括號

// threeTimes.js
export default function triple(x) {
    return x*3
}

export const A = '123'

那取用端就可以這樣寫

import triple, { A } from './threeTimes' // A 要加 {},注意逗號

console.log(triple(5), A)

 

ES6 ( 由於在 2015 年發布,又稱 ES 2015 )

這邊簡單筆記 ES6 語法,但不是全部。  

const / let

const ( 常數 )

你無法對 const 建立的變數重新賦值,但若是被賦予物件型別,你仍然可以更改內中記憶體位置指向的值。  

let

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

變數運行機制會從最內層找到最外層

function abc() {
    var a = 5
    console.log(a)
}

abc()
console.log(a)

得到的結果會是 5 與一個錯誤

原因是因為第三行的 console.log() 找到了函式內層的 a,值為 5

但在函式外層的 console.log() 卻找不到內層的 a,所以顯示錯誤

let 性質

正常來說,JS 的作用域是以函式為邊界,但若是一個變數使用 let 宣告,那該變數的生存範圍就會以離他最近的區域為僅有的生存範圍,可能是 if{} 或 for loop(){}const 也有同樣性質


 

新版字串 - 淘汰舊版字串方法吧 !

多行字串

console.log(
`
    haha
    haha2
    haha3
`
)
/*
印出

  haha
  haha2
  haha3

*/

串接變數

將你想插入字串中的變數 / 物件 / 陣列等等放入 ${},再放入`之中,不用再帶入任何+` 號

var a = 100

console.log( `潮爽德撿到${a}塊內`) // 潮爽德撿到100塊內

 

解構

  • 用途:當你需要將陣列或物件的元素各個拿出來放入變數之中,可以使用

  • 陣列的解構方法:

      var [a,b,c,d,e,f,e] = [1,2,3,4,5,6]
    
      console.log(b) // 2
      console.log(e) // undefined
    

    簡而言之就是一組對一個陣列元素,b 對上 2,e 沒有可以對應的元素,所以是 undefined

  • 物件的解構方法:

      const obj = {
          a : 15,
          b : 27,
          c : 33
      }
    
      var { a,b,c } = obj
    
      console.log(a) // 15
    

    注意,物件解構時變數的名字一定要和物件的元素名稱相同,如 var { a , b } 一定要對應到該物件中的 ab,否則變數會接收不到。

    另外就是解構完之後該變數本身內含物件元素,如果這個物件元素底下還有物件,那你也可以再對該變數進行解構一次,取它的元素。

const obj = {
    a : {
        aa : 55
    },
    b : {
        bb : 66
    },
    c : 33
}

var { a : { aa },b,c } = obj
// 注意 a 與 aa,這邊是解構再解構的意思,別和物件的 {} 搞混
var { bb } = b  
// 經過上一行的解構,已經有個物件變數為 b,所以我再解構這個物件變數一次,和上一行的 a 與 aa 同意義但不同寫法

console.log(aa) // 55 , 這邊的 aa 是個變數
console.log(bb) // 66

 

展開運算子 ...

簡單來說就是在陣列變數前面加上「...」,那麼它會取消掉陣列的 [] 與性質,或是物件的 {},只展現裡面的元素。

var arr = [1,2,3,4]

console.log(...arr) // 1 2 3 4 


function sum(a,b,c,d) {
    return (( a + b + c + d ) * 2)
}
console.log(sum(...arr)) // 20

你也可以用來複製 Array

var arr = [2,3,4]
var arr2 = [...arr]

console.log(arr2) // [2,3,4]

物件也可以使用,見下例子

var obj1 = {
    x:1,
    y:2
}

var obj2 = {
    obj1,
    z:3
}

var obj3 = {
    ...obj1,
    z:3
}

console.log(obj2) // { obj1: { x: 1, y: 2 }, z: 3 }
console.log(obj3) // { x: 1, y: 2, z: 3 }

可以看到 obj2 與 obj3 的差別,很簡單地可以了解,展開運算子之於物件也是解開 {} 的束縛。

若展開之後元素有重疊,則以較之後的元素值為準

var obj1 = {
    x:1,
    y:2
}

var obj3 = {
    ...obj1,
    y:22
}

console.log(obj3) // { x: 1, y: 22 }

用展開運算子複製物件或陣列,會是一個全新的物件或陣列,新的記憶體位置與新的值,所以可以不用擔心在更改過程中改到原本被複製的物件 / 陣列的值。

簡單來說,用展開運算子複製物件 / 陣列,是複製值,也是兩個截然不同的記憶體位置。
而用一般賦值複製,如 obj2 = obj1,是複製兩個相同的記憶體位置,指向同一個值。

 

反向展開 ( Rest Parameters )

var arr = [1,2,3,4]
var [a,...rest] = arr

console.log(rest) // [2,3,4]

通常與解構搭配使用,可以理解為 rest 前的 ... 將剩下的 2,3,4 打包起來給 rest,打包的概念正如同展開的相反,故稱反向展開。

注意 ...rest 只能放在解構的最後一個區塊,你沒辦法在後面再接變數

所以 var [a,...rest, theLast ] = arr 這樣的寫法是行不通的

物件也是同樣寫法

obj1 = {
    a:1,
    b:2,
    c:3
}

var {a , ...obj2} = obj1

console.log(obj2)  // { b: 2, c: 3 }

反向展開也可以用在函式,參考下面兩個例子

function sum(...args) {
    console.log(args)
    return args[0] + args[1]
}

console.log(sum(3,5))
function sum(a,...args) {
    console.log(args)
    return a + args[0]
}

console.log(sum(3,5,50,60,70))

可以發現這樣的用法很像函式的 arguments,差別在於上述例子中使用反向展開的 args 本質是陣列,而之前的 arguments 是長得像陣列的物件

 

default Parameters

可以用在 function 的參數上,可以直接幫參數賦予預設值

function sum(a,b,c = 10) {

    return (a + b)*c
}

console.log(sum(3,5)) // 80

也可以用在解構上,替 = 左邊的變數設定預設值。

 

npm 與 yarn

npm - Node Package Manager

回想 Module 的概念,我們可以寫 Module 供自己使用,然而在世界各地也有不少人分享他們所寫的 Module 在網路上。而 npm 就是我們可以取用這些 Module 的工具,而 npm 在安裝 Node.js 的時候就會連帶安裝。

  • 安裝套件與 node_modules

在 npm 找到你想安裝的套件之後,在 CLI 使用語法安裝 : npm install <套件名稱>
這時候你的專案資料夾底下會多出 node_modules 這個資料夾,它統一集中存放你所安裝的套件

  • package.json

在安裝套件的時候之前,先輸入 npm init 並連續 Enter 跳過那些詢問,然後你可以在你的專案資料夾看到 package.json,這個就很像你的設定檔,裡面也會記錄你所這個專案所使用的 npm 套件有哪些 ( 記錄在 "dependencies" 底下)

所以如果你換了一個新的開發環境,只要持有你專屬的 package.json,然後在 CLI 輸入 npm install,npm 就會自動根據你 package.json 底下所使用的套件紀錄全部下載下來,就不用帶著沉重的套件到處跑了。

所以 node_modules 通常不會納入 Git 控管,我們會將它放入 .gitignore ,我們只要記錄 package.json 即可。

但是你安裝套件的紀錄不會自動幫你寫入 package.json 底下的 dependencies,你必須在安裝的時候使用在最後面加入 --save ( npm install <套件名稱> --save ),那這個套件將會寫入 dependencies 底下;如果你是要安裝在 devDependencies 底下,則是使用 --save-dev ( npm install <套件名稱> --save-dev )。

dependencies 與 devDependencies 的差別在於後者為開發環境才會使用到的套件。

  • package.json 底下的 script 區塊

內中可以設定你想執行的指令名稱,指令名稱後面可以指定你要執行什麼。

 "script" : {
     "start": "node index.js"
 }

這時候輸入 npm run start,就等同於輸入 node index.js

yarn 基本上和 npm 概念都相同,只列出不同的點

可以去官方網站安裝

npm yarn
npm -v yarn -v
npm install yarn install
npm install <套件名稱> yarn add <套件名稱>
npm install <套件名稱> --save yarn add <套件名稱> (即 yarn 會自動將套件寫入 package.json )

 

 結語

個人覺得模組化的部分,在日後使用框架開發都會很常用到,有時也會忘記 exportexport default 的差別,所以偶爾會回來翻資料。

很多一開始的知識不容易記住,都是因為沒有用到,但那也沒關係,曾經有個印象就好,重點是知道要從哪個方向找資料。

而 npm 與 yarn,原本想說 npm 順順用就好,但最近開始轉用 yarn 了,喜新厭舊在這時候好像變好習慣了 ? 雖然 yarn 也不新就是了,但至少用 yarn 安裝 Vue cli 不會出現錯誤 QQ

大概就是這樣了,本篇筆記結束。


#ES6 #javascript #初學者 #新手