TsaiChihWei / learning-blog

1 stars 0 forks source link

[筆記] ES6 (ES2015) #11

Open TsaiChihWei opened 4 years ago

TsaiChihWei commented 4 years ago

ES6 宣告變數方式 const 與 let

let

用來宣告一個變數像 var 一樣,不過 let 是塊級作用域, var 是函數作用域。

const

const 用來宣告一個常數且一定要賦值,其值不能再藉由指派運算子(例如等號運算子)進行變動,否則報錯。常數名稱可以用大寫表示以利區分:

//宣告常數一定要賦值
const A //Uncaught SyntaxError: Missing initializer in const declaration
const PI = 3.14
PI = 20
console.log(PI) // output to be => TypeError: Assignment to constant variable.

注意!當你用 const 宣告物件時有例外情況:

const obj = {
  b: 10
}

obj.b = 20
console.log(obj.b) // output to be 20

const arr = [1, 2, 3]
arr.push(4, 5)
console.log(arr) // output to be [1, 2, 3, 4, 5]

因為變數存取物件(包含陣列)的方式是存取記憶體位置,原則上此 obj 物件的記憶體位置(指引)沒有被更動,被更動的是指引指向的值。

回顧一下: 從 Object 的等號真正的理解變數從博物館寄物櫃理解變數儲存模型

不過若你用 const 宣告了一個物件,當你想要修改這個物件的內容時(再將其他值指派給這個變數),JavaScript 引擎會認為你要創建新的物件,所以就會有新的記憶體位址,這樣常數就會被改變,所以會報錯:

const obj = {a: 1}
obj = {a: 2} //Uncaught TypeError: Assignment to constant variable.

//將其他值再指派給 obj 會有一個新的記憶體位址,

let 與 const 的共同特性

作用域 scope

letconst 是區塊作用域 (block scope) {} 包起來的區域,var 則是函式作用域 (function scope)。

使用 var 宣告變數 a 時,可用範圍在 test function 內,即便 console.log(a)if block 之外仍能讀取到 a 的值:

function test() {
    if (true) {
        var a = 10
    } 
    console.log(a);
}

test() // output to be 10

使用 letconst 宣告變數 a 時,可用範圍在 if block (大括號) 內,所以 console.log(a) 在離開 if block 的區域便無法讀取到 a 的值:

function test() {
    if (true) {
        let a = 10
    } 
    console.log(a)
}

test() // ReferenceError: a is not defined

仍然會提升 (Hoisting)

使用 letconst 宣告變數,在創造階段變數一樣會被放入記憶體中,但沒有初始化為 undefined。到了執行階段,賦值之前嘗試取用的話會發生錯誤,這一段不能取用變數的期間稱為暫時性死死區 (Temporal Dead Zone)。

不會出現在全域物件 window 裡面

var a = 1
const b = 1
let c = 1

console.log(window.a) //1
console.log(window.b) //undefined
console.log(window.c) //undefined

不能重複宣告

let a = 1
let a = 3 //Uncaught SyntaxError: Identifier 'a' has already been declared
const b = 2
const b = 3 //Uncaught SyntaxError: Identifier 'b' has already been declared

因應 ES6 的出現,使用上建議不要再用 var 來宣告變數,優先使用 const,若須重新賦值再使用 let ,藉由限縮變數的活動範圍來減少發生錯誤的可能。

TsaiChihWei commented 4 years ago

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

test() // output to be 10

使用 `var` 宣告變數 a 時,可用範圍在 test function 內,即便 `console.log(a)` 在 `if` block 之外仍能讀取到 a 的值。
``` js
function test() {
    if (true) {
        let a = 10
    } 
    console.log(a)
}

test() // ReferenceError: a is not defined

使用 let 宣告變數 a 時,可用範圍在 if block (大括號) 內,所以 console.log(a) 在離開 if block 的區域便無法讀取到 a 的值。

function test() {
    if (true) {
        const a = 10
    } 
    console.log(a)
}

test() // ReferenceError: a is not defined

constlet 同理,都是區塊作用域 (block scope)。

因應 ES6 的出現,使用上建議不要再用 var 來宣告變數,改用 letconst,限縮變數的活動範圍來減少發生錯誤的可能。

TsaiChihWei commented 4 years ago

模板字串 Template Literals

this is line one this is line two */ // 反斜線n \n 為換行

//Template Literals 寫法 let newStr = this is line one this is line two console.log(newStr) /* output to be =>

this is line one this is line two */

### 嵌入變數或任何表達式
``` js
//傳統寫法
let name1 = 'Jack'
let age1 = 25
console.log('His name is ' + name1 + 'and he is ' + age1 + ' years old.')
// output to be => His name is Jack and he is 25 years old.

//Template Literals 寫法
let name2 = 'Ryan'
let age2 = 23
console.log(`His name is ${name2} and he is ${age2} years old.`)
// output to be => His name is Ryan and he is 23 years old.

//加入表達式
//${} 中可以是任何 JavaScript expression
function sayHello(name) {
    return `Hello ${name.toUpperCase()}!`
}
console.log(sayHello('Maggie')) //output to be => Hello MAGGIE!
TsaiChihWei commented 4 years ago

解構賦值 Destructuring

解構賦值 ( Destructuring Assignment)是一個在 ES6 的新特性,目的用於提取陣列或物件中的資料變成獨立變數

陣列解構賦值

console.log(first, second, third, fourth) // 1 2 3 4

+ ES6 解構寫法
``` js
let arr = [1, 2, 3, 4]
let [first, second, third, fourth] = arr

console.log(first, second, third, fourth) // 1 2 3 4

其他案例

註:陣列解構賦值會將右方的資料與左邊對應,一個位置對應一個值。

let [x, , ,y, z] = [5, 6, 7, 8] console.log(x, y, z) // 5 8 undefined

+ 情況四:字串拆解
``` js
let str = 'Ryan'
let [a, b, c, d] = str

console.log(a, c) // R a
TsaiChihWei commented 4 years ago

物件解構賦值

物件的解構賦值強調的是屬性名稱,屬性名稱必須與變數名稱相互對應才能取到值,反之則會無法取值。

let gender = person.gender let firstName = person.firstName let lastName = person.lastName console.log(gender) // male console.log(firstName) // Leonardo console.log(lastName) // Dicaprio

+ ES6 解構寫法
``` js
let person = {
    gender: 'male',
    firstName: 'Leonardo',
    lastName: 'Dicaprio'
}

//一次宣告三個變數
let {gender, firstName, lastName} = person
console.log(gender) // male
console.log(firstName) // Leonardo
console.log(lastName) // Dicaprio

其他案例

let {gender, first, last} = person console.log(gender) //male console.log(first) //undefined console.log(last) //undefined

+ 情況二:將變數名稱重新命名 (不想以屬性名稱當作變數的時候)
``` js
let person = {
    gender: 'male',
    firstName: 'Leonardo',
    lastName: 'Dicaprio'
}

let {gender, firstName: first, lastName: last} = person
console.log(gender) //male
console.log(first) //Leonardo
console.log(last) //Dicaprio

使用冒號 : 後面接新的變數名稱

let {name} = person console.log(name) //{ firstName: 'Leonardo', lastName: 'Dicaprio' }

let {name: {firstName, lastName}} = person console.log(firstName) //Leonardo console.log(lastName) //Dicaprio

冒號 `:` 後面再使用大括號即再解構一次
+ 情況四:解構再解構再加上重新命名變數名稱
``` js
let person = {
    gender: 'male',
    name: {
        firstName: 'Leonardo',
        lastName: 'Dicaprio'
    }
}
let {name: {firstName: one, lastName: two}} = person
console.log(one) //Leonardo
console.log(two) //Dicaprio

test({ a: 1, b: 2 }) // output to be 1 2

/* 原本的寫法為: function test(obj) { console.log(obj.a, obj.b); }

test({ a: 1, b: 2 }) */


更多解構使用在函式參數定義中的詳細說明,可參考此兩篇文章:[MDN 解構賦值](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 和 [Day 08: ES6篇 - Destructuring Assignment(解構賦值)](https://ithelp.ithome.com.tw/articles/10185430?sc=pt)。
TsaiChihWei commented 4 years ago

加上預設值 Default Parameters

在解構賦值時使用預設值

將物件 obj 解構的情況:

const obj = {
  a: 1, 
  b: 2
}
const {a, b} = obj
console.log(a, b) // 1 2

若 obj 中只有 b 的時候:

const obj = {
  b: 2
}
const {a, b} = obj
console.log(a, b) //undefined 2

a 為 undefined,因為物件 obj 中沒有相對應的屬性名稱 a,這時候可以替 a 加上預設值:

const obj = {
  b: 2
}
const {a = 123, b} = obj
console.log(a, b) //123 2

若已經有賦值,則以賦的值為優先,否則將輸出預設值

const obj = {
  a: 1
}
const {a = 123, b = 456} = obj
console.log(a, b) //1 456

在函數的參數定義中使用預設值

假設我們寫了一個非常簡易的 sayHi function,情況如下:

function sayHi(name) {
    return `Hi, ${name}`
}

console.log(sayHi()) ////Hi, undefined

替 name 加上預設值:

function sayHi(name = 'Leo') {
    return `Hi, ${name}`
}

console.log(sayHi()) ////Hi, Leo
console.log(sayHi('Maggie')) ////Hi, Maggie
TsaiChihWei commented 4 years ago

展開運算子 Spread Operator

let arr3 = [...arr1, 4, 5, 6] console.log(arr3) //[ 1, 2, 3, 4, 5, 6 ]

let arr4 = [4, 5, ...arr1, 6] console.log(arr4) //[ 4, 5, 1, 2, 3, 6 ]

+ 可以用來複製陣列
``` js
let arr1 = [1, 2 ,3]
let arr2 = [...arr1]
console.log(arr2) //[1, 2, 3]
console.log(arr1 === arr2) //false
//arr2 是一個新陣列並不影響 arr1,但還有以下情形:

let newArr = [4]
let arrA = [...arr1, newArr]
// console.log(arrA) //[ 1, 2, 3, [ 4 ] ]

let arrB = [...arrA]
console.log(arrB) //[ 1, 2, 3, [ 4 ] ]

console.log(arrA === arrB) //false
console.log(arrA[3] === arrB[3]) //true
//arrA[3] 與 arrB[3] 還是指向同一個記憶體位置,因為 [4] 是一個陣列(物件)

let arr = [1, 2, 3] console.log(add(...arr)) //6

TsaiChihWei commented 4 years ago

其餘運算子 Rest Operator

//可自定義使用其餘運算子的變數名稱 let [one, two, ...others] = [1, 2, 3, 4, 5, 6] console.log(others) // [ 3, 4, 5, 6 ]

let [a, b, ...theRestOnes] = ['hi', 'hello', 'hey', 'hoo'] console.log(theRestOnes) //[ 'hey', 'hoo' ]

//注意!其餘運算子只能放在最後一位,並且只能有一個其餘參數,否則會出現錯誤 let [five, six, ...middle, lastOne] = [5, 6, 7 ,8, 9] console.log(middle) //SyntaxError: Rest element must be last element

//當右邊的值與左邊變數數量不相等時,用了其餘運算子的那個變數,就會變成一個空陣列 let [x, y, ...z] = [1] console.log(x) //1 console.log(y) //undefined console.log(z) // []

+ 使用在函式的參數定義中 (不確定的傳入參數值有幾個的時候),又稱「其餘參數 (Rest Parameters)」。
``` js
function sum(...others) {
    let total =0
    for (let i=0; i<others.length; i++) {
        total += others[i]
    }
    return total
}

console.log(sum(1, 2, 3, 4, 5)) //15

//其餘參數的值在沒有傳入實際值的時候,會變為一個空陣列,而不是undefined
function test(x, ...y) {
    console.log('x =', x, ', y =', y);
}

test(1, 2, 3) //x = 1 , y = [ 2, 3 ]
test(1) //x = 1 , y = []
test() //x = undefined , y = []
TsaiChihWei commented 4 years ago

小結 (懶人包)

展開運算子 Spread Operator:

TsaiChihWei commented 4 years ago

物件 上使用展開運算子與其餘運算子

此內容引用自 [ES6-重點紀錄] 擴展運算子 Spread OperatorDay 09: ES6篇: Spread Operator & Rest Operator(展開與其餘運算符)

  • 上面都只有談到與陣列搭配使用,但根據 Object Rest/Spread Properties 的內容,其實物件也能夠使用這個運算子 ...,只是目前還未正式加入 ES6 的標準中。這些都是還在制定中的 ES7 之後的草案標準,稱為展開屬性 (Spread Properties) 與其餘屬性 (Rest Properties)。
  • 使用方式基本上與陣列大同小異

    展開屬性 (Spread Properties) 使用案例:

  • 串聯物件
    
    let obj1 = {
    a: 1,
    b: 2
    }

let obj2 = { ...obj1, c: 3 } console.log(obj2) //{ a: 1, b: 2, c: 3 }

//遇到有相同屬性名的,合併後只會使用最後一個物件的內容值 let objOne = { x: 5, y: 6, z: 7 }

let objTwo = { y: 10 }

let newObj = { ...objOne, ...objTwo }

console.log(newObj) //{ x: 5, y: 10, z: 7 }


+ 複製物件
``` js
let obj1 = { a: 1, b: 2 }
let obj2 = { ...obj1 }
console.log(obj2) //{ a: 1, b: 2 }

console.log(obj1 === obj2) //false

其餘屬性 (Rest Properties) 使用案例:

let { a, ...obj2 } = obj1 console.log(a) //1 console.log(obj2) //{ b: 2, c: 3 } 物件

//這邊一樣可以自定義使用其餘運算子的變數名 (這邊用 rest) let { ...rest } = obj1 console.log(rest) //{ a: 1, b: 2, c: 3 } console.log(rest === obj1) //false

TsaiChihWei commented 4 years ago

箭頭函式

此篇內容引用自 MDN 箭頭函式重新認識 JavaScript: Day 10 函式 Functions 的基本概念

箭頭函式運算式 (arrow function expression) 為函式運算式的簡短語法。它沒有自己的 this、arguments、super、new.target 等語法。本函式運算式適用於非方法的函式,但不能被用作建構式 (constructor)。

基本語法:

(參數1, 參數2, …, 參數N) => { 陳述式 }

(參數1, 參數2, …, 參數N) => { return 表示式 }
//相當於 (參數1, 參數2, …, 參數N) => 表示式

// 只有一個參數時,括號才能不加:
(單一參數) => { 陳述式 }
單一參數 => { 陳述式 }

//若無參數,就一定要加括號: () => { statements }

範例:

const square = (n) => {return n * n}

等同於函式運算式(匿名函式)的寫法:

const square = function(n) {return n * n}

若只有一個參數可省略括號:

const square = n => {return n * n}

若箭頭函式裡的 {} 只有一句 return 語句的時候,可以將 return{}省略:

const square = n =>  n * n

其他範例:

這是一個函式運算式(匿名函式):

let arr = [1, 2, 3, 4, 5]
let newArr = arr.map(function (x) { return x * x })
console.log(newArr) //[ 1, 4, 9, 16, 25 ]

可簡化成箭頭函式:

let arr = [1, 2, 3, 4, 5]
let newArr = arr.map(x => x * x)
console.log(newArr) //[ 1, 4, 9, 16, 25 ]

小結

箭頭函式就是函式運算式(匿名函式)的懶人寫法。

TsaiChihWei commented 4 years ago

import 與 export

註:在 Node.js <= v13 的版本都還無法直接支援 import 與 export 的用法。

假設現在有兩個 JS 檔案在同一資料夾底下, main.jsmodule.js

//in module.js
function add(a, b){
    return a + b
}

const pi = 3.14

我們想要將 module.jsadd functionpi 輸出 (export) 出去,可以直接在前面加上 export 語句:

//in module.js
export function add(a, b){
    return a + b
}

export const pi = 3.14

main.jsmodule.jsadd functionpi 引入 (import):

//in main.js
import {add, pi} from './module'
console.log(add(3, 5)) //8
console.log(pi) //3.14

其他使用方法

也可以將想要輸出 (export) 的模組整合成一個區塊 {}

//in module.js
function add(a, b){
    return a + b
}
const pi = 3.14

export{
    add, 
    pi
}
//in main.js
import {add, pi} from './module'
console.log(add(3, 5)) //8
console.log(pi) //3.14

更改輸出模組的名稱,使用 as 後面接新名稱:

//in module.js
function add(a, b){
    return a + b
}
const pi = 3.14

export{
    add as sum, 
    pi
}
//in main.js
//記得引入這邊的名稱也要改成新的
import {sum, pi} from './module'
console.log(sum(3, 5)) //8
console.log(pi) //3.14

或是直接在引入 (import) 時更改模組名稱:

//in module.js
function add(a, b){
    return a + b
}
const pi = 3.14

export{
    add, 
    pi
}
//in main.js
import {add as sum, pi} from './module'
console.log(sum(3, 5)) //8
console.log(pi) //3.14

一次引入所有模組 (當模組很多的時候),使用 import * as [module name]

//in module.js
function add(a, b){
    return a + b
}
const pi = 3.14

export{
    add, 
    pi
}
//in main.js
//這邊的 myModule 可自行定義任何名稱
import * as myModule from './module'
console.log(myModule.add(3, 5)) //8
console.log(myModule.pi) //3.14

export default 預設輸出的用法:

註:一個 JS 檔案只能有一個 export default。

//in module.js
export default function add(a, b){
    return a + b
}
export const pi = 3.14
//in main.js
//注意這邊因為 export default 的關係,可以用任意名字 import 原先的 add function
//這邊沿用 add,並且不用加上大括號
import add, {pi} from './module'
console.log(add(3, 5)) //8
console.log(pi) //3.14

------
//另一種 import 方法
import {default as add, pi} from './module'
console.log(add(3, 5)) //8
console.log(pi) //3.14