學習來自: 六角學院
課程: Vue 3 實戰影音課程
Thank you
課前章節 – JS 必備觀念
JS 額外補充資源
為了加強學習 Vue 前的基礎知識
除了 本課程中的「課前章節 – JS 必備觀念」
除了課綱內的內容外,同時可參考以下資源
課程環境
課程相關資源
課程練習手冊:
Chrome 必裝的 Vue 開發套件:
課程使用的 CSS 函式庫 Bootstrap:
VSCode 相關套件:
關於 Vue dev tools 的常見問題,有幾個地方可以檢查看看
- 需確保當前頁面一定要有 createApp 的程式碼 (如果當頁有,可以另開新分頁確認,有時候重新整理也不會顯示)
- Vue 並非使用開發版本 (課程中是使用開發版本)
- dev tools 並非對應 Vue 3 的版本,請確認是否使用 dev 版本 (兩者僅能擇一開啟)
由於這是 Vue3 的版本插件,如果未來需要用舊版的插件
可以從 Chrome 的後台進行切換
JS 常見縮寫
// HTML
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
// JS
// #1 語法糖與新增語法
// 語法糖:不會影響運作,邏輯與當前 JS 一致
// const obj = {
// myName: "1. 物件",
// fn() {
// return this.myName;
// },
// };
// console.log(obj.fn());
// =====
// #2 物件字面值 Object literals
// #2-1 物件內的函式
// const obj3 = {
// myName: "物件",
// fn() {
// // 請改為縮寫
// return this.myName;
// },
// };
// console.log(obj3.fn());
// #2-2 物件內的變數
// const person = {
// name: "小明",
// };
// const people = {
// person,
// };
// console.log(people);
// #3 展開
// #3-1 不同陣列合併
// const groupA = ["小明", "杰倫", "阿姨"];
// const groupB = ["老媽", "老爸"];
// // const groupAll = groupA.concat(groupB);
// const groupAll = [...groupA, ...groupB];
// console.log(groupAll);
// #3-2 物件擴展
// 新增一個物件包含新方法,同時加入原有的方法
// const methods = {
// fn1() {
// console.log(1);
// },
// fn2() {
// console.log(1);
// },
// };
// const newMethods = {
// fn() {
// console.log(1);
// },
// ...methods,
// };
// console.log(newMethods);
// #3-3 轉成純陣列
// const doms = document.querySelectorAll("li");
// console.log(doms); // 請轉為純陣列
// const newDoms = [...doms];
// console.log(newDoms);
// #4 預設值
function sum(a, b = 2) {
// 請加入預設值避免錯誤
return a + b;
}
console.log(sum(1, 3));
This 的運作
// JS
// #1 一個函式中包含多少參數
// var a = "全域";
// function fn(params) {
// console.log(params, this, window, arguments);
// debugger;
// }
// fn(1, 2, 3);
// #2 this 的指向為何
// var obj = {
// name: "小明",
// fn: function (params) {
// console.log(params, this, window, arguments);
// // debugger;
// },
// };
// obj.fn(1, 2, 3);
// #3 注意:this 的指向相當複雜,大部分情境只需要了解其中一種即可(95%)
// 傳統函式中的 this 只與調用方式有關
var someone = "全域";
function callSomeone() {
console.log(this.someone);
}
// callSomeone(); // simple call
// #4 各種運用變化
// var obj = {
// someone: "物件",
// callSomeone() {
// console.log(this.someone);
// },
// };
// obj.callSomeone();
// var obj2 = {
// someone: "物件2",
// callSomeone,
// };
// obj2.callSomeone();
// var wrapObj = {
// someone: "外層物件",
// callSomeone,
// innerObj: {
// someone: "內層物件",
// callSomeone,
// },
// };
// wrapObj.callSomeone(); // 指向外層物件
// wrapObj.innerObj.callSomeone(); // 指向內層物件
// var obj3 = {
// someone: "物件 3",
// fn() {
// callSomeone(); // 通常平常不會這樣去取用 this
// },
// };
// obj3.fn();
var obj4 = {
someone: "物件 4",
fn() {
setTimeout(function () {
console.log(this.someone);
});
},
};
obj4.fn();
箭頭函式
// JS
// #1 箭頭函式的縮寫
// const arr = [1, 2, 3, 4, 5];
// // const filterArr = arr.filter(function (item) {
// // // console.log(item);
// // // console.log(item % 2);
// // return item % 2; // 結果為真值
// // });
// const filterArr = arr.filter((item) => item % 2); // 取有餘數的值(單數)
// // 箭頭函式會自動帶 return
// console.log(filterArr);
// #2 This 綁定的差異
// #2-1 活用觀念,將內層的改為箭頭
// var name = "全域";
// const person = {
// name: "小明",
// callName: function () {
// console.log("1", this.name); // 1 小明
// // setTimeout(function () {
// // console.log("2", this.name); // 2
// // console.log("3", this); // 3
// // }, 10);
// // 箭頭函式,沒有自己的 this
// setTimeout(() => {
// console.log("2", this.name); // 2
// console.log("3", this); // 3
// }, 10);
// },
// };
// person.callName();
// #3 陷阱
// #3-1
// var name = "全域";
// const person = {
// name: "小明",
// callName: () => {
// console.log(this.name); // 請尋找箭頭所在的作用域為何?
// },
// };
// person.callName();
// #3-2
// var name = "全域";
// const person = {
// name: "小明",
// callMe() {
// const callName = () => {
// console.log(this.name); // 請尋找箭頭所在的作用域為何?
// };
// callName();
// },
// };
// person.callMe();
// #4 實戰手法
var someone = "全域";
// function callSomeone() {
// console.log(this.someone);
// }
// 1. this 先指向其他變數
// 2. 使用箭頭函式
var obj4 = {
someone: "物件 4",
fn() {
// 方法一
// const vm = this; // vm 在 Vue 中意指 ViewModel
// setTimeout(function () {
// // callback function
// // console.log(this.someone);
// console.log(vm.someone);
// });
// 方法二
setTimeout(() => {
console.log(this.someone);
});
},
};
obj4.fn();
關注點分離概念說明
// HTML
<div class="component">
<ul></ul>
<input type="text" class="inputData">
<button type="button" class="addBtn">增加內容</button>
</div>
// JS
// #1 資料、畫面、方法分離
// 畫面 = html
// 資料 = component.data
// 方法 = 物件內的其它函式
// #2 元件結構
// 1. 資料
// 2. 方法、觸發器
// 3. 生命週期(初始化)
const component = {
data: [ // 資料
'這是第一句話',
'這是第二句話',
'這是第三句話'
],
removeData(id) {
this.data.splice(id, 1);
this.render();
},
render() { // 渲染方法
const list = document.querySelector('.component ul');
let content = '';
this.data.forEach((item, i) => {
content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`
});
// 縮寫優化
// const content = component.data.map(item => `<li>${item}</li>`).join('');
list.innerHTML = content;
// 加入監聽
const btns = document.querySelectorAll('.btn');
btns.forEach(btn => btn.addEventListener('click', (e)=> {
// #2 重點,移除項目是先移除資料,而不是直接移除 DOM
// 如果要進行 AJAX 或更複雜行為,不會因為 DOM 與資料混合而難以運作
const id = e.target.dataset.id;
this.removeData(id)
}))
},
init() {
this.render();
}
}
component.init();
傳統形式
- 將文字提取
- 產生一個新的節點,並且帶入文字
關注點分離
- 將文字提取出,並且寫入資料集
- 資料集的內容,轉換成畫面
關注點分離實作概念
// HTML
<div class="component">
<ul></ul>
</div>
// JS - 課程講解
// 畫面 - HTML
// 資料 - DATA
// 方法 - function
// #元件結構
// 1. 資料
// 2. 方法、觸發器
// 3. 生命週期
const component = {
// 資料
data: ["這是第一句話", "這是第二句話", "這是第三句話"],
// 事件觸發
removeData(id) {
// console.log(id);
// console.log(this);
this.data.splice(id, 1);
this.render();
},
// 渲染方法
render() {
//
// 方法一
// const vm = this;
const list = document.querySelector(".component ul");
// console.log(list);
let content = ""; // li 結構使用
this.data.forEach((item, i) => {
// console.log(item);
content = `${content}<li>${item}
<button type="button" class="btn" data-id="${i}">刪除</button></li>`;
// console.log(content);
});
list.innerHTML = content;
const btns = document.querySelectorAll(".btn");
// console.log(btns);
btns.forEach((btn) =>
// btn.addEventListener("click", function (e) {
// // console.log(e);
// // console.log(e.target.dataset.id); // 陣列索引位置
// console.log(this);
// this.removeData(e.target.dataset.id);
// })
// 方法二
// 把上面的 function 改成箭頭函式
btn.addEventListener("click", (e) => {
// console.log(e);
// console.log(e.target.dataset.id); // 陣列索引位置
console.log(this);
this.removeData(e.target.dataset.id);
})
);
},
// 生命週期
init() {
this.render();
},
};
component.init();
// JS - 範例
// #1 資料、畫面、方法分離
// 畫面 = html
// 資料 = component.data
// 方法 = 物件內的其它函式
// #2 元件結構
// 1. 資料
// 2. 方法、觸發器
// 3. 生命週期(初始化)
// const component = {
// data: [ // 資料
// '這是第一句話',
// '這是第二句話',
// '這是第三句話'
// ],
// removeData(id) {
// this.data.splice(id, 1);
// this.render();
// },
// render() { // 渲染方法
// const list = document.querySelector('.component ul');
// let content = '';
// this.data.forEach((item, i) => {
// content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`
// });
// // 縮寫優化
// // const content = component.data.map(item => `<li>${item}</li>`).join('');
// list.innerHTML = content;
// // 加入監聽
// const btns = document.querySelectorAll('.btn');
// btns.forEach(btn => btn.addEventListener('click', (e)=> {
// // #2 重點,移除項目是先移除資料,而不是直接移除 DOM
// // 如果要進行 AJAX 或更複雜行為,不會因為 DOM 與資料混合而難以運作
// const id = e.target.dataset.id;
// this.removeData(id)
// }))
// },
// init() {
// this.render();
// }
// }
// component.init();
傳參考特性 (常踩到的雷)
因為關注點分離,畫面由框架來處理,開發者將專注於資料處理,因此資料處理的知識顯得格外重要。
// JS
// #1 物件是以傳參考的形式賦值
// const person = {
// name: '小明',
// obj: {}
// }
// const person2 = person;
// person2.name = '杰倫';
// // console.log(person2 === person);
// // console.log(person2, person);
// const obj2 = person.obj;
// obj2.name = "物件下的名稱";
// console.log(person);
// #2 陷阱
// const fn = (item) => {
// item.name = '杰倫';
// // ...
// }
// const person = {
// name: '小明',
// obj: {}
// }
// fn(person);
// console.log('person', person);
// // ...
// 關於:這樣的錯誤,ESLint 也有建議不要這麼做(但你是有經驗的開發者時,下方也有提供關閉的方式)
// https://cn.eslint.org/docs/rules/no-param-reassign
// #3 解決方案
// #3-1 淺層拷貝
// const person = {
// name: '小明',
// obj: {}
// }
// // 方法一
// const person2 = Object.assign({}, person);
// // 方法二
// const person3 = {
// ...person
// };
// console.log(person === person2, person === person3);
// person2.obj.age = 16;
// console.log(person, person2);
// #3-2 深層拷貝
const person = {
name: '小明',
obj: {}
}
// 物件先轉成字串,然後再轉成物件
const person2 = JSON.parse(JSON.stringify(person));
// console.log(person2);
// console.log(person2 === person);
person2.obj.age = 16;
console.log(person2, person);
Promise 非同步觀念
// JS
// #1 非同步的觀念
// function getData() {
// setTimeout(() => {
// console.log("... 已取得遠端資料");
// }, 0);
// }
// // 請問取得資料的順序為何
// const component = {
// init() {
// console.log(1);
// getData();
// console.log(2);
// },
// };
// component.init();
// 更正確的說法,Promise 是為了解決傳統非同步語法難以建構及管理的問題,如有興趣可搜尋 "callback hell js"
// #2 Promise
// 在此不需要學習 Promise 的建構方式,僅需要了解如何運用即可
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (status) {
resolve("promiseSetTimeout 成功");
} else {
reject("promiseSetTimeout 失敗");
}
}, 1000);
});
};
// #2-1 基礎運用
// promiseSetTimeout(true).then(function (res) {
// console.log(res);
// });
// #2-2 串接
// promiseSetTimeout(true)
// .then(function (res) {
// console.log(1, res);
// return promiseSetTimeout(true);
// })
// .then((res) => {
// console.log(2, res);
// });
// #2-3 失敗捕捉
promiseSetTimeout(false)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
// #2-4 元件運用
const component = {
data: {},
init() {
console.log(this);
promiseSetTimeout(true).then((res) => {
this.data.res = res;
console.log(this.data);
});
},
};
component.init();
使用 Axios 串接 API
// HTML
<!-- 本章節額外載入的遠端套件 -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// JS
// #3 實戰取得遠端資料
// #3-1
// https://github.com/axios/axios
axios
.get("https://randomuser.me/api/gg")
.then((res) => {
console.log(res.data.results);
})
.catch((err) => {
// console.log(err);
console.log(err.response);
});
// #3-2 記得捕捉錯誤,Axios 錯誤捕捉技巧
// https://randomuser.me/
在瀏覽器上運行 ES 模組
匯入 (html, js)
// 預設匯入
import 自訂名稱 from ...
// 具名匯入
import { 具名名稱 } from ...
匯出 (js)
// 預設匯出: 每個檔案唯一
export default
// 具名匯出: 每個檔案多個
export const xxx = ...
// HTML
<div class="component">
<ul></ul>
</div>
<script type="module">
// #1 匯出匯入本一體,先掌握匯出更容易理解匯入
// #1-1 將標籤定義 <script type="module">
// #1-2 預設匯出:defaultExport.js
// 常見的匯出方式,通常用於匯出物件,在 Vue 開發中可用來匯出元件
// #1-3 具名匯出:namedExport.js
// 可用於匯出已定義的變數、物件、函式,專案開發中通常用於 “方法匯出”
// 第三方的框架、函式、套件很常使用具名定義 “方法”
// #2 匯入方法
// #2-1 預設匯入
// 因為預設匯出沒有名字,所以可以為它命名
import newComponent from "./export1.js";
newComponent.init();
// 注意:因為傳參考的特性,因此元件無法重複利用(Vue.js 中會解決此問題)
// #2-2 具名匯入
// 單一匯入(建議寫法)
// import { a, b } from "./export2.js";
// console.log(a);
// b();
// 全部匯入(不建議,錯誤較難發現)
import * as all from "./export2.js";
console.log(all.a);
all.b();
console.log(all.c(1, 2));
// #3 SideEffect
// sideEffect.js(沒有包含匯出的檔案)
import "./sideEffect.js";
console.log($);
</script>
// JS - export1.js
export default {
data: [
// 資料
"這是第一句話",
"這是第二句話",
"這是第三句話",
],
removeData(id) {
this.data.splice(id, 1);
this.render();
},
render() {
// 渲染方法
const list = document.querySelector(".component ul");
let content = "";
this.data.forEach((item, i) => {
content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`;
});
// 縮寫優化
// const content = component.data.map(item => `<li>${item}</li>`).join('');
list.innerHTML = content;
// 加入監聽
const btns = document.querySelectorAll(".btn");
btns.forEach((btn) =>
btn.addEventListener("click", (e) => {
// #2 重點,移除項目是先移除資料,而不是直接移除 DOM
// 如果要進行 AJAX 或更複雜行為,不會因為 DOM 與資料混合而難以運作
const id = e.target.dataset.id;
this.removeData(id);
})
);
},
init() {
this.render();
},
};
// JS -export2.js
export const a = 1;
export function b() {
console.log(1);
}
export function c(a, b) {
return a + b;
}
// JS - sideEffect.js
// 立即函式
(function (global) {
global.$ = "我是 jQuery";
})(window);
現行的 ES 模組使用技巧
// HTML
<!-- <script type="module">
// #1 每一個 type="module" 的作用域都是獨立的
var a = 1;
window.b = 2;
</script>
<script type="module">
console.log(b);
</script> -->
<div id="app">{{ counter }}</div>
<script type="module">
// #2 可以運用的 ESM 套件
// 網路上找到的 “ESM”,如果條件允許是可以使用 import 方式載入
// https://cdnjs.com/libraries/vue
// 比對語法:https://v3.vuejs.org/guide/introduction.html#declarative-rendering
import { createApp } from "https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.19/vue.esm-browser.min.js";
const Counter = {
data() {
return {
counter: 0,
};
},
};
createApp(Counter).mount("#app");
</script>