# let、const、箭頭函式
## var
`var` 的宣告屬於 **全域變數**,代表 **全域物件的屬性**。
```javascript=
for (var i=0; i<3; i++) {}
console.log(i) // 3
```
假設一個 `for` 迴圈用 `var` 宣告 `i`,在全域還是可以取得 `i` 值。
如果想要限制 `i` 的作用域的話,可以使用立即函式,避免外層取到 `for` 迴圈裡面的值,最直接的方式就是使用 `let` 宣告。
```javascript
(function () {
for (var i=0; i<3; i++) {}
})()
console.log(i) // i is not defined
// ---
for (let i=0; i<3; i++) {}
console.log(i) // i is not defined
```
在 JavaScript 中,`var` 的作用域是函式作用域,但在函式內的區域變數也可能被視為全域變數,傳統用 `var` 宣告變數可能會汙染全域,如果使用 `let`、`const` 就可以避免這種情形。
```javascript=
if (true) {
var a = 10
}
console.log(a) // 10
console.log(window.a) // 10
```
## let
`let` 的作用域屬於區塊內({} 大括號),和 `const` 最大差異就是可以重新賦值,而 `const` 不行。
```javascript=
let a = 0
{
let a = 1 // 因為 let 的作用域是區塊內,所以不算重複宣告
a = 2
console.log(a) // 2
}
console.log(0) // 0
```
舉個非同步 `for` 迴圈的例子,假設想要讓 `setTimeout` 依序執行並印出結果,如果用 `var` 宣告的話,因為 `var` 是全域變數,所以只會取出最後一次執行的結果。
而 `setTimeout` 是非同步程式,會先放到「事件佇列」裡面,等到所有程式碼執行完畢後才會執行。
如果想要達到預期結果,只要用 `let` 宣告就可以依序執行並取得正確數值。
```javascript=
for (var i=0; i<3; i++) {
setTimeout(function () {
console.log(`這是第 ${i} 次執行結果`)
}, 0)
}
// 這是第 3 次執行結果
```
```javascript=
for (let i=0; i<3; i++) {
setTimeout(function () {
console.log(`這是第 ${i + 1} 次執行結果`)
}, 0)
}
// 這是第 0 次執行結果
// 這是第 1 次執行結果
// 這是第 2 次執行結果
```
## const
`const` 是宣告一個常數,用 `const` 宣告的變數沒辦法再被調整。
雖然變數沒辦法被調整,但如果是物件的話,因為物件屬於傳參考特性,只要不直接替換掉物件,可以修改內容屬性值。
```javascript=
const person = {
name: '小明',
money: 500
}
person.name = '大明'
console.log(person.name) // 大明
// ---
person = {}
console.log(person) // Assignment to constant variable.
```
## Hoisting 與 暫時性死區
JavaScript 中有 hoisting 的現象,先來比較一下這三種的狀況:
```javascript=
console.log(a) // undefined
var a = 1
// ---
console.log(b) // b is not defined
let b = 2
// ---
console.log(c) // Cannot access 'c' before initialization
const c = 3
```
1. var: `undefined`,有 hoisting
2. let: `not defined`,沒有 hoisting
3. const: 宣告前無法使用,沒有 hoisting
看起來沒什麼問題,接著猜測一下面範例結果:
* (1) 如果 `let` 沒有提升的話,就會往外層查找取得 **小明** 的值
* (2) 如果 `let` 有提升的話,就會出現其他狀況
```javascript=
let Ming = '小明'
{
console.log(Ming)
let Ming = '大明'
}
```
結果是(2): `Cannot access 'Ming' before initialization`
`let` 在提升的過程中會產生一個「**暫時性死區**」Temporal Dead Zone(TDZ)。
在這個區域內是無法存取變數,也就不會賦予 `undefined` 的值,如果試圖取值就會跳出錯誤提示。
所以 `let` 雖然有類似提升 **創造 - 執行階段** 的概念,但是和 `var` 的 hoisting 概念並不相同。
```javascript=
let Ming = '小明'
{
// 創造
let Ming // 暫時性死區 TDZ
// 執行
console.log(Ming)
let Ming = '大明'
}
```
## 重點整理
||作用域|重複宣告|重新賦值|Hoisting|
|:---:|:---:|:---:|:---:|:---:|
|var | 函式 | v | v | v |
|let | 區塊 | x | v | TDZ |
|const | 區塊 | x | x | x |
## This 與 箭頭函式
### 箭頭函式沒有 arguments
在傳統函式裡,在執行的時候會自動帶上 arguments 的參數,並且屬於類陣列。
但是在箭頭函式並沒有 arguments,因此會顯示 `arguments is not defined`。
```javascript=
const fn = () => {
console.log(arguments) // arguments is not defined
}
fn(1, 2, 3)
```
可以使用 `...args` 來取得函式的所有參數。
```javascript=
const fn = (...args) => {
console.log(args) // [1, 2, 3]
}
fn(1, 2, 3)
```
### 箭頭函式沒有自己的 This
在傳統函式中,this 取決於呼叫/調用的方式
```javascript=
var scope = 'global'
var obj = {
scope: 'local',
fn: function() {
console.log(this.scope) // local
setTimeout(function() {
console.log(this.scope) // global
}, 10)
}
}
obj.fn()
```
在箭頭函式中因為沒有自己的 This,所以會調用外層作用域的 this
```javascript=
var scope = 'global'
var obj = {
scope: 'local',
fn: function() {
console.log(this.scope) // local
setTimeout(() => {
console.log(this.scope) // local
}, 10)
}
}
obj.fn()
```
如果把 `obj` 裡面的 `fn` 也改成箭頭函式,就會繼續往外層找,找到全域 scope
```javascript=
var scope = 'global'
var obj = {
scope: 'local',
fn: () => {
console.log(this.scope) // global
setTimeout(() => {
console.log(this.scope) // global
}, 10)
}
}
obj.fn()
```
### 無法透過 call、apply、bind 重新給予 this
```javascript=
var scope = 'global'
var obj = {
scope: 'local'
}
var fn = (para1, para2) => {
console.log(this, para1, para2)
}
const fn2 = fn.bind()
fn.call(obj, 1, 2) // window, 1, 2
fn.apply(obj, [1, 2]) // window, 1, 2
fn2(obj, 1, 2) // window, 1, 2
```
### 箭頭函式沒有 protorype 無法作為建構函式
```javascript=
const fn = function(a) {
this.item = a
}
const fnArr = (a) => {
this.item = a
}
console.log(fn.prototype, fnArr.prototype)
// {constructor: f}, undefined
```
```javascript=
const a = new fn('a')
console.log(a)
// fn { item: 'a' }
```
```javascript=
const arr = new fnArr('arr')
consol.log('arr')
// fnArr is not a constructor
```
### 常見陷阱題 :zap:
#### 箭頭函式無法作為物件實字
```javascript=
const fn = () => { data: 1 }
const fn2 = () => ({ data: 1 })
console.log(fn()) // undefined
console.log(fn2()) // { data: 1 }
```