wordpress_blog

This is a dynamic to static website.

JavaScript 必修篇 – 前端修練全攻略 (2)

AJAX – 網路請求

什麼是 AJAX?它如何改善網頁使用體驗?

AJAX 是非同步 JavaScript 與 XML 技術。

什麼是網路請求?

  • 透過 Chrome 執行 Enter URL 動作,我傳送了一個網路請求,取得:get URL 網頁上的資訊
  • 其他軟體也可以發出網路請求
  • 透過 JS 發出網路請求

從網頁架構瞭解網頁請求 – 上集

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js-compulsory</title>
</head>
<body>

  <h1>js-compulsory</h1>
  <ul class="list"></ul>
  <img src="https://images.unsplash.com/photo-1630042111810-af23e1de9176?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=736&q=80" alt="Photo by Lissete Laverde">

  <script src="js/all.js"></script>
</body>
</html>
// all.js

console.log('1234');
  • Chrome Network – 開發人員工具

從網頁架構瞭解網頁請求 – 下集

網頁請求先後分別是 index.html 的 HTML 結構、 img src、all.js,共發了3次網頁請求。

  • Chrome get 取得請求 → 本地端伺服器
  • Chrome ← 本地端伺服器

網頁請求狀態碼

  • HTTP 狀態碼 – MDN文件
    • 資訊回應 (Informational responses, 100 -199)
    • 成功回應 (Successful responses, 200 – 299)
    • 重定向 (Redirects , 300 – 399)
    • 用戶端錯誤 (Client errors, 400 – 499)
    • 伺服器端錯誤 (Server errors, 500 – 599)

狀態碼(Status)有哪些

  • 404 Not Found 伺服器找不到請求的資源
  • 200 OK 請求成功
  • 304 Not Modified
  • 500 Internal Server Error 伺服器端發生未知或無法處理的錯誤

清除快取並強制重新載入 (開發人員工具要打開)

  • 重新整理圖示長按左鍵後選取清除快取並強制重新載入
  • 重新整理圖示右鍵後選取清除快取並強制重新載入

request、response 講解

  • Google Network → Name – index.html → Headers → Request Headers

request(請求):傳送給伺服器要什麼資料。

response(回傳):回傳給瀏覽器資料,response header、response data

用 Node.js 開啟伺服器,更加瞭解 request、response 的差異

// app.js


const http = require('http');

http.createServer(function(request, response) {
  console.log(request);
  if(request.url == '/') {
    console.log('接收到網頁請求');
    response.writeHead(200, {'Content-Type': 'text/HTML'});
    response.write('<h1>index</h1>');
    response.end();
  }

}).listen(process.env.PORT || 3000);
console.log('Server已開啟port: 3000.');

AJAX – 網路請求小節測驗

AJAX – axios 套件教學

各種發出網路請求的 JS 寫法種類介紹

AJAX 透過 JS 發出網路請求。

JavaScript 原生寫法

// XMLHttpRequest 範例

function reqListener () {
  console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();
// Fetch 範例

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });

套件,需額外載入 JS

axios 環境安裝

套件cdn 程式碼與自己撰寫載入的程式碼載入順序有差

套件程式碼載入放在自己撰寫的程式碼之前。

檢查套件載入有沒有成功

  • 使用 Google Network 查看 Status
  • 使用 console.log(axios);
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js-compulsory</title>
</head>
<body>

  <h1>js-compulsory</h1>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js

// 測試套件有沒有載入成功
console.log(axios);

axios – 嘗試串接外部資料

// 範例程式碼

axios.get('https://hexschool.github.io/ajaxHomework/data.json')
  .then(function (response) {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js-compulsory</title>
</head>
<body>

  <h1>js-compulsory</h1>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js

// 測試套件有沒有載入成功
// console.log(axios);

// https://hexschool.github.io/ajaxHomework/data.json

axios.get('https://hexschool.github.io/ajaxHomework/data.json')
  .then(function (response) {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });

axios – response 參數詳細講解

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js-compulsory</title>
</head>
<body>

  <h1>js-compulsory</h1>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js

// 測試套件有沒有載入成功
// console.log(axios);

// https://hexschool.github.io/ajaxHomework/data.json

axios.get('https://hexschool.github.io/ajaxHomework/data.json')
  .then(function (response) {
    console.log(response);

    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });

axios – 將外部資料寫入到網頁上

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js-compulsory</title>
</head>
<body>

  <h1>js-compulsory</h1>
  <h2 class="title"></h2>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js

// 測試套件有沒有載入成功
// console.log(axios);

// https://hexschool.github.io/ajaxHomework/data.json

axios.get('https://hexschool.github.io/ajaxHomework/data.json')
  .then(function (response) {
    let ary = response.data;
    console.log(ary[0].name);
    const title = document.querySelector('.title');
    // title.innerHTML = '王小明';
    // title.innerHTML = ary[0].name;
    title.textContent = ary[0].name;
  });

我的提問,外部資料圖片抓取問題

// index.html

<ul class="list"></ul>
// style.css

img {
  width: 320px;
  height: 240px;
}
// script.js

// 外部資料
// https://ptx.transportdata.tw/MOTC/v2/Tourism/ScenicSpot?%24top=40&%24format=JSON

// 資料模擬
let data = [
  {
    name: "green mountain across body of water",
    pictureUrl:
      "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80"
  },
  {
    name: "mountains reflected on water"
    // 沒有 pictureUrl
  },
  {
    name: "brown field near tree during daytime",
    pictureUrl: "" // pictureUrl 為空值
  },
  {
    name: "花瓶岩",
    pictureUrl: "http://210.69.151.212/ptngis/files/photos/l/marks/C1-11501.jpg" // pictureUrl 圖片網址錯誤
  }
];
const list = document.querySelector(".list");

// 初始化狀態 - 渲染資料
function dataRender() {
  // 初始化一個空字串
  let str = "";

  data.forEach(function (item, index) {
    // let content = `<li>
    //   <h2>${item.name}</h2>
    //   <img src="${item.pictureUrl}" alt="圖片">
    // </li>`;
    // str+=content;

    let content = `<li>
        <h2>${item.name}</h2>
        <img src="${item.pictureUrl}" alt="${item.name}" >
      </li>`;
    str += content;
    // }
  });

  list.innerHTML = str;
}
dataRender();

// 綁定監聽事件
// 最後一項錯誤的網址,瀏覽器會先去找看看有沒有,如果沒有才會觸發。所以可以看到最後一張比較慢才顯示
const images = document.querySelectorAll("img");
images.forEach((item) => {
  item.addEventListener("error", (e) => {
    // console.log(e.target);
    e.target.setAttribute(
      "src",
      "https://images.unsplash.com/photo-1561657819-51c0511e35ab?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=871&q=80"
    );
  });
});

// 第一個資料正常:圖片正常
// 第二個沒有資料:使用取代圖片
// 第三個是空值
// 第四個是錯誤的圖片網址

// 關於陣列圖片抓取的問題
// 關於沒有資料、空值、錯誤的圖片網址,該怎麼處理、避免圖片區域是空白情形?

axios – 非同步觀念

// all.js

// 測試套件有沒有載入成功
// console.log(axios);

// https://hexschool.github.io/ajaxHomework/data.json

let ary = [];

axios.get('https://hexschool.github.io/ajaxHomework/data.json')
  .then(function (response) {
    console.log('資料有回傳了');
    ary = response.data;
    console.log(ary);
  });

  console.log(ary);
// Google Console
   []           all.js:15
   資料有回傳了  all.js:10
   [{...}]      all.js:12

透過函式設計處理非同步

資料回傳後,再執行函式。

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js-compulsory</title>
</head>
<body>

  <h1>js-compulsory</h1>
  <h2 class="title"></h2>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js

// 測試套件有沒有載入成功
// console.log(axios);

// https://hexschool.github.io/ajaxHomework/data.json

let ary = [];

axios.get('https://hexschool.github.io/ajaxHomework/data.json')
  .then(function(response) {
    console.log('資料有回傳了'); // 1
    ary = response.data;
    renderData();
  });

// 渲染資料
function renderData() {
  console.log(ary); // 2
  const title =  document.querySelector('.title');
  // title.textContent = '測試';
  title.textContent = ary[0].name;
}
console.log(ary);  // 3

// 執行的先後順序 3 → 1 → 2

AJAX – axios 套件教學小節測驗

第五週總複習

網路請求補充知識 (還未觀看)

從 Wireshark、Fiddler 深入了解網路請求

計算機網路總結

AJAX POST API 講解

網路請求種類介紹

post 網路請求文件介紹

六角學院 AJAX 練習

注意,此範例僅供練習,並不會儲存用戶資料至資料庫(僅緩存)。

註冊

新增一個帳號。

  • Method: POST
  • URL: https://hexschool-tutorial.herokuapp.com/api/signup
  • Data:
{
  email: 'lovef2e@hexschool.com',
  password: '12345678'
}
  • Success Response:
{
  "success": true,
  "result": {},
  "message": "帳號註冊成功"
}
  • Error Response:
{
  "success": false,
  "result": {},
  "message": "此帳號已被使用"
}
登入

登入一個已存在的帳號。

  • Method: POST
  • URL: https://hexschool-tutorial.herokuapp.com/api/signin
  • Data:
{
  email: 'lovef2e@hexschool.com',
  password: '12345678'
}
  • Success Response:
{
  "success": true,
  "result" {},
  "message": "登入成功"
}
  • Error Response:
{
  "success": false,
  "result": {},
  "message": "此帳號不存在或帳號密碼錯誤"
}

四種常見的 POST 請求 content-type 介紹

請求資料格式 request header Content-Type

  • application/x-www-form-urlencoded
  • application/json
  • multipart/form-data
  • text/plain (記事本格式最少使用)

axios 預設屬於第2種請求資料格式,支援其他格式、可以自己設定。

multipart/form-data,傳送檔案格式的時候,檔案:圖片、pdf、word、mp4。

透過 axios 實作註冊 post 網路請求

// axios post 範例

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>  
</head>
<body>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js

// URL
// https://hexschool-tutorial.herokuapp.com/api/signup

let obj =  {
  email: 'hexschool2021@hexschool.com',
  password: '12345678'
}

axios.post('https://hexschool-tutorial.herokuapp.com/api/signup', obj)
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});
// Google Console
   {data: {…}, status: 200, statusText: 'OK', headers: {…}, config: {…}, …}
   XHR finished loading: POST "https://hexschool-tutorial.herokuapp.com/api/signup"
>

從 chrome 觀察 post 請求

  • Chrome Network
    • Name
    • Headers
    • Payload
    • Preview
    • Response
// all.js - 1

// URL
// https://hexschool-tutorial.herokuapp.com/api/signup

let obj =  {
  email: 'hexschool2021@hexschool.com',
  password: '12345678'
}

axios.post('https://hexschool-tutorial.herokuapp.com/api/signup', obj)
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});
// index.html - 2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>  
</head>
<body>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// all.js - 2

// URL
// https://hexschool-tutorial.herokuapp.com/api/signup

function callSignUp(){
  let obj =  {
    email: 'hexschool2021@hexschool.com',
    password: '12345678'
  }
  
  axios.post('https://hexschool-tutorial.herokuapp.com/api/signup', obj)
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
}
// Google Console - 2
>  callSignUp();
<  undefined
   {data: {…}, status: 200, statusText: 'OK', headers: {…}, config: {…}, …}
   XHR finished loading: POST "https://hexschool-tutorial.herokuapp.com/api/signup"
>

實作 axios DOM 表單註冊流程

// 範例程式碼 index.html

帳號:
  <input type="text" class="account">
  <br>
  密碼:
  <input type="text" class="password">
  <br>
  <input type="button" value="送出" class="send">
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
// 範例程式碼 - script.js

const account = document.querySelector('.account');
const password = document.querySelector('.password');
const send = document.querySelector('.send');

send.addEventListener('click',function(e){
  callSingUp()
})
function callSingUp(){
  if (account.value == "" || password.value==""){
    alert("請填寫正確資訊");
    return;
  }
  let obj = {};
  obj.email = account.value;
  obj.password = password.value;
  axios.post('https://hexschool-tutorial.herokuapp.com/api/signup', obj)
    .then(function (response) {
      if (response.data.message=="帳號註冊成功"){
        alert("恭喜帳號註冊成功");
      }else{
        alert("帳號註冊失敗,有可能有人用你的email註冊!");
      }
      account.value = "";
      password.value="";
    })
    .catch(function (error) {
      console.log(error);
    });
}
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title> 
  <link rel="stylesheet" href="style.css">
</head>
<body>

  <div class="container">
    <form>
      <h2>註冊帳號</h2>
      <label for="account">帳號:</label>
      <input id="account" type="text" class="account" placeholder="請輸入帳號">
      
      <br><br>
      
      <label for="password">密碼:</label>
      <input id="password" type="password" class="password" placeholder="請輸入密碼">
      
      <br><br>
      
      <input type="button" value="送出" class="send">
    </form>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// style.css

/* css reset start */
/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}
/* css reset end */

/* css reset settings start */
*,*::before,*::after {
  box-sizing: border-box;
}

img {
  max-width: 100%;
  height: auto;
}
/* css reset settings end */

/* css styles start */
*,*:before,*:after {
  box-sizing: border-box;
}

.container {
  max-width: 940px;
  margin: 12% auto;
}

form {
  max-width: 375px;
  width: 100%;
  margin: 0 auto;
  background: #eee;
}

form h2 {
  font-size: 28px;
  text-align: center;
  padding-top: 16px;
  padding-bottom: 16px;
}

form label {
  width: 100%;
  display: block;
  margin-bottom: 16px;
  padding-left: 5%;
  font-size: 20px;
}

.account, .password {
  width: 90%;
  padding-top: 8px;
  padding-bottom: 8px;
  margin-left: 5%;
  margin-right: 5%;
  font-size: 20px;
}

.send {
  margin-left: 5%;
  margin-bottom: 16px;
  padding: 8px 16px;
  background: #353535;
  color: #fff;
  border-radius: 4px;
  border: 0;
  font-size: 20px;
}

.send:hover {
  background: #c9184a;
}

/* css styles end */
// all.js

// URL
// https://hexschool-tutorial.herokuapp.com/api/signup

const account = document.querySelector('.account');
const password = document.querySelector('.password');
const send = document.querySelector('.send');

// console.log(account,password,send);

send.addEventListener('click',function(e){
  // console.log('是否被點擊');
  callSignUp();

});

function callSignUp(){
  // email: 'hexschool2021@hexschool.com',
  // password: '12345678'
  if(account.value == "" || password.value == ""){
    alert("請填寫正確資訊");
    return;
  }
  let obj =  {};
  obj.email = account.value;
  obj.password = password.value;
  console.log(obj);
  
  axios.post('https://hexschool-tutorial.herokuapp.com/api/signup', obj)
  .then(function (response) {
    // console.log(response);
    // alert(response.data.message);
    if(response.data.message == "帳號註冊成功"){
      alert("恭喜帳號註冊成功");
    }
    else {
      alert("此帳號註冊失敗,有可能有人用你的email註冊!");
    }
    account.value = "";
    password.value = "";
  })
  .catch(function (error) {
    console.log(error);
  });
}

AJAX POST 小節作業

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title> 
  <link rel="stylesheet" href="style.css">
</head>
<body>

  <!-- 註冊帳號 -->
  <div class="container">
    <form>
      <h2>註冊帳號</h2>
      <label for="account-signup">帳號:</label>
      <input id="account-signup" type="text" class="account account-signup" placeholder="請輸入帳號">
      
      <br><br>
      
      <label for="password-signup">密碼:</label>
      <input id="password-signup" type="password" class="password password-signup" placeholder="請輸入密碼">
      
      <br><br>
      
      <input type="button" value="送出" class="send send-signup">
    </form>
  </div>

  <!-- 登入帳號 -->
  <div class="container">
    <form>
      <h2>登入帳號</h2>
      <label for="account-signin">帳號:</label>
      <input id="account-signin" type="text" class="account account-signin" placeholder="請輸入帳號">
      
      <br><br>
      
      <label for="password-signin">密碼:</label>
      <input id="password-signin" type="password" class="password password-signin" placeholder="請輸入密碼">
      
      <br><br>
      
      <input type="button" value="送出" class="send send-signin">
    </form>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="js/all.js"></script>
</body>
</html>
// style.css

/* css reset start */
/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}
/* css reset end */

/* css reset settings start */
*,*::before,*::after {
  box-sizing: border-box;
}

img {
  max-width: 100%;
  height: auto;
}
/* css reset settings end */

/* css styles start */
.container {
  max-width: 940px;
  margin: 12% auto;
}

form {
  max-width: 375px;
  width: 100%;
  margin: 0 auto;
  background: #eee;
}

form h2 {
  font-size: 28px;
  text-align: center;
  padding-top: 16px;
  padding-bottom: 16px;
}

form label {
  width: 100%;
  display: block;
  margin-bottom: 16px;
  padding-left: 5%;
  font-size: 20px;
}

.account, .password {
  width: 90%;
  padding-top: 8px;
  padding-bottom: 8px;
  margin-left: 5%;
  margin-right: 5%;
  font-size: 20px;
}

.send {
  margin-left: 5%;
  margin-bottom: 16px;
  padding: 8px 16px;
  background: #353535;
  color: #fff;
  border-radius: 4px;
  border: 0;
  font-size: 20px;
}

.send:hover {
  background: #c9184a;
}

/* css styles end */
// all.js

// URL
// https://hexschool-tutorial.herokuapp.com/api/signup

const accountSignup = document.querySelector('.account-signup');
const passwordSignup = document.querySelector('.password-signup');
const sendSignup = document.querySelector('.send-signup');

// console.log(accountSignup,passwordSignup,sendSignup);

sendSignup.addEventListener('click',function(e){
  // console.log('是否被點擊');
  callSignUp();

});

function callSignUp(){
  // email: 'hexschool2021@hexschool.com',
  // password: '12345678'
  if(accountSignup.value == "" || passwordSignup.value == ""){
    alert("請填寫正確資訊");
    return;
  }
  let objSignup =  {};
  objSignup.email = accountSignup.value;
  objSignup.password = passwordSignup.value;
  // console.log(objSignup);
  
  axios.post('https://hexschool-tutorial.herokuapp.com/api/signup', objSignup)
  .then(function (response) {
    // console.log(response);
    // alert(response.data.message);
    if(response.data.message == "帳號註冊成功"){
      alert("恭喜帳號註冊成功");
    }
    else {
      alert("此帳號註冊失敗,有可能有人用你的email註冊!");
    }
    accountSignup.value = "";
    passwordSignup.value = "";
  })
  .catch(function (error) {
    console.log(error);
  });
}

// URL
// https://hexschool-tutorial.herokuapp.com/api/signin

const accountSignin = document.querySelector('.account-signin');
const passwordSignin = document.querySelector('.password-signin');
const sendSignin = document.querySelector('.send-signin');

// console.log(accountSignin,passwordSignin,sendSignin);

sendSignin.addEventListener('click',function(e){
  // console.log('是否被點擊');
  callSignin();
});

function callSignin(){
  if(accountSignin.value == "" || passwordSignin.value == ""){
    alert("請填寫正確資訊");
    return;
  }
  let objSignin = {};
  objSignin.email = accountSignin.value;
  objSignin.password = passwordSignin.value;
  // console.log(objSignin);

  axios.post('https://hexschool-tutorial.herokuapp.com/api/signin', objSignin)
    .then(function (response) {
      // console.log(response);
      // alert(response.data.message);
      if(response.data.message == '登入成功'){
        alert("帳號登入成功");
      }
      else {
        alert('此帳號登入失敗,請重新登入帳號');
      }
      accountSignin.value = "";
      passwordSignin.value = "";
    })
    .catch(function (error) {
      console.log(error);
    })
}

AJAX POST API 講解小節測驗

todolist 待辦事項

todolist 經典題目介紹

取值複習 getAttribute

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <h2 class="title">標題內容</h2>
  
  <script src="all.js"></script>
</body>
</html>
// all.js

const title = document.querySelector(".title");

// console.log(title);

title.addEventListener("click",function(e){
  // console.log(e.target);
  console.log(e.target.textContent);
  console.log(e.target.getAttribute("class"));
});

範圍內容取值

方法一

// index.html - 1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <h2 class="title">
    <span>標題內容</span>
    <input type="button" value="檢視">
  </h2>
  
  <script src="all.js"></script>
</body>
</html>
// all.js - 1

const title = document.querySelector(".title");

// console.log(title);

title.addEventListener("click",function(e){
  // console.log(e.target);
  // console.log(e.target.textContent);
  // console.log(e.target.getAttribute("class"));
  
  // console.log(e.target.nodeName);
  if (e.target.nodeName!=="INPUT") {
    return;
  }
  // console.log('你有正確點到 input');
  console.log(e.target.getAttribute("value"));

});

方法二

// index.html - 2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <h2 class="title">
    <span>標題內容</span>
    <input type="button" class="view" value="檢視">
  </h2>
  
  <script src="all.js"></script>
</body>
</html>
// all.js - 2

const title = document.querySelector(".title");

title.addEventListener("click",function(e){
  if (e.target.getAttribute("class")=="view") {
    console.log(e.target.getAttribute("value"));
  }

});

監聽大範圍內容取值

// index.html - 1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <div class="box">
    <h2 class="title">
      <span>標題1</span>
      <input type="button" class="view" value="檢視1">
    </h2>
    <h2 class="title">
      <span>標題2</span>
      <input type="button" class="view" value="檢視2">
    </h2>
    <h2 class="title">
      <span>標題3</span>
      <input type="button" class="view" value="檢視3">
    </h2>
  </div>

  
  
  <script src="all.js"></script>
</body>
</html>

方法一 – class 比較直覺、不會犯錯

// all.js - 1

const list = document.querySelector('.box');
// console.log(list);
list.addEventListener('click',function(e){
  // console.log(e.target.nodeName);
  if (e.target.getAttribute('class') == 'view'){
    console.log(e.target.getAttribute('value'));

  }
});

方法二

// all.js - 2

const list = document.querySelector('.box');
// console.log(list);
list.addEventListener('click',function(e){
  // console.log(e.target.nodeName);
  if (e.target.nodeName == "INPUT"){
    console.log(e.target.getAttribute('value'));

  }
});

方法三

// all.js - 3

const list = document.querySelector('.box');
// console.log(list);
list.addEventListener('click',function(e){
  // console.log(e.target.nodeName);
  if (e.target.nodeName !== "INPUT"){
    return;
  }
  console.log(e.target.getAttribute('value'));

});

data- 屬性妙用

  • data- 屬性取值,data-自訂名稱=”值”
    • 例如:data-num=”3″、data-content=”標題內容”
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <h2 class="title" data-content="標題內容" data-num="3">標題內容</h2>
  
  <script src="all.js"></script>
</body>
</html>
// all.js

const title = document.querySelector('.title');
// console.log(title);

console.log(title.getAttribute("data-content"));
console.log(title.getAttribute("data-num"));

let data = title.getAttribute("data-content");

console.log(data);

HTML 結構設計

畫面實作

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <input type="text" class="txt" placeholder="請輸入待辦事項">
  <input type="button" class="save" value="儲存待辦">
  <ul class="list">
    <li>待辦事項一 <input class="delete" type="button" value="刪除待辦"></li>
    <li>待辦事項二 <input class="delete" type="button" value="刪除待辦"></li>
  </ul>
  <script src="all.js"></script>
</body>
</html>

資料初始化渲染

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <input type="text" class="txt" placeholder="請輸入待辦事項">
  <input type="button" class="save" value="儲存待辦">
  <ul class="list">
    
  </ul>
  <script src="all.js"></script>
</body>
</html>
// all.js - 1

let data = [
  {
    "content": "待辦事項一"
  },
  {
    "content": "今天記得刷牙"
  }
]

let str = '';
data.forEach(function(item){
  str+=`
  <li>${item.content} <input class="delete" type="button" value="刪除待辦"></li>
  `
});
// console.log(str);
const list = document.querySelector(".list");
list.innerHTML = str;
// all.js - 2

let data = [
  {
    "content": "待辦事項一"
  },
  {
    "content": "今天記得刷牙"
  }
]

function renderData(){
  let str = '';
  data.forEach(function(item){
    str+=`
    <li>${item.content} <input class="delete" type="button" value="刪除待辦"></li>
    `
  });
  // console.log(str);
  const list = document.querySelector(".list");
  list.innerHTML = str;
}

renderData();

待辦新增功能

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <input type="text" class="txt" placeholder="請輸入待辦事項">
  <input type="button" class="save" value="儲存待辦">
  <ul class="list">
    
  </ul>
  <script src="all.js"></script>
</body>
</html>
// all.js

const txt = document.querySelector('.txt');
const save = document.querySelector('.save');
// console.log(txt);
// console.log(save);

let data = [];

function renderData(){
  let str = '';
  data.forEach(function(item){
    str+=`
    <li>${item.content} <input class="delete" type="button" value="刪除待辦"></li>
    `
  });
  // console.log(str);
  const list = document.querySelector(".list");
  list.innerHTML = str;
}

save.addEventListener('click',function(e){
  // console.log('你點擊到了');

  if(txt.value==""){
    alert("請輸入內容");
    return;
  }
  // {
  //   "content": "待辦事項一"
  // }
  let obj = {};
  obj.content = txt.value;
  // console.log(txt.value);
  // console.log(obj);
  data.push(obj);
  renderData();
});

// renderData();

待辦刪除功能-前置解說

  • splice 刪除指定資料
  • forEach 會用到 index 索引值
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <input type="text" class="txt" placeholder="請輸入待辦事項">
  <input type="button" class="save" value="儲存待辦">
  <ul class="list">
    
  </ul>
  <script src="all.js"></script>
</body>
</html>
// all.js

const txt = document.querySelector('.txt');
const save = document.querySelector('.save');
// console.log(txt);
// console.log(save);

let data = [];

function renderData(){
  let str = '';
  data.forEach(function(item,index){
    str+=`
    <li>${item.content} <input class="delete" type="button" data-num="${index}" value="刪除待辦"></li>
    `
  });
  // console.log(str);
  const list = document.querySelector(".list");
  list.innerHTML = str;
}

save.addEventListener('click',function(e){
  // console.log('你點擊到了');

  if(txt.value==""){
    alert("請輸入內容");
    return;
  }
  // {
  //   "content": "待辦事項一"
  // }
  let obj = {};
  obj.content = txt.value;
  // console.log(txt.value);
  // console.log(obj);
  data.push(obj);
  renderData();
});

// renderData();

待辦刪除功能

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS必修篇</title>
</head>
<body>

  <input type="text" class="txt" placeholder="請輸入待辦事項">
  <input type="button" class="save" value="儲存待辦">
  <ul class="list">
    
  </ul>
  <script src="all.js"></script>
</body>
</html>
// all.js

const txt = document.querySelector('.txt');
const save = document.querySelector('.save');
const list = document.querySelector('.list');
// console.log(txt);
// console.log(save);
// console.log(list);

let data = [];

function renderData(){
  let str = '';
  data.forEach(function(item,index){
    str+=`
    <li>${item.content} <input class="delete" type="button" data-num="${index}" value="刪除待辦"></li>
    `
  });
  // console.log(str);
  const list = document.querySelector(".list");
  list.innerHTML = str;
}

// 新增待辦功能
save.addEventListener('click',function(e){
  // console.log('你點擊到了');

  if(txt.value==""){
    alert("請輸入內容");
    return;
  }
  // {
  //   "content": "待辦事項一"
  // }
  let obj = {};
  obj.content = txt.value;
  // console.log(txt.value);
  // console.log(obj);
  data.push(obj);
  renderData();
});

// 刪除待辦功能
list.addEventListener('click',function(e){
  // console.log(e.target.nodeName);
  if(e.target.getAttribute('class',"delete")!=="delete"){
    alert('你目前不是點擊到按鈕');
    return;
  }
  // alert('你目前有確實點到刪除按鈕');
  let num = e.target.getAttribute("data-num");
  console.log(num);
  data.splice(num,1);
  renderData();
});

// renderData();

todolist 待辦事項小節測驗

陣列資料操作

陣列操作 – map

JS array map

  1. 能將原始陣列運算後,重新組合回傳一個新陣列
  2. 不會影響到原陣列
// all.js - 1

// 第一個範例
const arr = [1,5,10];
const newArr = arr.map(function(item){
  return item*item;
});

console.log(newArr);
console.log(arr);

// 第二個範例
const data = [1,8,13,20];
const newData = data.map(function(item){
  return item>10;
});

console.log(data);
console.log(newData);
// Google Console - 1
   (3) [1, 25, 100]
   (3) [1, 5, 10]
   (4) [1, 8, 13, 20]
   (4) [false, false, true, true]
>
// all.js -2

// 第三個範例
const data = [1,8,13,20];
const newData = data.map(function(item){
  let obj = {};
  obj.checkNum = item > 10;
  return obj;
});

console.log(data);
console.log(newData);

// {
//   checkNum: true;
// }
// Google Console - 2
   (4) [1, 8, 13, 20]
   (4) [{...},{...},{...},{...}]
>

陣列操作 – map 補充

  • map 觀念補充
  • map、forEach 差異
// all.js - 1

// map 觀念補充、map,forEach 差異

const arr = [1,5,10];
const newArr = arr.map(function(item){
  // return item*item;
});

console.log(newArr);
console.log(arr);
// Google Console - 1
   (3) [undefined, undefined, undefined]
   (3) [1, 5, 10]
>
// all.js - 2

// map 觀念補充、map,forEach 差異

const arr = [1,5,10];
const newArr = arr.forEach(function(item){
  return item*item;
});

console.log(newArr);
console.log(arr);
// Google Console - 2
   undefined
   (3) [1, 5, 10]
>

forEach 並沒有回傳一個新陣列的特性、也沒辦法用 return 回傳值。forEach 使用時機,裡面資料都跑一次,加總、額外處理、空陣列組出要的資料。

// all.js - 3

// map 觀念補充、map,forEach 差異

const arr = [1,5,10];
let total = 0;
arr.forEach(function(item){
  total+=item;
});

console.log(arr);
console.log(total);
// Google Console - 3
   (3) [1, 5, 10]
   16
>

map 使用情境,新變數要拿原始變數回傳一個陣列的時候,處理陣列、回傳一個組合好的陣列,map 就比較合適。

  • 有無 return,forEach 沒有 return、map 有 return

陣列操作 – filter 資料篩選

JS array filter 篩選

  1. 篩選出符合條件的內容
  2. 不會影響到原陣列

常用於比價、下拉選擇市區、有誰有及格。

// all.js - 1

// JS array filter 篩選
// 1.篩選出符合條件的內容,組合後回傳新陣列。
// 2.不會影響到原陣列
// 常用於比價、下拉選擇市區

const arr = [1,5,10];
const newArr = arr.filter(function(item){
  return item >= 5;
});

console.log(newArr);
// Google Console - 1
   (2) [5, 10]
>
// all.js - 2

// JS array filter 篩選
// 1.篩選出符合條件的內容,組合後回傳新陣列。
// 2.不會影響到原陣列
// 常用於比價、下拉選擇市區、有誰有及格

// 分數
const scoreData = [
  {
    name: "小明",
    score: 88
  },
  {
    name: "小英",
    score: 62
  },
  {
    name: "小花",
    score: 53
  }
];

const filterScore = scoreData.filter(function(item){
  return item.score >= 60;
});

console.log(filterScore);
// Google Console - 2
   (2) [{...},{...}]
>

陣列操作 – find 尋找頭一筆資料

// all.js

// JS array find 尋找頭一筆資料

const arr = [1,2,3,5,10,20,30,40];

const newArr = arr.find(function(item){
  console.log(item);
  return item >= 5;
});

console.log(newArr);

// 分數
const scoreData = [
  {
    name: "小明",
    score: 88
  },
  {
    name: "小英",
    score: 62
  },
  {
    name: "小花",
    score: 53
  }
];

const filterScore = scoreData.find(function(item){
  return item.score >= 60;
});

console.log(filterScore);
// Google Console
   1
   2
   3
   5
   5
   {name: '小明', score: 88}
>

陣列操作 – findindex 尋找資料索引

// all.js

// find 值提取出來
// findIndex 索引 編號

const colors = ['red','blue','black'];

const blueIndex = colors.findIndex(function(item){
  return item == "blue";
});

console.log(blueIndex);

// 訂單編號

const orders = [
  {
    name: '小廖',
    orderId: '12384955'
  },
  {
    name: '小華',
    orderId: '12384945'
  },
  {
    name: '小美',
    orderId: '12314955'
  },
];

const huaId = orders.findIndex(function(item){
  return item.orderId == "12384945";
});

console.log(huaId);
console.log(`這個訂單編號是${orders[huaId].name}`);
// Google Console

   1
   1
   這個訂單編號是小華
>

陣列資料操作小節測驗

箭頭函式

函式陳述式與函式表達式差異

// all.js - 1

// 箭頭函式

// 函式陳述式
function numA (x){
  return x * x;
}

// 函式表達式
const numB = function(x){
  return x * x;
}

const numC = (x) => {
  return x * x;
}
// Google Console - 1
>  numA(3)
<  9
>  numB(3)
<  9
>  numC(3)
<  9
>
// Google Console - 1
>  numB
<  f (x){
     return x * x;
   }
>  numB(3)
<  9
>

函式陳述式有提升的特性,函式表達式沒有提升的特性。

// all.js - 2

console.log(numA(3));
// 函式陳述式
function numA (x){
  return x * x;
}
console.log(numA(3));

// Google Console - 2
   9
   9
>
// all.js - 3

console.log(numB(3));
// 函式表達式
const numB = function(x){
  return x * x;
}
// console.log(numB(3));

// Google Console - 3
x  Uncaught ReferenceError: Cannot access 'numB' before initialization at xxx
>

箭頭函式基本寫法

// all.js

// 函式表達式
const numA = function(x){
  return x * x;
}

console.log(numA(3));

// 箭頭函式
const numB = (x) =>{
  return x * x * x;
}

console.log(numB(4));
// Google Console
   9
   64
>

箭頭函式再縮寫

縮寫寫法

  1. 如果函式搭配到 return
  2. 如果只有一個參數,可以省略括號
  3. 沒有參數,還是要有空括號
// all.js

// 箭頭函式
// 縮寫寫法
// 1.如果函式搭配到 return
// 2.如果只有一個參數,可以省略括號
// 3.沒有參數,還是要有空括號

const numA = (x) => {
  return `數字相乘 ${x*x}`;
};
console.log(numA(3));

const numB = (x) => `數字相乘 ${x*x}`;
console.log(numB(4));

const numC = x => `數字相乘 ${x*x}`;
console.log(numC(5));

const num = () => `數字相乘 ${9}`;
console.log(num());
// Google Console
   數字相乘 9
   數字相乘 16
   數字相乘 25
   數字相乘 9
>

陣列 map 搭配箭頭函式寫法

// all.js - 1

// 陣列操作 map、filter

// const data = [1,8,13,20];

// const newData = data.map(function(item){
//   return item + 2;
// });

// console.log(newData);

const data = [1,8,13,20];

const newData = data.map(item => item + 3);

console.log(newData);
// Google Console - 1
   (4) [4, 11, 16, 23]
>

單行可以使用箭頭函式再縮寫,多行還是建議需要加上大括號。

箭頭函式小節測驗

第六週總複習

最終關卡 – todolist 待辦事項 (未製作)

JS – 彩蛋課程 – NPM 管理

NPM 介紹

NPM 套件管理工具。

Node.js、NPM 環境安裝

資源連結

Node.js 安裝選擇 LTS(長期支援)版本安裝,比較穩定。

VSCode 終端機教學

版本檢查

  1. 檢查 Node.js 版本: node -v
  2. 檢查 NPM 版本: npm -v

開啟資料夾方式

  • 直接拖曳資料夾到 VSCode
  • 從 VSCode 選單 File → Open Folder

開啟終端機

  • 從 VSCode 選單 View → Terminal 開啟終端機面板
  • 終端機 – 輸入指令
  • node -v
  • npm-v
  • 都有回報版本號就有安裝成功

npm init – 專案環境初始化

指令

  • 專案初始化:npm init
  • 專案初始化,並提供預設值:npm init -y

開啟 Terminal 介面

  • View → Terminal、或者快捷鍵 Ctrl + `
  • 確認是否在專案位置
  • 輸入指令 npm init
  • 完成設定後會產生 package.json

各種套件,都會同步記錄在 package.json。

npm install – 環境安裝教學

  • NPM 官網
  • 安裝模組指令:npm install 模組名稱

備註:若您是 Mac 電腦,有時可能會因為權限不夠而出現紅字 err 出錯,此時請嘗試將指令改為:sudo npm install 模組名稱

  • bootstrap – npm i bootstrap 或者 npm install bootstrap
  • vue – npm i vue 或者 npm install vue

npm 全域安裝 -g

NPM 安裝範圍。

  • package.json 檔案裡面的 dependencies 的套件,可以使用 npm install 指令安裝
  • node_modules 不會進到 git 版本控制,通常來說只放開發的 Code
  • package.json 裡面的 dependencies 可以安裝自己想要安裝的套件版本號
  • 全域空間 – npm install jquery -g
    • 建議還是先把套案安裝在專案資料夾
    • 可能會運用在 jest, mocha, express…等
    • Mac 的話會安裝在 /usr/local/lib/node_modules

練習

  • 把專案資料夾裡面 node_modules 刪除、然後使用 npm install 安裝回來
  • 可以練習使用全域空間 npm install 模組名稱 -g

–save、–save-dev 指令差異

  • production (上線)依賴模組:npm install 模組名稱 –save
  • development (開發)依賴模組:npm install 模組名稱 –save-dev

Webpack套件

練習

  • npm install bootstrap –save 套件安裝是在 dependencies (上線階段)
  • npm install bootstrap –save-dev 套件安裝是在 devDependencies (開發階段)
  • npm install bootstrap 套件安裝是在 dependencies (上線階段)

常見指令補充

  • 更新套件:npm update 模組名稱
  • 移除套件:npm uninstall 模組名稱

舉例

  • npm uninstall jquery
  • npm uninstall webpack
  • npm uninstall vue
  • npm uninstall bootstrap
  • npm uninstall bootstrap -g
  • npm update vue

也可以從 package.json 修改套件的名稱、版本號,再使用 npm install 安裝套件。

webpack 壓縮打包工具管理

為什麼要學 webpack?

前端壓縮打包工具。

舉例

  • vue
  • react
  • 前端應用
  • spa (單頁面應用)

webpack 環境建立

  • 安裝 Webpack 套件指令:npm install webpack webpack-cli –save-dev

環境建立步驟

  • 建立一個專案資料夾 – webpack-demo
  • 開啟終端機
  • 專案環境初始化 – npm init、快速初始化 npm init -y
  • webpack 套件指令:npm install webpack webpack-cli –save-dev,這裡會安裝在開發環境
  • 在 package.json 的 devDependencies 裡面會產生webpack、webpack-cli 名稱、版本號就代表 Webpack 環境建立好了

Webpack Guides

進入點(entry)、輸出點(output) 觀念建立

Download

Webpack Concepts

Webpack 官網圖片講解

  • MODULES WITH DEPENDENCIES→bundle your scripts→STATIC ASSETS
  • 進入點 – MODULES WITH DEPENDENCIES
  • 輸出點 – STATIC ASSETS

操作步驟

  • 建立 src 資料夾 – 這個就是進入點、然後在裡面建立 index.js 檔案
  • 建立 dist 資料夾 – 這個就是輸出點
  • 在 package.json 新增 “build”: “webpack” 名稱、值
  • 在終端機輸入指令:npm run build,build 叫做編譯,
    在 dist 資料夾就會新增一個 main.js 檔案
// src/index.js

let a = 1;
let b = 2;
console.log("hello");
function hello(a,b){
  console.log(a+b);
}
hello(1,2);
// package.json - 新增 "build": "webpack"

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}
// dist/main.js

console.log("hello"),console.log(3);

webpack.config.js 環境建立

客製化進入點、輸出點資料夾名稱、以及裡面的檔案。

操作步驟

  • 新增 webpack.config.js 檔案
  • 客製化進入點路徑、輸出點檔案名稱
  • 移除輸出點 main.js 檔案
  • 開啟終端機執行指令:npm run build
// webpack.config.js - 範例

const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js',
  },
};
// webpack.config.js - 客製化

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
};

講解

  • path.resolve(__dirname, ‘dist’),可以使用 console.log 查看

NPM script 講解

可以自訂、客製化指令。

操作步驟講解

  • 在根目錄上新增 hello.js 檔案
  • 透過 package.json 的 “scripts” 的指令
  • 在 “scripts” 新增 “hello”: “node hello.js”
  • 打開終端機輸入指令:npm run hello
// hello.js

console.log("hello1");
console.log("hello2");
console.log("hello3");
// package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "hello": "node hello.js",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}

開發、上線模式 mode 切換

終端機執行指令npm run build”,出現的訊息 “mode” option has not been set、”mode” option to ‘development’ or ‘production’ 該如何調整、差異在哪裡。

操作步驟

  • 在 package.json 把原本 “build”: “webpack” 改寫成開發模式 “dev”: “webpack –mode development”
  • 在 package.json 新增一個上線模式 “deploy”: “webpack –mode production”
  • 最後的結果會影響到 dist/bundle.js 檔案,他的狀況會有些差異
// package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "hello": "node hello.js",
    "dev": "webpack --mode development",
    "deploy": "webpack --mode production"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  }
}
// dist/bundle.js - 開發模式

/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/***/ (() => {

eval("let a = 1;\r\nlet b = 2;\r\nconsole.log(\"hello\");\r\nfunction hello(a,b){\r\n  console.log(a+b);\r\n}\r\nhello(1,2);\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?");

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module can't be inlined because the eval devtool is used.
/******/ 	var __webpack_exports__ = {};
/******/ 	__webpack_modules__["./src/index.js"]();
/******/ 	
/******/ })()
;
// dits/bundle.js - 上線模式

console.log("hello"),console.log(3);

開發模式產生的 bundle.js 幫助除錯、狀況條列,通常開發的時候都會用開發模式。

上線模式產生的 bundle.js 比較精簡、把程式碼壓縮成一行、做各種優化。

第二種寫法 (比較少寫在這裡)

  • 在 webpack.config.js 寫上 mode: ‘production’
// webpack.config.js

const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
};

比較少寫在 webpack.config.js 原因是通常會透過 package.json 用下指令的方式去做開發。

export、import 語法教學

JS – 模組化

預設匯出 – 操作步驟

  • 新增資料夾 ESModule 放 modules 做練習
  • 使用 VSCode 開啟 ESModule 專案
  • 新增一個 index.js 檔案
  • 新增一個 export1.js 檔案
  • 新增一個 index.html 檔案、並載入 <script> index.js、用 ESModule 要加上 type=”module” 這樣子 import export 語法才會生效
// index.js - 1

import data from "./export1.js";

console.log(data);
// export1.js - 1
export default 1;

// 預設匯出
// index.html - 1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  

  <script src="index.js" type="module"></script>
</body>
</html>
// Google Console - 1
   1
>
// index.js - 2

import data from "./export1.js";

console.log(data.a);
// export1.js - 2

let a = 1;
let b = 2;
let c = a+b;

export default {
  a: 5,
  content: b
};

// 預設匯出 default export
// Google Console - 2
   5
>

其他常見做法 – 具名匯出

  • 新增 export2.js 檔案
  • 匯入 index.js 檔案
  • 使用 * 匯入、代表全部都載入,通常不建議這樣使用,會不好除錯,使用 * 的時候要搭配 as 自訂名稱
// export2.js - 1

// 具名匯出

export const c = 1;

export function add(x,y){
  return x+y;
}
// index.js - 1

import data from "./export1.js";
import {c,add} from "./export2.js";
console.log(data.a);

console.log(c);
console.log(add(2,9));
// Google Console - 1
   5
   1
   11
>
// index.js - 2

import data from "./export1.js";
import {add} from "./export2.js";
console.log(data.a);

// console.log(c);
console.log(add(2,9));
// Google Console - 2
   5
   11
>
// index.js - 3

import data from "./export1.js";
import * as data2 from "./export2.js";
console.log(data.a);

// console.log(c);
console.log(data2.add(3,88));
// Google Console - 3
   5
   91
>

匯出做法

  • 預設匯出
  • 具名匯出

依照專案團隊開發共同討論出共識要使用那種做法。

依老師來說會使用預設匯出的做法。

補充做法(冷門)

// export3.js

function a(){
  console.log(88);
}

a();
// index.js

import "./export3.js"
// Google Console
   88
>

預設匯出、具名匯出混用

// export2.js

// 具名匯出

export const c = 1;

export function add(x,y){
  return x+y;
}

export default 8;
// index.js

import data from "./export1.js";
import total, {c,add} from "./export2.js";
console.log(data.a);

// console.log(c);
console.log(add(3,88));
console.log(total);

// import "./export3.js"
// Google Console
   5
   91
   8
>

webpack import 載入流程

Download

操作步驟

  • 新增一個 c.js 在 src 資料夾裡
  • 在 src/index.js 匯入 c.js
  • 使用終端機執行指令(從 package.json 的指令) npm run dev
  • 如果要看編譯模式執行指令 npm run deploy
// src/c.js

export default 333;
// src/index.js

import c from './c.js';
let a = 1;
let b = 2;
console.log("hello");
function hello(a,b){
  console.log(a+b);
}
hello(1,2);
console.log(c);

加入 index.html 顯示 bundle.js 結果

Download

操作講解

  • 在 dist 資料夾裡新增 index.html
  • 載入 <script> bundle.js
  • 打開 dist/index.html 看 Google Console 有無資料顯示、正確載入 bundle.js
// index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>標題</h1>

  <script src="./bundle.js"></script>
</body>
</html>

載入 CSS-loader 流程

Download

操作講解

  • 安裝 css-loader 指令:npm install –save-dev css-loader
  • 新增一個 css 檔案:style.css 放在 src 資料夾裡面
  • 環境設定,在 webpack.config.js
  • 在 index.js 匯入 import css from “./style.css”;
  • 執行 npm run deploy
// src/style.css

h1 {
  color: red;
}
// webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
// index.js

import c from './c.js';
import css from "./style.css";
let a = 1;
let b = 2;
console.log("hello");
function hello(a,b){
  console.log(a+b);
}
hello(1,2);
console.log(c);

出現錯誤

  • 缺少 style-loader 套件安裝,npm install style-loader –save-dev
  • 再執行 npm run deploy
  • 打開 dist/index.html 看標題是否有變紅色

載入 Sass-loader 流程

Download

Sass 是 SCSS 預處理器

操作步驟

  • 安裝 sass-loader、指令:npm install sass-loader sass webpack –save-dev,有安裝的可以不用再安裝、sass 或者 node-sass 這兩個都可以擇一即可,這裡選擇 node-sass 使用
  • 進入點 src/index.js 依照文件範例去修改 import “./style.scss”;
  • 把 src/style.css 改成 src/style.scss、然後把程式碼貼上、調整
  • webpack.config.js 調整成 sass-loader 文件上範例的程式碼
  • 執行 npm run deploy
  • 打開 dist/index.html 查看標題是否有正確載入變粉紅色
// src/index.js

import c from './c.js';
import css from "./style.scss";
let a = 1;
let b = 2;
console.log("hello");
function hello(a,b){
  console.log(a+b);
}
hello(1,2);
console.log(c);
// src/style.scss

$body-color: pink;

h1 {
  color: $body-color;
}
// webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
};

載入 webpack 測試伺服器

即時更新、監聽。

Download

更新說明

由於 dev server 版本更新(v3 -> v4)因此設定方式的寫法也有些差異

v3 版本寫法

devServer: {
    contentBase: path.join(__dirname, "dist"),
}

v4 版本寫法

devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
}

操作步驟

  • 安裝模組指令:npm install webpack-dev-server –save-dev
  • 使用上用 CLI,指令是:npx webpack serve,npx 可以不用只需要 webpack serve 這個指令就可以
  • NPM Scripts,”scripts” 指令要加上 “serve”: “webpack serve”
  • webpack.config.js 也需要做些資料夾設定,從 Webpack devServer 找到 webpack.config.js 範例程式碼複製貼上
  • 把 package.json “scripts” 的 “dev” 改成 “dev”: “webpack serve –mode development”
  • 執行 npm run dev
  • 網址可以輸入 127.0.0.1:9000
// package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "hello": "node hello.js",
    "dev": "webpack --mode development",
    "deploy": "webpack --mode production",
    "serve": "webpack serve"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^6.5.1",
    "node-sass": "^7.0.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}
// webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    compress: true,
    port: 9000,
  },
};
// package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "hello": "node hello.js",
    "dev": "webpack serve --mode development",
    "deploy": "webpack --mode production",
    "serve": "webpack serve"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^6.5.1",
    "node-sass": "^7.0.1",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}

出現錯誤

  • webpack.config.js 的 devServer ‘public’ 改成 ‘dist’
  • 終止批次工作
  • 重新執行 npm run dev
// webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
    compress: true,
    port: 9000,
  },
};

自動開啟伺服器網頁

  • webpack.config.js 的 devServer 新增 open: true
  • 終止批次工作 Ctrl + c
  • 重新執行 npm run dev
  • 在 style.scss 把 pink 改成 blue 看是否有即時更新
// webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
    compress: true,
    port: 9000,
    open: true
  },
};

載入第三方套件 axios – 以 import 為例

Download

操作步驟

  • 安裝 axios 套件:npm install axios –save
  • 在進入點 src/index.js 寫 import axios from “axios”;,會從 node_modules 找、看有沒有載入的檔案
  • 撰寫 axios get
  • 執行 npm run dev 看是否有載入成功
// src/index.js

import c from './c.js';
import css from "./style.scss";
import axios from "axios";
axios.get("https://hexschool.github.io/ajaxHomework/data.json")
.then(function(response){
  console.log(response);
});
let a = 1;
let b = 2;
console.log("hello");
function hello(a,b){
  console.log(a+b);
}
hello(1,2);
console.log(c);

JavaScript 總複習題

第七週總複習

JS 百題斬 (未練習)

JavaScript 加碼直播 (未觀看)