---
# System prepended metadata

title: Vue3 新手夏令營 - 課前章節 JS 必備觀念
tags: [Vue3 新手夏令營]

---

---
tags: Vue3 新手夏令營
---

# Vue3 新手夏令營 - 課前章節 JS 必備觀念

## this 指向

### 傳統函式
this 的指向相當複雜
在傳統函式中大部分(幾乎95%)都是與調用的方式有關
主要看的是，在調用該函式時，函式的前方是否帶有物件

以下使用各種範例做說明：

- 一個函式中包含多少參數，請參考以下程式碼。
```javascript=
var a = '全域'
function fn(params) {
  console.log(params, this, window, arguments);
  debugger;
}
// 上述可知 一個函式中包含 console.log 中這四個參數，
// 且在該範例中 this 指向 全域物件
```
- this 的指向為何，請參考以下程式碼。
```javascript=
var obj = {
  name: '小明',
  fn: function(params) {
    console.log(params, this, window, arguments);
    debugger;
  }
}
obj.fn()
// 這裡是用 obj.fn 去調用該函式 所以此時的 this 會指向 obj
```
- 傳統函式中的 this 只與調用方式有關，請參考以下程式碼。
```javascript=
var someone = '全域';
function callSomeone() {
  console.log(this.someone);
}
callSomeone(); // 此為簡易呼叫，詳見下方補充。
// 函式前方不帶任何物件 所以此時 this 指向全域物件
```
> 【補充】
> simple call(簡易呼叫)：函式前方不帶任何物件，這種呼叫方式就是 simple call(簡易呼叫)。

- 各種運用變化
1. 把函式放在物件裡面宣告，請參考以下程式碼。
```javascript=
var obj = {
  someone: '物件',
  callSomeone() {
    console.log(this.someone); 
  }
}
obj.callSomeone();
// 這裏用 obj.callSomeone 呼叫函式 所以 this 指向 obj
```
2. 把函式寫在全域中 再到物件裡獲取 後通過物件調用函式，請參考以下程式碼。
```javascript=
function callSomeone() {
  console.log(this.someone); 
}

var obj2 = {
  someone: '物件2',
  callSomeone
}
obj2.callSomeone();
// 這裏用 obj2.callSomeone 呼叫函式 所以 this 指向 obj2
```
3. 把函式寫在全域中 再到物件裡獲取 後通過物件調用函式，看誰調用就是指向誰，請參考以下程式碼。
```javascript=
function callSomeone() {
  console.log(this.someone); 
}

var wrapObj = {
  someone: '外層物件',
  callSomeone,
  innerObj: {
    someone: '內層物件',
    callSomeone,
  }
}
wrapObj.callSomeone();
// 這裏用 wrapObj.callSomeone 呼叫函式 所以 this 指向 wrapObj
wrapObj.innerObj.callSomeone();
// 這裏用 wrapObj.innerObj.callSomeone 呼叫函式 所以 this 指向 wrapObj.innerObj
```
4. 把函式寫在全域中 再到物件裡的方法中調用該函式 最後一樣通過物件調用函式，請參考以下程式碼。
```javascript=
function callSomeone() {
  console.log(this.someone); 
}

var obj3 = {
  someone: '物件 3',
  fn() {
    callSomeone(); // 補充：平常不會這樣去取用 this
  }
}
obj3.fn();
// 這裡因為 callSomeone 被調用時前方不帶任何物件所以 this 指向全域
```
5. 使用 setTimeout 回調函式調用，知識點為『回調函式大多是簡易呼叫』，請參考以下程式碼。
```javascript=
var obj4 = {
  someone: '物件 4',
  fn() {
    setTimeout(function () {
      console.log(this.someone);
    });
  }
}
obj4.fn();
// setTimeout 是 callback function(回調函式)
// 而 callback function(回調函式)大部分都是簡易呼叫
// 所以 setTimeout 的 this 會指向到全域
```

### 箭頭函式

箭頭函式沒有自己的 this
箭頭函式的 this 會指向它外層作用域的 this
當箭頭函式的外層找不到任何函式時，箭頭函式的 this 就會指向全域

以下使用各種範例做說明：

- 箭頭函式的縮寫，請參考以下程式碼。
```javascript=
const arr = [1, 2, 3, 4, 5];

const filterArr = arr.filter(item => item % 2); // 這裡在取有餘數的值（單數）
console.log(filterArr); // 輸出結果是 [1, 3, 5]
// 箭頭函式寫法：首先把傳統函式改成箭頭函式 只需要把 function 拿掉 並在 () 後方加入箭頭 => 即可
// 當沒有參數時 就寫 () => {...}
// 一個參數時可寫成 item => {...}
// 多個參數時寫 (a, b) => {...}
// 當函式中是 return 某個簡單的東西可以省略 return 與 {} 並將其改寫成一行 如上代碼
```
- This 綁定的差異，請參考以下程式碼。
```javascript=
// 活用觀念，將內層改為箭頭函式
var name = '全域'
const person = {
  name: '小明',
  callName: function () { 
    console.log('1', this.name);
    setTimeout(() => {
      console.log('2', this.name);
      console.log('3', this);
    }, 10);
  },
}
person.callName();
// 已知在不改成箭頭函式時 setTimeout 中的 this 會指向全域
// 但假設把 setTimeout 改成箭頭函式(如上) this 就可以指向外層函式的 this 即指向 person
```
- 陷阱題1，請參考以下程式碼。
```javascript=
var name = '全域'
const person = {
  name: '小明',
  callName: () => { 
    console.log(this.name);
  },
}
person.callName();
// 首先看到箭頭函式 就知道 this 會指向他外層的 this
// 但這裡外層沒有其他函式 表示外層直接就是全域 所以 this 會指向全域
```
- 陷阱題2，請參考以下程式碼。
```javascript=
var name = '全域'
const person = {
  name: '小明',
  callMe() {
    const callName = () => { 
      console.log(this.name);
    };
    callName();
  }
}
person.callMe();
// 承上題 看到箭頭函式 就找外層 這裡外層是 callMe
// callMe 的 this 指向 person 所以 callName 就也指向 person
```
- 實戰手法，確保 this 指向 obj4 的兩種方式
1. 把 this 先指向其他變數 如 `const vm = this;` 這個 vm 在 Vue 中意指 ViewModel，請參考以下程式碼。
```javascript=
var someone = '全域';
var obj4 = {
  someone: '物件 4',
  fn() {
    const vm = this;
    setTimeout(function () {
      console.log(vm.someone);
    });
  }
}
obj4.fn();
// 在開頭有先宣告 vm = this 所以 vm 會指向 obj4
```
2. 直接使用箭頭函式，請參考以下程式碼。
```javascript=
var someone = '全域';
var obj4 = {
  someone: '物件 4',
  fn() {
    setTimeout(() => {
      console.log(this.someone);
    });
  }
}
obj4.fn();
// 箭頭函式看外層的 this，所以看 fn 的 this，就會指向 obj4
```

## 關注點分離

關注點分離主要就是把資料跟畫面做分離
這裏以原生 JS 實作 todo list 來說明
主要會分成三大類別：
畫面(HTML)
資料(data)
方法(幫畫面與資料做溝通的各種 function)

以下開始撰寫程式碼：
>首先我們用物件的方式來建立元件 component
>component 的結構會有以下部分：
>- 資料(data)
>- 方法、觸發器(render, delItem)
>- 生命週期(init)

```javascript=
const component = {
  data: [ // 資料
    "第一個資料",
    "第二個資料",
    "第三個資料",
  ],
  delItem(id) { // 事件觸發器
    this.data.splice(id, 1);
    this.render();
  },
  render() { // 渲染畫面的方法
    const list = document.querySelector("ul");
    let str = "";
    this.data.forEach((item, i) => {
      str += `<li>${item} <button type="button" class="del" data-id="${i}">刪除</button></li>`;
    });
    list.innerHTML = str;
    const btns = document.querySelectorAll(".del");
    btns.forEach((item) => {
      item.addEventListener("click", (e) => { // 這裏要記得用箭頭函式確保你取得的 this 指向 component
        this.delItem(e.target.dataset.id);
      });
    });
  },
  init() { // 生命週期，進入畫面時第一次會觸發的方法
    this.render();
  },
};
component.init();
```
基本上 Vue 會接觸到的結構就是資料、事件觸發器以及生命週期，
渲染方法會透過 Vue.js 去處理，所以在 Vue.js 基本上是不會用到渲染方法的。
以上就是關注點分離的一個基礎概念。

## 物件參考的特性

以下使用各種範例做說明：

- 物件是以傳參考的形式賦值，請參考以下程式碼。
```javascript=
const person = {
  name: '小明',
  obj: {}
}
const person2 = person; // 首先我們建立一個新物件等於原物件
person2.name = '杰倫'; // 然後修改他的屬性
console.log(person2 === person) // 輸出結果為 true 
// 物件傳參考的特性會讓兩個物件指向同一個記憶體位置
// 假設我們又建立一個 obj2 = person.obj 如下：
const obj2 = person.obj;
obj2.name = 'person > obj > name';
// 此時輸出 person 就會發現 person.obj 同樣增加了 name 屬性 
// 且值為 'person > obj > name'
```
- 陷阱（常見的錯誤）請參考以下程式碼。
```javascript=
const fn = (item) => {
  item.name = '杰倫';
}
const person = {
  name: '小明',
  obj: {}
}
fn(person);
console.log(person); // name 變成杰倫了 這也是由於物件傳參考的特性
```
>【補充】
>關於這樣的錯誤，ESLint 也有建議不要這麼做（但你是有經驗的開發者時，下方也有提供關閉的方式）
>https://cn.eslint.org/docs/rules/no-param-reassign
- 解決方案
1. 淺層拷貝，請參考以下程式碼。
```javascript=
const person = {
  name: '小明',
  obj: {}
}
// 方法一 複製某物件的所有內容到新的物件身上
const person2 = Object.assign({}, person);

// 方法二 用展開把物件拷貝過來
const person3 = {...person};

// 以上兩方式都屬於淺拷貝
// 淺層拷貝是指他只會複製第一層的內容 第二層起 若也是物件 就還是傳參考的特性
```
2. 深層拷貝，請參考以下程式碼。
```javascript=
const person = {
  name: '小明',
  obj: {}
}
const person2 = JSON.parse(JSON.stringify(person));
// 深層拷貝的方式為 把物件先轉成字符串 再把字符串轉回物件
// console.log(person === person2) 會輸出 false 兩者已無任何關聯
```

## Promise

以下使用各種範例做說明：
- 非同步的觀念
```javascript=
function getData() {
  setTimeout(() => {
    console.log('... 已取得遠端資料');
  }, 0);
}

const component = {
  init() {
    console.log(1);
    getData();
    console.log(2);
  }
}
component.init();
// 上述執行順序會是 1 > 2 > ... 已取得遠端資料
// 原因是 setTimeout 屬於非同步行為
// 而 JS 中的所有非同步行為都會在最後才執行
// 更正確的說法，Promise 是為了解決傳統非同步語法難以建構及管理的問題，如有興趣可搜尋 "callback hell js"(JS 回調地獄)
```
- Promise 實例，請參考以下程式碼。
```javascript=
// 在此不需要學習 Promise 的建構方式，僅需要了解如何運用即可
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}
// 首先建立一個函式 該函式回傳一個 promise 的方法
// promise 中會傳入兩個參數 分別是 resolve 跟 reject 他們各自會回傳一個結果
// 我們可以看到在建立的函式裏有傳入一個 status 參數，他表示一個狀態
// 基本上執行過程就是判斷這個 status 狀態
// 當 status 狀態為成功時會執行 resolve，失敗則執行 reject
// 成功可用 .then() 執行後續代碼，失敗則只能用 .catch() 接收
```
- 基礎運用，請參考以下程式碼。
```javascript=
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}

promiseSetTimeout(true)
  .then(function(res) {
    console.log(res);
  })
// 調用 promiseSetTimeout 函式 然後傳入 status 為 true
// 此時會返回 resolve 結果 我們可以通過 .then() 來接收該結果
// 這裡設回傳的結果為參數 res 然後使用 console.log(res) 查看書處內容
// 最後就會得到『promiseSetTimeout 成功』
```
- Promise 的串接方法，請參考以下程式碼。
```javascript=
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}

promiseSetTimeout(true)
  .then(function(res) {
    console.log(1, res);
    return promiseSetTimeout(true);
  })
  .then(function(res) {
    console.log(2, res);
  })
// 我們在 .then() 中可以使用 return 帶入另一個非同步行為
// 接著就可以繼續使用 .then() 來做串接
// 此時輸出就會是 1 promiseSetTimeout 成功 > 2 promiseSetTimeout 成功
// 這樣做他就可以實現依序執行，先執行第一次調用，再執行 return 的非同步行為
```
- Promise 失敗捕捉，請參考以下程式碼。
```javascript=
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}

promiseSetTimeout(false)
  .then(function(res) {
    console.log(res);
  })
  .catch(function(err) {
    console.log(err);
  })
// 在執行 promise 時要有 .then() 方法接收成功結果，
// 也必須有 .catch() 方法接收失敗結果，這樣才是正確的使用方式哦
```
- 元件運用，請參考以下程式碼。
```javascript=
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}

const component = {
  data: {},
  init() {
    promiseSetTimeout(true)
    .then((res) => {
      this.data = res;
      console.log(this.data);
    });
  },
};
component.init();
// 上述過程是在元件初始化時，就把非同步的資料寫回 data 的方式
// 這是在實戰中非常常見的結構
```
- 實戰取得遠端資料，請參考以下程式碼。
```javascript=
// 這裡要記得引入 axios 套件後再撰寫程式碼哦
// https://github.com/axios/axios 是 axios 的文件連結
// 以下使用 randomuser api 做測試(https://randomuser.me/api)
axios.get("https://randomuser.me/api")
  .then((res) => {
    console.log(res.data.results);
  })
  .catch((err) => {
    console.log(err.response);
  });
// 首先要知道 axios 是基於 promise 所建立的
// 所以一樣可通過 .then() 和 .catch() 取得成功與失敗結果
// 使用方式就直接寫 axios.get('傳入網址').then(...).catch(...) 即可
// 記得每次除了 .then() 外都要捕捉錯誤
// Axios 錯誤捕捉技巧因有對 err 進行過封裝 
// 所以回傳失敗內容須通過 err.response 才能獲取到
```

## ES Module
JavaScript 的模組化，就是可以將檔案拆分 進行匯入、匯出等等。
在過去這種模組化都是需要透過一些工具來進行編譯的
但近幾年許多瀏覽器陸續都開始支援模組化的功能了

以下介紹如何在瀏覽器下進行這種原生的模組概念：

1. script 標籤必須加上 type="module" ，該程式碼才能進行匯出與匯入
2. 要先 export 匯出 才能做 import 匯入
3. 匯出分成兩種方式：預設匯出以及具名匯出。
4. 預設匯出：常見的匯出方式，通常用於匯出物件，在 Vue 開發中可用來匯出元件。如 `export default {...}`
5. 具名匯出：用於匯出已定義的變數、物件、函式等，專案開發中通常用於 『方法匯出』，第三方的框架、函式、套件很常使用具名定義 『方法』。如 `export const a = xxx; export function b() {...};`
6. 匯入方法分成三種方式：預設匯入、具名單一匯入、具名全部匯入。
7. 預設匯入：因為預設匯出沒有名字，所以可以為它命名 如 `import $ from "./jquery.js"`（注意：因為傳參考的特性，因此元件無法重複利用，但在 Vue.js 中會解決此問題）
8. 具名匯入 - 單一匯入：建議寫法，如 `import { a, b } from "./xxx.js";` 通過 a 或 b 調用
9. 具名匯入 - 全部匯入：不建議寫法，如 `import * as all from "./xxx.js";` 通過 all.a 或 all.b 調用
10. SideEffect：通常用在舊的函式庫，因為裡面直接是立即函數，所以不需匯出就可直接匯入。如 jQuery 可直接 `import "./jquery.js";`

## ES Module 延伸

基本上每個 `<script type="module"></script>` 的作用域都是獨立的，
假設故意把變數或函式寫成 window.xxx 的話就可以在其他 module 中通過 window.xxx 獲取到（平常不太會這樣做，了解一下這個觀念即可）
但如果一個是 module 另一個是普通 script 就獲取不到 window.xxx

另外在網路上可看到許多套件都逐漸釋出了 ESM 版本給大家使用
如果條件允許的話是可以直接使用 import 方式進行載入的
像之後的 Vue.js 就已經有釋出 ESM 的形式了（可以到 [cdnjs](https://cdnjs.com/) 中查找 Vue）
有些 cdn 的名稱是 vue.esm-xxx 這種的就是 ESM 的版本了
像 vue.esm-browers 就是專門給瀏覽器使用的
這邊我們就用 vue.esm-browers 練習載入 ESM

先到 [Vue3 官網 - 介紹](https://v3.vuejs.org/guide/introduction.html#declarative-rendering)拷貝初始化的程式碼如下：
```htmlembedded=
<div id="counter">
  Counter: {{ counter }}
</div>
```
```javascript=
const Counter = {
  data() {
    return {
      counter: 0
    }
  }
}

Vue.createApp(Counter).mount('#counter')
```
接著把他們改成使用 module 方式匯入：
```htmlembedded=
<div id="counter">
  Counter: {{ counter }}
</div>
<script type="module"> // 建立 module
// 通過具名匯入的單一匯入方式把 createApp 載入進來
import { createApp } from "https://cdnjs.cloudflare.com/ajax/libs/vue/3.1.4/vue.esm-browser.min.js"; 
const Counter = {
    data() {
        return {
            counter: 0,
        };
    },
};
// 把 Vue.createApp 改成 createApp 即可運作
createApp(Counter).mount("#counter"); 
</script>
```


# 6/2 直播錄影檔 - 那個 let, const, var 到底差在哪？

以下使用各種範例做說明：
- 為什麼要宣告變數，請參考以下程式碼。
```javascript=
function fn() {
  a = 0;
}
fn();
// 舉例來說像上方程式碼沒有宣告 a 但可以取到 a 的值
// 這個 a 就稱為一個全域屬性 
// 但由於很難知道 a 的來源是哪，所以我們不推薦這樣做
```
- 基於上述深入講解，請參考以下程式碼。
```javascript=
// 假設這是第 1 行程式碼
function fn() {
  a = 0;
}
fn();

// 然後這是第 300 行程式碼
function fnB() {
  a = 1;
}
fnB();
console.log(a); 
// 上方結果 a 會是 1 
// 但當程式碼很多行 我們有可能會忘記前面使用過 a 
// 進而導致一些很難除錯的問題
```
- 所以上述的正確撰寫方式應該如下
```javascript=
// 假設這是第 1 行程式碼
function fn() {
  var a = 0;
}
fn();

// 這是第 300 行程式碼
function fnB() {
  var a = 1;
}
fnB();
console.log(a);
// 上方結果會是 a is not defined
// 因為每個函式都有自己的作用域 
// 所以兩個函式的 a 都只在自己的作用域中 不會互相衝突
// 這也是我們要宣告變數的原因 當我們沒有宣告變數的時候就可能造成全域的污染
```
- 全域、區域污染，請參考以下程式碼。
```javascript=
a = 0;
function fn() {
  a = 1;
}
fn()
console.log(a)
// 上方結果為 1 因為他們沒有被宣告成變數 兩個就都是全域屬性
```
1. 屬性可以被刪除，請參考以下程式碼。
```javascript=
a = 0;
console.log(a); // 輸出 0
console.log(window); // 全域物件 且裡面會有 a 屬性
delete window.a // 用 delete 刪除屬性 a
console.log(a); // 輸出 a is not defined
console.log(window); // 全域物件的 a 也不見了
```
2. 變數無法被刪除，請參考以下程式碼。
```javascript=
var b = 1;
console.log(window) // 全域物件 且裡面有 b
delete window.b // 刪除屬性在這裡不起作用了 因為 b 是變數不是屬性了
console.log(window); // 所以這裡 b 還是存在
console.log(b); // 可以輸出 1
```
- var 的作用域，請參考以下程式碼。
```javascript=
function fn() {
  var a = 1;
  debugger; // 這個可以讓程式碼停在這一行 並開啟 Sources 功能
  // 此時可以看到右邊的 Scope(作用域)裡面有一個 Local 就是他目前的函式中有哪些東西
}
fn();
```
1. 全域跟函式中各自宣告 a，兩者不會互相影響，因為函式有自己的作用域，請參考以下程式碼。
```javascript=
var a = 0;
function fn() {
  var a = 1;
  console.log('local', a) // 1
}
fn();
console.log('全', a) //  0
```
2. 全域宣告了 a，函式把 a 的值改成 1，此時函式中的 a 就等於全域中的 a ，請參考以下程式碼。
```javascript=
var a = 0;
function fn() {
  a = 1;
}
fn();
console.log('全', a) // 1，在函式中 全域的值被改變了
```
- var 的辭法作用域
1. var 作用域在程式碼寫完的當下就確定了，請參考以下程式碼。
```javascript=
var a = 0;
function fnA() {
  console.log(a);
}

function fnB() {
  var a = 1;
}
fnB(); // 他有自己的作用域在函式裡面，所以不會影響到全域
fnA(); // 這裡就很清楚，輸出結果是全域的 0
```
2. 上方的進階題，請參考以下程式碼。
```javascript=
var a = 0;
function fnA() {
  console.log(a);
}
function fnB() {
  var a = 1;
  fnA();
}
fnB();
// 這裏要清楚一件事：
// 我們不是看在哪調用 fnA() 去決定 a 值
// 而是看 fnA() 被創建在哪、函式中是否有宣告 a 
// 沒有就找 function fnA() {...} 的外層 
// 所以上面這題就指向全域的 a，輸出為 0
// 這邊也說明一個概念：
// 你不需要執行代碼後才去找 a ，而是在程式碼寫完的當下就已經可以確定 a 的值了 
```
- var 特性
1. function 作用域與重複宣告，請參考以下程式碼。
```javascript=
function fn() {
  var a = 1;
  var a = 0;
}
fn();
// 作用域只在函式裡 且 var 重複宣告也不會報錯 在該函式外會取不到 a
```
2. 為 let 埋下的梗....請參考以下程式碼。
```javascript=
{
  var b = 2;
}
console.log(b);
// {} 是一個 block，沒任何作用，就只是一個 block，先說明程式碼可以這樣寫不會報錯
// 然後在 block 裡面用 var 宣告變數 b，在 block 外也可以獲取到這個變數 b
```
3. for 迴圈，請參考以下程式碼。
```javascript=
for (var i = 0; i < 10; i++) {
  console.log(i); // 這裡會輸出 0~9
}
console.log(i); // 這裡會輸出 10，因為 i 超過 9 才會跳出 for 迴圈
```
4. for迴圈 + setTimeout，請參考以下程式碼。
```javascript=
for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
// 輸出 10 因為 setTimeout 屬於非同步行為
// JS 默認在所有事件結束後才會執行非同步行為 
// 所以執行 setTimeout 時 i 已經跳出 for 了
```
5. hoisting(提升)，請參考以下程式碼。
```javascript=
console.log(a); // 輸出 undefined
var a = 1;
console.log(a); // 輸出 1
// 我們在初始化的過程中嘗試去取值
// 即在 var a = 1; 上一行 console.log(a)
// 會取得 undefined，這就是 hoisting
// 另外 a is not defined 是表示我們完全沒宣告就取 a
```
- let 與 var 差異

1. let 屬於 block 作用域，請參考以下程式碼。
```javascript=
{
  let a = 1;
}
console.log(a); 
// 上方 console.log 會出錯 a is not defined
// 這是因為 let 的作用域只存在 block 內，不像 var 還能在外層取到值
```
2. function 也是 block 所以離開函式無法取到 a 請參考下方程式碼。
```javascript=
function fn() {
  let a = 1;
}
console.log(a); // 這個也會出錯，a is not defined
// let 的作用域主要就是看 {}，所以有看到 {} 都會影響到他的作用域
```
3. for
```javascript=
for (let i = 0; i < 10; i++) {
  console.log(i); // 這裏會輸出 0~9
  setTimeout(() => {
    console.log(i); // 這裏也輸出 0~9
  }, 0);
}
console.log(i); // 會出錯
// 這邊我們知道在 for 迴圈裡使用 var 時 setTimeout 都是輸出 10
// 但 let 卻可以正常輸出 0~9 
// 原因就是 let 屬於 block 作用域 這讓每個 i 都獨立存在於 block 內
// 這也是為何 let 比 var 穩定很多的原因
// 另外這是一個常見考題 下次遇到就知道怎麼做囉～～(用 let 啦哪次不用)
```
4. let 宣告的變數不會出現在 window 上，請參考以下程式碼。
```javascript=
var a = 0;
let b = 1;
console.log(window)
// 上方輸出後打開 window 這個全域物件會看到 a 但不會看到 b
```
5. let 無法重複宣告同一個變數，請參考以下程式碼。
```javascript=
let a = 0;
let a = 1;
console.log(a)
// 會報錯 Identifier 'a' has already been declared
// 已經宣告就過不能再重複宣告
```
6. let 沒有 hoisting 但有暫時性死區(TDZ)，請參考下方程式碼。
```javascript=
// 變數的部分
console.log(a)
let a = 0;
// 這樣做會報錯 Cannot access 'a' before initializatio
// 你不能在初始化之前獲取他 需在宣告的下一行才執行操作
```
```javascript=
// 函式的部分
function fn(a) {
  console.log(a)
  var a = 2;
  console.log(a)
}
fn(1)
// 上方程式碼可以正常執行，但若把上方的 var 改成 let 就會出錯 
// 你以為宣告前可以獲取 a 但其實不行，這就是使用 let 宣告時的暫時性死區
// 他會使你無法取到第一個 console.log(a) 的值
```
- const 特性，請參考下方程式碼。
```javascript=
// 我們可以像下方這樣重新賦值
let a = 0;
a = 1; 
a = 2;
console.log(a); // 輸出結果為 2

// 但不可以像下面這樣
const b = 0;
b = 1;
console.log(b); // 會報錯 Assignment to constant variable
// 因為用 const 宣告的是常數 常數是不能被重新賦值的
```
- 物件傳參考的特性
1. 只需修改屬性值的話 就可以用 const 宣告的物件，請參考下方程式碼。
```javascript=
const a = {
  name: '卡斯伯'
}
a.name = 'Ray'
```
2. 修改整個物件的話 就不可以用 const 宣告，請參考下方程式碼。
```javascript=
const a = {
  name: '卡斯伯'
}
a = {
  name: 'Ray'
}
// 結論：可以用 const 就用 const 不要都用 let 宣告
```
# 6/9 直播錄影檔 - 物件傳值？傳參考？

以下使用各種範例做說明：
- 入門考題 變數傳值的部分
```javascript=
var person = '小明';
var person2 = person;
console.log(person === person2) // true
// 這邊是 傳值 把小明那個 person 記憶體複製一份給 person2
// 所以 person2 會得到 person 的值 小明
// 接下來我們修改 person2 的值如下
person2 = '杰倫';
console.log(person, person2); // 小明 杰倫
// 兩個就是各自的記憶體位置 不會互相影響
```
- 要開始傳參考了 這是物件的形式
```javascript=
var person = {
  name: '小明'
}
var person1 = person; // 把兩個物件的記憶體位置指向同一個地方
console.log(person === person1) // 這裡會輸出 true（我知道你知道）
// 然後我們再來改一下 person1 物件的 name
person1.name = '杰倫' ;
console.log(person.name); // 噠噠～這裡會輸出杰倫哦
// 這就是物件傳參考啦 他們的記憶體指向同一個地方
// 所以 person1 跟 person 對應的記憶體位置是同一個
// 改 person1 物件中屬性的時候 person 物件就也會跟著變囉
// 這是最常見的狀況
```
- 來看看陣列是否也有這個問題吧
```javascript=
var member = ['爸', '媽'];
var member2 = member;
member2.push('小三');
console.log(member); // 有小三
// 這是因為 JS 裡面只有物件這個型別 沒有陣列 陣列也是物件
```
- 再來看看函式有沒有這個問題
```javascript=
function fn(name) {
  return `${name}被抓到了`
}
var fn2 = fn;
fn2.magicName = '奇怪的東西';
console.log(fn === fn2); // true
// 上面為 true 是因為 JS 裡面只有物件這個型別 沒有函式 函式也是物件
console.dir(fn2);
// console.dir 主要用於顯示物件，他會顯示物件中詳細的每個屬性及 prototype 的資訊
// 所以用 console.dir 就可以看到 fn2 中有 函式 以及 magicName
```
- 利用傳參考的特性把物件名稱或函式改成中文字
```javascript=
function 函式(name) {
  return `${name}被抓到了`
}
console.log(函式('漂亮阿姨'));

var 名偵探 = console;
名偵探.柯南 = console.log;

名偵探.柯南(函式('漂亮阿姨'))
// 由於傳參考的關係 上方這些寫法都是可以動的
```
- let/const
```javascript=
var 名偵探 = console;
名偵探.柯南 = console.log;
const person = {
  name: '小明'
}
person.name = '杰倫';
名偵探.柯南(person)
// 上方這種因為傳參考特性 可以用 const 宣告物件（因為只改物件中的屬性值 沒有改整個物件指向）
// 這種就建議使用 const 宣告而不要用 let or var 做宣告
// 但如果是要指向新的物件 如下
person = {};
// 這樣在一開始宣告 person 就要使用 let ，如果用 const 就會報錯
// 結論：若只是改變屬性值 建議就直接使用 const 宣告了
```
- let/const
```javascript=
const family = ['爸', '媽', '小三'];
family.forEach((item, key) => {
  if(item === '小三') {
    family.splice(key, 1)
  }
});
console.log(family); // 小三被拿掉了
// 這裡因為是修改陣列中的其中一個 所以也是傳參考 可以用 const 哦
```
- function 改物件屬性
```javascript=
function fn(item) {
  item.name = '杰倫';
}
const person = {
  name: '小明',
}
fn(person);
console.log(person); // name = 杰倫
// 可以清楚看到這裡只有一個物件 就是 person
// 所以在函式中也只是改變這個物件的屬性值而已 還是傳參考～
// 這也是實戰中需要注意的部分 盡量不要把傳入函式的物件屬性值改掉 因為會改到原始的物件哦
```

<!-- - 實戰中常見的狀況
```javascript=
function fn(item) {
  const newItem = {
    name: '杰倫'
  }
  // item = newItem;
  // item.name = newItem;
  Object.keys(item).forEach(key => {
    名偵探.柯南(key);
    item[key] = newItem[key];
  })
  console.log('item', item);
}
const person = {
  name: '小明',
}
fn(person);
console.log(person); // 1小明 , 2. 杰倫


// 3 傳參考很棒  4. 傳參考很煩
// #1 淺層複製
// const person = {
//   name: '小明'
// }
// const person2 = { ...person };
// person2.name = '杰倫';
// console.log(person2.name, person.name);
// console.log(person === person2);

// #2 深層複製
// var person = {
//   name: '小明',
//   family: {
//     name: '小明家',
//     members: ['爸', '媽']
//   }
// }
// const person2 = JSON.parse( JSON.stringify(person));
// person2.name = '杰倫';
// person2.family.name = '杰倫家'
// console.log(person, person2);
// console.log(person.family === person2.family); 
// 1 相等、2 不相等
// 很難打的按 3

// 擴展
// const person = {
//   name: '小明',
//   fn: function() {
//     console.log(`我叫作 ${this.name}`);
//   }
// };

// const person2 = {
//   ...person,
//   name: '杰倫',
// };
// person.fn();
// person2.fn();
// 1. 小明 , 2. 杰倫
// 3. 小明 , 4. 杰倫

// const family = [{name: '爸'}, { name: '媽' }];
// family.forEach((item, key) => {
//   const newItem = {
//     name: '杰倫'
//   };
  
//   family[key] = newItem; 
//   console.log('family[key]', family[key]);
//   console.log('item', item);
// });
// console.log(family); // 請問在此有加上 杰倫 ㄇ？
// 1有杰倫, 2.沒有杰倫

// 真地獄
// var person = {
//   name: '小明'
// }
// person.person = person;

// console.log(person.person === person.person.person);
// true 3 or false 4

var a = {
  x: '小明'
}
var b = a;
a.y = a = {
  x: '杰倫'
};

// console.log(b === a); // 1一樣、 2不壹樣
console.log('a:', a );
console.log('b:', b );

// 1. a 結果
// 2. { x: '杰倫'}
// 3. 其它

console.log(b.y === a);
// 1一樣、 2不一樣
```
 -->