# 前端其他
## 基本知識
### 瀏覽器
> - Code 通過瀏覽器內核渲染出畫面
#### 瀏覽器內核
> - 瀏覽器內核可分為[渲染(排版)引擎](https://zh.wikipedia.org/wiki/%E6%8E%92%E7%89%88%E5%BC%95%E6%93%8E)與JS
> - 渲染引擎:
> 負責取得標記式內容(如HTML、XML及圖像檔案等等)、整理資訊(如CSS及XSL等),並將排版後的內容輸出至顯示器或印表機。
> - Trident: 舊IE
> - EdgeHTML: Edge(Win10)
> - Gecko: FireFox
> - 開源
> - webkit: Safari
> - Bink: Chrome, 新Opera
> - Gecko的分支
> - Presto: 舊Opera
> - JS: 解析與執行Javascript
### Web 標準
> - 由 W3C 與其他組織制定
> - `結構` :
> - 對網頁元素整理分類
> - `XML` `HTML`
> - `表現` :
> - 外觀
> - `CSS`
> - `行為` :
> - 互動
> - `DOM` `ECMAScript`
------------------------------------
## Sublite
> - 指令版 `Command+Shift+P`
### Package control
> - 安裝: 打開指令版輸入install, 點擊Install Package Control
> - 安裝插件:
> - 打開命令版輸入PC, 點擊Package control: Install Package
> - 輸入插件名
> - 插件
> - ConvertToUTF8: 自動轉編碼為UTF-8
> - emmet:
> - \>: 層級
> ```Html
> <!-- div>ul>li*3 + tab -->
> <div>
> <ul>
> <li></li>
> <li></li>
> <li></li>
> </ul>
> </div>
> ```
> - +: 同級
> ```html
> <!-- div+p+ul +tab -->
> <div></div>
> <p></p>
> <ul></ul>
> ```
> - (): 分組
> ```html
> <!-- div>(p>ul)+(ul>li) +tab -->
> <div>
> <p>
> <ul></ul>
> </p>
> <ul>
> <li></li>
> </ul>
> </div>
> ```
> - 刪除插件:
> - 命令版> PC> Package control: Remove Package> 插件名
>
### HTML
> - 開頭輸入!或html:5後按Tab會自動補齊Html框架
> - 右鍵可以直接開啟網頁瀏覽
>
### Markdown
> - 安裝Markdown各式插件
> - Extended
> - Preview
> - SlideShow
> - Editing
> - md檔按 command+b 生成 Html 檔
-------------------------------------------
## 環境
- 編輯器
- Atom
- VS Code
- 集合服務器
- Window: wamp,
- Mac: xampp
- Linux: Nginx
- 虛擬機
- VMware
- putty(Mac), WinSCP(win), ssh(Linux)
- 版控
- git for window, git-scm(mac), yum/apt(linux)
- tortoisegit(window), snailsvn(Mac), yum/apt(linux)
- Node JS
- [nodemon](https://nodemon.io/)
- 用來開應用程式用的, 更新不用重開服務器, 他會熱更新
- 安裝 `% npm i nodemon -g`
- 使用 `% nodemon <your node app>`
- API
- phonegap-cli, dcloud
- APICloud Studio(中國專用)
- 數據庫
- Navicat
- MonogoDB, mySQL,...
- sequelize
- 操作數據庫的一個框架
- WebPack ( 這東西下面三個種都能幹 )
- 編譯
- babel -> 專門編譯 js
- 封裝
- browserify
- 同步
- browser-sync
- webGL
- three.js
- 其他
- [JWT](https://jwt.io)
- 實現狀態保持的一種工具
- 能夠模擬類似於 session 的登入記錄功能
## NPM
> - 更新 NPM
```shell
node -v # 查看版本
npm -v
sudo npm cache clean -f # 清快取
sudo npm install npm@latest -g # 升級到最新穩定版
```
> - 查看與安裝模塊
```shell
$ # 查詢 npm 服務器有啥模塊
$ npm search <模塊名> # 查看模塊名(ex. express)相關模塊
$ npm info <模塊名> # 查看某個模塊的所有資訊
$ npm view <模塊名> versions # 查看某個模塊的版本訊息
$ # 安裝模塊
$ npm init [-y] # 拿到 package.JSON
$ # -y : 全部問題都用預設
$ npm i <模塊名>[@latest] [模塊2] [模塊3] [-D||-S] [-g]
$ npm i <模塊名>[@<版本>] [-D||-S] [-g]
$ # -D : --save-dev 的簡寫, 會寫入 package.json 的 devDependencies(開發環境) 裡
$ # -S : --save 的簡寫, 會寫入 package.json 的 dependencies(生產環境)裡
$ # -D 跟 -S 對一些打包用的工具會有使用上的區別,例如 webpack
$ # -g : 將模塊安裝到全局</usr/local/lib> (容易有權限問題)
$ # i : install 縮寫
$ # 一次裝多個模塊: 用空格隔開即可
$ npm install # 依照環境清單安裝模塊
$ # 解除安裝模塊
$ npm uninstall <模塊名> [-D||-S] [-g]
$ # 看你要不要砍 package 裡面的東西, 還有看你裝哪來決定要不要加 -g
$ # 查看安裝過的模塊
$ npm ls [-g] [--depth 0]
$ npm list [-g] [--depth 0] # ls == list
$ # --depth : 限制輸出層級;
$ # 0: 只輸出第一層; (否則很亂)
```
> #### 安裝全局的權限問題
> - 如果開發者只有自己的話
> - 就把 `/usr/local/lib/node_modules` `/usr/local/bin` `/usr/local/share`
> 權限改成自己就好了
```
$ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
```
> - [官方文件解法](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)
> - 要馬節點版本
> - 要馬創一個資料夾, 將 npm 改預設, 並改 `.profile` 讓大家都可以用
```shell
$ mkdir ~/.npm-global # 創建一個執行資料夾
$ npm config set prefix '~/.npm-global' # 新增 npm 的頭
$ vi ~/.profile # 修改 profile
export PATH=~/.npm-global/bin:$PATH # 這句話丟進去
$ source ~/.profile 重新讀取一下
$ # 之後 npm install -g 就會改裝到 ~/.npm-global 下了
```
> #### 缺少XCODE 套件問題(No Xcode or CLT version detected!)
```shell
sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --install
```
> - 延伸: 查看權限
> - [Linux命令大全](https://man.linuxde.net/ls)
```shell
$ # Linux 的 ll [指定檔案或資料夾] == ls -alF , 不指定就是當前
$ # -a : 所有檔案及目錄 vs -A : 不含 ./ 跟 ../(預設)
$ # -l : 單列顯示 vs -C : 多列顯示(預設)
$ # -F : 檔名顯示標示符
$ # `/` : 文件; `*` : 可執行文件; `@` : 連接符;
$ # `|` : FIFO管道; `=` : sockets套接字;
$ ls -alF /usr/local/bin/
```
> - 延伸: 新增別名
> - 有些 Linux 有的縮寫指令, 在MAC沒有
> 直接去 `.bash_profile` 新增別名就好了
```shell
$ vi ~/.bash_profile
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF
$ source ~/.bash_profile
$ # source <FileName> : 在當前環境下讀取並執行某個可執行檔, 與 `.<FileName>`是一樣的
```
> - 延伸: 修改權限
> - https://www.jianshu.com/p/d42c435729fc
```shell
$ # chmod : 修改文件的讀寫執行
$ # chown : 修改文件的用戶權限
```
> - 其他相關常見管理工具
> - nrm : 可以快速切換源
> - 安裝: `$ npm install -g nrm`
> - 版本: `$ nrm --version`
> - 查看源: `$ nrm ls`
> - 切換源: `$ nrm use <registry>` ( registry: 源名 )
> - 添加源:`$ nrm add <registry> <url> [home]` ( home 為源的主頁 )
> - `nrm add test http://npm.test.com/ http://www.test.com/`
> - 刪除源:`$ nrm del <registry>`
> - 測試源速度: `$ nrm test <registry>`
> - 測試所有源速度: `$ nrm test`
> - 訪問源主頁: `$ nrm home <registry>`
> - 添加時如果沒有添加主頁,這就沒有屁用
> - [yarn](https://classic.yarnpkg.com/zh-Hant/) : FB 出的包管理工具,為了解決 npm 各種問題而生
> - 基本上用法跟 npm 差不多,關鍵字不太ㄧ樣而已,然後有更多的功能可以用
## 服務器
### wamp
> - 將要給別人用的東西放到 www/ 文件夾裡
> - 路徑轉換: `C:\wamp\www\你的文件` => `http://localhost/你的文件`
### xampp
> - 資料要放在 htdocs/ 文件夾裡
> - 路徑的話, 直接點 Go to Applocation 就好了
> 
> - 但是這個路徑是直接從 IP 連出去的,
> - 如果要走 localhost 需要設定, 還沒研究成功
> - 另外預設是連到 dashboard 資料夾, 怎麼改預設資料夾我也還沒成功
> - 不過先不管, 反正用 IP/檔案就能跑了, 將就點, 測 ajax 要緊
## 數據交互
### 表單
> - 最基本, 簡單
> - http 數據請求都是表單
> - 事實上 ajax jsonp 對於後端來說, 也都是表單,
> ajax 就是模仿 form 完成的, form 怎麼傳, ajax 就怎麼傳
> ### 屬性
> - action
> - 提交到哪
> - method
> - GET, POST, PUT, HEADER, DELETE, ..., 自定義
> - 大小寫不限, http 是有規定要大寫
> - name
> - 必須
> - 可重複, 服務器會收到一組陣列
> - submit 按鈕 : 提交
```htmlmixed=
<body>
<form action="" method="GET">
<input type="text" name="user"/>
<input type="password" name="pass"/>
<input type='submit'/>
</form>
</body>
```
> ### GET POST
> - GET:
> - 提交後, 數據會放在 url 裡, 亦即 head 中
> 
> - 因為在 head 中, 數據容量小
> - 看得見
> - 利於分享收藏, 例如
> https://www.youtube.com/watch?v=Ynpe9FIgUCM
> 還是需要有看得見的參數傳給別人, 才能讓人快速到達目標
> - 有緩存
> - POST:
> - 提交後, 數據會在 body 中
> - 因為在 body 中, 數據容量大
> - 如果數據需求大, 一般會使用額外的客戶端或插件來傳送
> - 看不見
> - 看不見不等於比較安全, https 才是相對安全
> - 沒緩存
> - #### 表單驗證範例
```javascript=
<head>
<meta charset='utf-8'>
<script>
/* 獲取元素酷炫寫法1.
const $ = function (...args){
return document.querySelectorAll(...args);
}
*/
// 獲取元素酷炫寫法2.
// 必須 bind this,
// 否則如果只有 querySelectorAll 附址到 $ 後,
// $ 是綁定在全局變量(window), 不是 #document
const $ = document.querySelectorAll.bind(document);
window.onload = function () {
// let f = document.getElementById('f');
let f = $(`#f`)[0]; // 酷炫寫法
let u = document.getElementsByName('user')[0];
f.onsubmit = function () {
if (u.value == '') {
alert('給我寫帳號');
return false;
}
}
console.log(f);
}
</script>
</head>
<body>
<form action="" method="GET" id='f'>
<input type="text" name="user"/>
<input type="password" name="pass"/>
<input type='submit'/>
</form>
<script src='test.js'></script>
</body>
```
> - #### 如何避免重複提交表單?
> - 設定當提交後 `disable` 為 true, 確認收到之後再打開就行了
### ajax
> - 不用刷新頁面, 可以跨域, 最常用
> - 用 ajax 送 get 也不會顯示在 url
> #### 演示一下
> - 創建三個檔案
> - `ajax.html` : 以 `ajax` 送資料
> - `form.html` : 以 表單 送資料 => 拿來對比 ajax 傳資料的不同
> - `1.php` : php 接口
```shell
$ pwd # 註1.
/Users/...略/htdocs/test
$ ls
1.php ajax.html form.html jquery.js
```
> - 註1. 我用 terminal cd 到路徑的方法, 直接開到那個資料夾, 右鍵複製路徑
> <img src='https://i.imgur.com/IWWdExM.png' style='width: 300px;'/> => <img src='https://i.imgur.com/yhzRYvI.png' style='width: 200px;'/>
> - #### ajax 送資料
> - ##### ajax 小筆記
> - type 沒寫默認也是 GET
> - dataType
> - 在後端有聲明數據類型的前提下, 可以省
> - 為了避免後端沒處理, 還是寫一下好
> - dataType 種類: text, xml, json, jsonp
> - ##### php 小筆記
> - echo 是 php 輸出的意思
> - 前端傳 GET 就用 $_GET 收,
> 傳 POST 就用 $_POST 收,
> - GET 跟 POST 事實上區別不大
`$ vi ajax.html`
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<script src='jquery.js' charset='utf-8'></script>
<script>
$(function () {
$(`#btn`).click(function () {
$.ajax({
url: '1.php',
data: {a: 10, b:5},
type: 'get', //=> 沒寫默認也是GET
dataType: 'text', //=> 在後端有聲明數據類型的前提下,
// dataType可以省, 保險起見還是寫一下,
// dataType 有幾種: text, xml, json, jsonp
success(str){
alert(str);
},
error() {
alert('請求失敗')
}
})
})
})
</script>
</head>
<body>
<input type='submit' id='btn'/>
</body>
</html>
```
`$ 1.php`
```php
<?php
echo $_GET['a'] + $_GET['b'];
// echo 是 php 輸出的意思
// 前面傳GET, 所以這裡用 $_GET 收
// 如果前面傳 POST, 這邊當然就用 $_POST 收
?>
```
> - 開啟 xampp 地址 = `http://IP/檔案` 或直接點 Go to Applocation 改後面資料處
> - 結果演示
> - `http://192.xx.xx.xx/test/ajax.html`
> <img src='https://i.imgur.com/QOSV3vy.png' style='width: 100px;'/> => 點擊後 => <img src='https://i.imgur.com/O4aFCbu.png' style='width: 300px;'/>
> - 網頁都沒有刷新就跳出結果了! 當然url 也沒有出現參數
> - #### 對照: 同一個 php 用 form 送資料
`$ vi form.html`
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- IP 我改掉了, 當然不可能是 xx.xx -->
<!-- action: 要送去哪-->
<!-- method: 怎麼送-->
<form action="http://192.xx.xx.xx/test/1.php" method='get'>
<input type="text" name='a'> <!-- 必須要有name-->
<input type="text" name='b'>
<input type="submit"> <!-- 還要有送出鈕 -->
</form>
</body>
</html>
```
> - 開啟 xampp 地址
> - `http://192.xx.xx.xx/test/form.html`
> <img src='https://i.imgur.com/4U3hA4R.png' style='width: 300px;'/> => 填值後送出
> - 畫面跑出 <img src='https://i.imgur.com/KWZwLZy.png' style='width: 50px;'/> 且
> 網址刷新過變成 `http://192.xx.xx.xx/test/1.php?a=100&b=200`
> - #### 小結: 表單與 ajax 主要差別
> - 會不會跳轉
> - 表單會
> - ajax 不會
> - 某些傳送方法會不會把參數寫在url上
> - 表單的 GET 會
> - ajax 的 GET 不會
> - 穩定性不同
> - 表單相對穩定, 因為是瀏覽器的功能
> - ajax是 js 的功能, 相對沒表單穩定, 但用戶體驗較好
### XMLHttpRequest
> - ajax 的本體, 所以有些人會叫 ajax 為 xhr
> - IE6 不兼容, IE6 使用 `ActiveXObject('Microsoft.XMLHttp')`
> - `ActiveXObject()` 是 IE 伺服器的插件
```htmlmixed=
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<script src='ajax2.js'></script>
</head>
<body>
<input type='submit' id='btn'/>
</body>
</html>
```
```javascript=
window.onload = function () {
let btn = document.getElementById('btn');
btn.onclick = function () {
let xhr = new XMLHttpRequest();
/* 兼容IE6 寫法
let xhr
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiceXObject('Microsoft.XMLHttp')
}
*/
console.dir(xhr); // XMLHttpRequest 實例對象
console.dir(xhr.readyState); // 0
xhr.open('GET', '1.php?a=3&b=2', true);
//xhr.open('GET', '2.php', true); // 我測試錯誤用的, 傳資料給不存在的檔案
console.dir(xhr.readyState); // 1
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr.readyState, xhr.status);
/*
2 200
3 200
4 200
*/
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log('OK', xhr.responseText);
// OK 5
// xhr.responseText 拿到回來的資料
// xhr.responseText 送資料給 1.php a=3, b=2 所以回來 5
} else {
console.log('No OK');
}
}
}
}
}
```
```php=
<?php
echo $_GET['a'] + $_GET['b'];
?>
```
### xhr.open()
`xhrReq.open(method, url, async, user, password);`
> - `async` 參數寫 Boolean, true 表示異步, false表示同步
> - 開同步會被瀏覽器罵, 不過沒有不能開~只是說爛東西別用而已
> Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.
### xhr.send()
> - 傳 body 的東西
> - 如果要傳 `{a:1, b:{c:2}}` 要怎麼處理?
> - JSON
> - `{"a": 1, "b": {"c": 2}}`
> - content-type: application/json
> - 變相
> - 編碼
> - `data="%7B%22a%22%3A1%2C%20%22b%22%3A%20%7B%22c%22%3A2%7D%7D"`
> - content-type: urlencoded
> ```javascript
> // 空格可以刪掉, 省空間
> encodeURIComponent(`{"a":1, "b": {"c":2}}`)
> "%7B%22a%22%3A1%2C%20%22b%22%3A%20%7B%22c%22%3A2%7D%7D"
> decodeURIComponent("%7B%22a%22%3A1%2C%20%22b%22%3A%20%7B%22c%22%3A2%7D%7D")
> "{"a":1, "b": {"c":2}}"
> ```
### xhr.onreadystatechange
`XMLHttpRequest.onreadystatechange = callback;`
> - 當準備狀態(通信狀態)改變時, 執行回調,
> - xhr.readyState :
> - 純粹紀錄通信狀態, 跟成功失敗與否無關
> - `0` : UNSENT -> 剛剛創建, 初始狀態, 還沒調用 `open()`
> - `1` : OPENED -> `open()`以調用已連接
> - `2` : HEADERS_RECEIVED -> `send()` 方法已被呼叫, 且可取得 header 與狀態
> - `3` : LOADING -> 回應資料下載中,此時 `responseText` 會擁有部分資料(head)。
> - `4` : DONE, 完成下載操作(body也搞定)
### xhr.status :
> - [HTTP 狀態碼](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81)
> - `1` : 資訊
> - `2` : 成功
> - `3` : 重定向
> - `301` : 永久重定向
> - `302` : 臨時重定向
> ```
> General
> Request URL: https://yahoo.com/ # 請求 yahoo.com
> Status Code: 301 # 重定向到
> Request Header
> location: https://www.yahoo.com/ # www.yahoo.com
> ```
> - `304` : 重定向緩存
> 
> - 如果再次向服務器要同一個檔名, 服務器會判斷時間版本是否最新
> - 如果是最新的, 返回 304
> - 如果是舊的, 重傳內容跟 202
> - 重定向 (redirect) vs 轉發 (forward)
> - 重定向:
> - 對瀏覽器下命令, 讓瀏覽器訪問新的 loaction
> - 網址會變
> - 轉發:
> - 服務器內部把請求交給其他 module 處理
> - 網址不變
>
> - `4` : 請求失敗(客戶端)
> - `404`
> - `5` : 服務器錯誤
> - `500` : 反正有錯都算 500
> - `503` : 服務器維護或擠爆了
> - `6` : 自定義(6以後)
### xhr.responseText
> - 回應文本
### xhr.responseXML
> - 回應 xml
> #### xml
> - xml 與 html 最大區別在於 html tagName 是固定的
```xml
<!-- json 一句話就搞定, 體積小, 又方便, 所以現在比較少見到 xml 了
let people = {name:'GodJJ', age: 20};
-->
<!-- xml 寫法 -->
<people>
<name>GodJJ</name>
<age>20</age>
</people>
<!-- 操作xml
xml = getElementsByTagName('name')[0];
-->
```
### ajax POST 問題
> #### 前情提要
> - 如果我把上面的案例的 GET 改成 POST, 參數改到 `send()` 裡 ( 用 body 傳 )
```javascript=
window.onload = function () {
let btn = document.getElementById('btn');
btn.onclick = function () {
let xhr = new XMLHttpRequest();
xhr.open('POST', 'g.php?', true); // 把 GET 改成 POST
xhr.send('a=3&b=2'); // 把 資料參數用 send() 傳
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log(xhr.responseText);
} else {
console.log('ERROR');
}
}
}
}
}
```
```php=
<?php
echo $_POST['a'] + $_POST['b']; # 接收 POST
?>
```
> - 運行的結果: 找不到 a b
```
<br />
<b>Notice</b>: Undefined index: a in <b>/opt/lampp/htdocs/test/g.php</b> on line <b>2</b><br />
<br />
<b>Notice</b>: Undefined index: b in <b>/opt/lampp/htdocs/test/g.php</b> on line <b>2</b><br />
0
```
> - 但是用 form 可以送到
```htmlmixed=
<head>
<meta charset='utf-8'>
</head>
<body>
<!-- GET 改成 POST -->
<form action='http://192.xx.xx.x/test/g.php' method='POST'>
<input type='text' name='a'>
<input type='text' name='b'>
<input type='submit'>
</form>
</body>
```
> - 查看報文
```
// FORM POST - request //
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 11
Content-Type: application/x-www-form-urlencoded #=> 問題在這行
Host: 192.168.64.2
Origin: http://192.168.64.2
Referer: http://192.168.64.2/test/gForm.html
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
// AJAX POST - request //
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7
Connection: keep-alive
Content-Length: 7
Content-Type: text/plain;charset=UTF-8 #=> 問題在這行
Host: 192.168.64.2
Origin: http://192.168.64.2
Referer: http://192.168.64.2/test/gAjax.html
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
```
> - Content-Type 的用意在於, 他是什麼類型, 服務器就怎麼解析
> - FORM POST 傳給服務器的 Content-Type 依照 enctype 屬性有三種,
> - application/x-www-form-urlencoded
> - 預設
> ```html
> <form action='' method='POST'></form>
> ```
> - urlencoded: `a=1&b=2&c=3`, 反正就是用 & 分隔數據的方法
> - 常用於處理簡單數據
> - multipart/form-data; boundary=----WebKitFormBoundaryWBQBnwHG6EAMjPQA
> - 通常是上傳檔案時用的
> ```html
> <form action='' method='POST' enctype='multipart/form-data'></form>
> ```
> - 以 delimiter 分隔數據
> - text/plain
> - 純文本
> - Ajax 預設的 Content-Type 是 `text/plain`, 用純文本解析 `'a=3&b=2'`, 當然拿不到值
> #### 解決辦法: 模仿 FORM 傳一樣的 Content-Type
### setRequestHeader()
`myReq.setRequestHeader(header, value);`
> - 設置 HTTP 請求頭部的方法,
> - header: 屬性名
> - value: 屬性值
> - 必須放在 `open()` 跟 `send()` 之間
> - 放在 `open()` 前面: 都還沒連接, 是要設置什麼?
> - 放在 `send()` 後面: 都送出請求了, 是要設置什麼?
> - 當然 Content-Type 也不用制式的跟著標準走, 隨便你怎麼設, 但是後端要寫處理方法
```javascript=
window.onload = function () {
let btn = document.getElementById('btn');
btn.onclick = function () {
let xhr = new XMLHttpRequest();
xhr.open('POST', 'g.php?', true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send('a=3&b=2');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log(xhr.responseText);
} else {
console.log('ERROR');
}
}
}
}
}
```
### 練習-模仿JQ: 封裝 GET 與 POST 成 ajax 函數
> - 版本一:
> - 先把函數拆出來, 並引入
> - 把需要的東西傳一傳
> - 由於 GET 跟 POST 有些差別, 必須做些處理
> - 參數:
> - GET 的參數跟 URL 是綁在一起的
> - `xhr.open('1.php?a=2&b=3')`;
> - POST 的參數是單獨丟到 `send()` 裡
> ```
> xhr.open('1.php');
> xhr.send('a=2&b=3');
> ```
> - 改 content-type
> - POST 要特別寫, GET 不用
> - 數據:
> - 數據一般都是 JSON 格式, 傳進來要處理成參數的樣子
> - 成功時, 使用 callback 把結果傳出來
```shell
$ tree
.
├── ajax.js #=> 拆出來可複用的 ajax 函數
├── g.php #=> 接收GET數據的 php
├── g2.js #=> 點擊調用的 js
├── gAjax.html #=> 開啟的頁面
└── p.php #=> 接收POST數據的 php
```
```htmlmixed=
<head>
<meta charset='utf-8'>
<script src='ajax.js'></script> # 引入
<script src='g2.js'></script>
</head>
<body>
<input type='submit' id='btn'>
</body>
```
```javascript=
function ajax(url, type='GET', data={}, success, error) {
let xhr = new XMLHttpRequest();
// - 數據處理
let arr = [];
for (let name in data) {
// 傳進來的參數 => {a:1, b:2}
// 想變成的形式 => 'a=1&b=2'
arr.push(`${name}=${data[name]}`) // arr.push(`a=1`)
}
console.log(arr); // ["a=1", "b=2"]
let dataStr = arr.join(`&`);
console.log(dataStr); // a=1&b=2
// - GET POST 判斷
if (type == 'POST') {
xhr.open('POST', url, true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send(dataStr);
} else if (type == 'GET') {
xhr.open('GET', url+`?`+dataStr, true); // 1.php?a=2&b=3
xhr.send();
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
// 只是輸出, 沒有任何意義, 所以要傳兩個 callback 把數據傳出去
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log('ajax: ' + xhr.responseText);
success && success(xhr.responseText) // 如果有 callback 近來就調用
} else {
console.log('AJAX: ERROR');
error && error(); // 如果有 callback 近來就調用
}
}
}
}
```
```javascript=
window.onload = function () {
let btn = document.getElementById('btn');
btn.onclick = function () {
ajax('p.php', 'POST', {a:3, b:4},
function (x) { // 成功時, 把php拿到的東西傳出來處理
console.log('回調:' + x);
},
function () {
console.log('回調: error');
})
}
}
```
```php=
<?php
echo $_GET['a'] + $_GET['b'];
?>
```
```php=
<?php
echo $_POST['a'] + $_POST['b'];
?>
```
> - 版本二: 參數太多了
> - 直接傳一個 JSON 進來
```javascript=
function ajax(option) {
let xhr = new XMLHttpRequest();
// 參數默認值處理
option = option || {};
option.data = option.data || {};
option.type = option.type || 'GET';
// 使用參數全部要改成從 option 找
let arr = [];
for (let name in option.data) {
arr.push(`${name}=${option.data[name]}`)
}
let dataStr = arr.join(`&`);
if (option.type == 'POST') {
xhr.open('POST', option.url, true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send(dataStr);
} else if (option.type == 'GET') {
xhr.open('GET', option.url+`?`+dataStr, true);
xhr.send();
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log('ajax: ' + xhr.responseText);
option.success && option.success(xhr.responseText)
} else {
console.log('AJAX: ERROR');
option.error && option.error();
}
}
}
}
```
```javascript=
window.onload = function () {
let btn = document.getElementById('btn');
btn.onclick = function () {
ajax({
url: 'p2.php',
type: 'POST',
data: {a:30, b:20},
success: function (x) {
console.log(`回調: ${x}`);
},
error: function () {
console.log(`回調: error`);
}
})
}
}
```
> - 版本三: 我想要 JSON 形式的數據
> - 傳出來的數據是從 php 返回的, 所以要改 php
> - 把資料轉成 json
```php=
<?php
$res = $_POST['a'] + $_POST['b'];
echo '{"resault": '.$res.'}'; # php 用 . 拼接字符串
?>
```
```php=
<?php
$res = $_GET['a'] + $_GET['b'];
echo '{"result": '.$res.'}';
?>
```
```javascript=
window.onload = function () {
let btn = document.getElementById('btn');
btn.onclick = function () {
ajax({
url: 'g.php',
type: 'GET',
dataType: 'json', // 資料類型 json
data: {a:1, b:2},
success: function (data) {
console.log(data);
},
error: function () {
console.log(`error`);
}
})
}
}
```
```javascript=
function ajax(option) {
let xhr = new XMLHttpRequest();
option = option || {};
option.data = option.data || {};
option.type = option.type || 'GET';
option.dataType = option.dataType || 'text'; // 預設類型為 'text'
let arr = [];
for (let name in option.data) {
arr.push(`${name}=${option.data[name]}`)
}
console.log(arr);
let dataStr = arr.join(`&`);
console.log(dataStr);
if (option.type == 'POST') {
xhr.open('POST', option.url, true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send(dataStr);
} else if (option.type == 'GET') {
xhr.open('GET', option.url+`?`+dataStr, true);
xhr.send();
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
// 處理資料
let data = xhr.responseText;
// 如果是 json
if (option.dataType == 'json') {
if (window.JSON && JSON.parse) { // 用 JSON.parse 轉
data = JSON.parse(data);
} else {
data = eval('{'+data+'}') // 相容處理
}
} else if (option.dataType == 'xml') { // 如果是 xml
data = xhr.responseXML; // 資料獲取就在 responseXML 裡
}
option.success && option.success(data);
} else {
console.log('AJAX: ERROR');
option.error && option.error();
}
}
}
}
```
> #### JSON.parse
> - `JSON.parse(text[, reviver])`
> - `text` : 要解析的字串
> - `reviver` : 這是一個 function, 會把解析出來的物件或數值當參數傳進去處理
> - 把字串轉成 JS 可執行的物件或數值, 並 return
> - IE8+
> - JSON 必須雙引號
```javascript=
JSON.parse('[]') // []
JSON.parse('1') // 1
JSON.parse('"dd"') // "dd"
JSON.parse('{\'a\':1}') // Unexpected token ' in JSON
JSON.parse('{"a":1}') // {a: 1} //=> 必須要雙引號
JSON.parse('undefined') // Unexpected token u in JSON
JSON.parse('null') // null
JSON.parse('true') // true
// 不允許尾部有逗號
JSON.parse('[1,2,3]') // [1, 2, 3]
JSON.parse('[1,2,3,]') // Unexpected token ] in JSON
JSON.parse('{"a":1, "b":2}') // {a: 1, b: 2}
JSON.parse('{"a":1, "b":2, }') // Unexpected token } in JSON
// JSON 必須雙引號
JSON.parse("{'error': 1, 'msg': 'GIVE ME PASSWORD!'}"); // Unexpected token ' in JSON
JSON.parse(`{"error": 1, "msg": "GIVE ME PASSWORD!"}`); // {error: 1, msg: "GIVE ME PASSWORD!"}
```
```javascript=
JSON.parse('{"a": 1, "b":2}', function () {
console.log(arguments);
})
// Arguments(2) ["a", 1, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// Arguments(2) ["b", 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// Arguments(2) ["", {…}, callee: ƒ, Symbol(Symbol.iterator): ƒ]
//=> JSON.parse() 會把 解析出來的值放在 [k,v] 傳進 reviver 參數裡
JSON.parse('{"a": 1, "b":2}', function (k,v) {
if (typeof v == 'number') {
return v * 2
}
return v;
})
// {a: 2, b: 4}
```
> #### JSON.stringify
> `JSON.stringify(value[, replacer [, space]])`
> - 把JS的值變成JSON格式的字串, 主要拿來轉物件的
```javascript=
// 主要用途
JSON.stringify({a:1, b:2}); // "{"a":1,"b":2}"
JSON.stringify({'a':1, b:2}); // "{"a":1,"b":2}"
// 其他也能轉字串, 不過主要不是拿來幹這事的
JSON.stringify([1,2,3]); // "[1,2,3]"
```
### jsonp (少)
> - 可以跨域, 安全性差
>
#### 先偷個數據下面要用
> - 打開搜尋引擎, 輸入關鍵字
> <img src='https://i.imgur.com/BjIt3Px.png' style='width: 300px;'/>
> - 此時會看到Google 推薦的關鍵字
> - 你每打一個字, 就會像服務器請求數據
> <img src='https://i.imgur.com/MYq6OZO.png' style='width: 200px;'/>
> 我打了apple 五個字, 所以請求了五次
> - 研究一下請求, 把請求copy出來
> - 方法很多, 可以直接看報文
> <img src='https://i.imgur.com/OukqUF0.png' style='width: 200px;'/>
> - 也可以右鍵 copy
> <img src='https://i.imgur.com/z7CoNFZ.png' style='width: 200px;'/>
```
# Google 搜 apple 的請求
https://www.google.com/complete/search?q=apple&cp=5&client=psy-ab&xssi=t&gs_ri=gws-wiz&hl=zh-TW&authuser=0&psi=tf8PXtGxI66xmAX88pCoCA.1578106806267&ei=tf8PXtGxI66xmAX88pCoCA
q=apple&
cp=5&
client=psy-ab&
xssi=t&
gs_ri=gws-wiz&
hl=zh-TW&
authuser=0&
psi=tf8PXtGxI66xmAX88pCoCA.1578106806267&
ei=tf8PXtGxI66xmAX88pCoCA
# 不過因為我看不懂 Google 的 callback 參數在哪, 所以去 yehoo 拿一份
https://tw.search.yahoo.com/sugg/gossip/gossip-tw-ura/?output=fxjsonp&callback=YUI.Env.JSONP.yui_3_12_0_3_1578107249520_1530&command=apple&nresults=12
output=fxjsonp&
callback=YUI.Env.JSONP.yui_3_12_0_3_1578107249520_1530& # 我要的東西
command=apple& # 我要的東西
nresults=12
# 把攏長的網址整理一下
# 我只是把 callback 名字改成 show, 其他都沒動
https://tw.search.yahoo.com/sugg/gossip/gossip-tw-ura/?output=fxjsonp&callback=show&command=apple&nresults=12
```
> - 搜一下網址, 會得到一份文檔, 有沒有整理都會拿到
```txt
# Google 搜 apple 的數據
)]}'
[[["apple",0],["apple id",0,[67],{"zl":40009}],["apple watch",0,[67],{"zl":40009}],["apple id 是什麼",0,[67],{"zl":40009}],["apple store",0],["apple tv 是什麼",0,[67],{"zl":40009}],["apple tv",0],["apple watch 5",0],["apple pay",0],["apple music",0]],{"k":1,"q":"roKR-j4OGoRF1fbiF_2KERX9cFU"}]
# yahoo 搜 apple 的數據(整理前)
YUI.Env.JSONP.yui_3_12_0_3_1578107249520_1530(["apple",["apple","apple store","apple watch","apple daily","apple tv","apple id","apple pay","apple pencil","apple ipad","apple airpods","apple music","apple iphone 11"],[],[]])
# yahoo 搜 apple 的數據(整理後)
# 數據都一樣, 只是改了 callback 名
show(["apple",["apple","apple store","apple watch","apple daily","apple tv","apple id","apple pay","apple pencil","apple ipad","apple airpods","apple music","apple iphone 11"],[],[]])
```
#### jsonp 原理
> - 準備一個回調函數讓人來調, 調用的過程一定會傳參數進來, 此時就拿到數據了
> - 如此就達到跨域效果
> - 導致缺點就是安全性太低(過於開放)
```html=
<head>
<meta charset='utf-8'>
<script>
// 準備一個回調函數, 等別人來調
function show(json) {
console.log(json);
}
</script>
<!-- 本地調用 -->
<script src='appleData.js'></script>
<!-- 給 yahoo 調用 -->
<script src='https://tw.search.yahoo.com/sugg/gossip/gossip-tw-ura/?output=fxjsonp&callback=show&command=apple&nresults=12'></script>
<!-- 結果都一樣
["apple", Array(12), Array(0), Array(0)]
["apple", Array(12), Array(0), Array(0)]
-->
</head>
```
```javascript=
// 調用函數
show(["apple",["apple","apple store","apple watch","apple daily","apple tv","apple id","apple pay","apple pencil","apple ipad","apple airpods","apple music","apple iphone 11"],[],[]])
```
#### 運用
> #### 版本一
> - 輸入搜尋字, 賦予 script 函數 src 值
```html=
<head>
<meta charset='utf-8'>
<script>
function show(ajax) {
console.log(ajax);
}
window.onload = function () {
let txt = document.querySelector('#txt');
// 當我輸入時, 讓 script 賦予 src
txt.oninput = function () {
let s = document.querySelector('#s');
s.src = `https://tw.search.yahoo.com/sugg/gossip/gossip-tw-ura/?output=fxjsonp&callback=show&command=${txt.value}&nresults=12`;
// 賦予之後 yahoo 會調用上面的 show 函數來執行
}
}
</script>
<!-- 寫一個script 標籤 -->
<script id='s'></script>
</head>
<body>
<input type='text' id='txt'>
</body>
<!-- 問題:
假設我輸入 apple, 但是結果只會產生一個陣列,
理論上應該每打一個關鍵字就會像服務器請求一次啊?
而且產生的陣列不是 apple 的相關 , 而是 ["a", Array(12), Array(0), Array(0)] !!!
原因:
當我打入第一個 a 時就已經調用一次 show()了
亦即 <script id='s'></script>
=> 變成 <script src='https://tw.search.yahoo.com/.../...&callback=show&command=a&...'></script>
=> 調用 show(["a", Array(12), Array(0), Array(0)]);
執行完了, 你再改他的 src 一萬次, 他也不會往回跑去執行上面的程式碼,
也就是說 script 標籤執行完就廢了, 跟 img 不同
-->
```
> #### 版本二
> - 解決上述問題
> - 解決編碼問題
> - 編碼:
> - 如果搜尋的是中文, 那`${txt.value}`變中文, url 地址就有中文,
> 有些瀏覽器並不會幫你編碼而會掛掉
> - 甚至如果搜尋的東西在 url 有特殊含意, 例如 `&` `=`..., 那肯定會出問題
> - `encodeURIComponent()`
> - 這東西就是編碼用的, 返回編碼後的結果
> ```javascript
> encodeURIComponent('中文') // "%E4%B8%AD%E6%96%87"
> encodeURIComponent('a&b') // "a%26b"
> ```
```htmlmixed=
<head>
<meta charset='utf-8'>
<script>
function show(ajax) {
// console.log(ajax);
// ["apple", Array(12), Array(0), Array(0)]
// Yahoo 調用回調的參數是陣列, 且數據在 [1]
console.log(ajax[1]);
// 額外寫個 ul li, 把數據呈現出來
let box = document.querySelector('#box');
box.innerHTML = '';
ajax[1].forEach(v => {
// console.log(v);
let li = document.createElement('li');
li.innerText = v;
box.appendChild(li);
})
}
window.onload = function () {
let txt = document.querySelector('#txt');
txt.oninput = function () {
// 上面的改版, 每次輸入都創建一個 script 標籤, 這樣就不會有標籤廢了的問題
let s = document.createElement('script');
// s.src = `https://tw.search.yahoo.com/sugg/gossip/gossip-tw-ura/?output=fxjsonp&callback=show&command=${txt.value}&nresults=12`;
// 編碼
s.src = `https://tw.search.yahoo.com/sugg/gossip/gossip-tw-ura/?output=fxjsonp&callback=show&command=${encodeURIComponent(txt.value)}&nresults=12`;
document.head.appendChild(s);
}
}
</script>
</head>
<body>
<input type='text' id='txt'>
<ul id='box'></ul>
</body>
```
### WebSocket
> - 性能高 vs ajax(相較差)
> - 雙向 vs ajax(單向: 客戶端向服務器)
> - ajax 只能開定時器一直看服務器變化通知
> - WebSocket 比較接近 callback, 一有變化馬上調用來通知
> - 直接跨域 vs ajax(跨域麻煩)
### 跨域
> - `google.com` -> `yahoo.com` 拿東西
> - 跨域有風險,例如 [XSS](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC), 所以瀏覽器會有所限制
> - 雖然有風險,但有時又是必須的
> - 有些網站有多個域名
> - 有些功能需要第三方數據, 例如登入, 需要第三方告訴你帳密對不對
> - 上面四種都能跨域, 差別在於方便與安全與否
### http
> - http vs https
> - s: security
> - 版本
> - http1.0 一次性連接
> - http1.1 保持連接
> - http2.0
> - 強制 https (提升安全)
> - 自帶雙向 (提升效能)
> - 多路復用 (提升效能)
> - [rfc](https://zh.wikipedia.org/wiki/RFC) :
> - 這是一套編碼,
> - 用來記錄各種網路協議規範
> - [http1.1](https://www.ietf.org/rfc/rfc2616.txt)
> - 三次握手
> <img src='https://i.imgur.com/14rIOGD.jpg' style='width: 300px;'/>
> - http 數據(消息)
> - head <= 32k
> - body <= 1G
> - [OSI](https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B) 七層模型
> - 實體層
> - 通信工程, 物理實體方面的
> - 資料連結層
> - 內網尋址
> - ARP, ICMP(ping)
> - 網路層
> - 外網尋址
> - IP
> - 傳輸層
> - 通信穩定
> - TCP: 傳輸控制協議
> - 保證到達(丟失重傳)
> - 保證正確(錯誤重發)
> - 保證順序
> - 文件下載, 聊天
> - UDP: 用戶數據協議
> - 三大不保證
> - 要求低延遲
> - 直播, IP電話
> - 會議層
> - 統一網路結構
> - 被傳輸層取代
> - 表達層
> - 紀錄狀態
> - vs. 無狀態: 服務器不會記得客戶端
> - 應用層取代
> - 應用層
> - 應用細節
> - HTTP, FTP, SMIP, POP3
> - HTTP 還是無狀態, 只是運用 coockie 紀錄狀態
## 進程與線程
### 進程
> - 進程擁有獨立的執行與存儲空間 (php node)
> - 進程執行成本高(慢)
> - 進程比較安全(隔離)
> - 進程間通信麻煩
> - 兄弟進程通信
> - 管道
> - 共享內存
> - socket
> - 書寫簡單
### 線程
> - 進程內所有線程共享一套空間 (java c)
> - 線程執行成本低(快)
> - 線程比較不安全(一個掛了, 如沒特別處理, 整個進程就可能掛了)
> - java 的每個線程都在虛擬機上執行, 虛擬機可以選擇只關掉一個線程
> - 線程間通信較容易
> - 書寫麻煩
### 其他
> - 拹程, 在線程裡面再切
> - 堵塞IO: 整個程序卡住
> - 非堵塞IO: 不卡住, 繼續往下執行
## 暫放
### 深拷貝與淺拷貝
> - 淺拷貝
```javascript=
let arr = [{a:1, b:2}, 2,3];
let arr2 = arr.concat([]);
arr2.push(4);
console.log(arr); // [ { a: 1, b: 2 }, 2, 3 ]
console.log(arr2); // [ { a: 1, b: 2 }, 2, 3, 4 ] //=> 淺拷貝成功
```
```javascript=
let arr = [{a:1, b:2}, 2,3];
let arr2 = arr.concat([]);
arr2.push(4);
arr2[0].a++; // 對 [0] 的那個對象的 a ++
console.log(arr); // [ { a: 2, b: 2 }, 2, 3 ] //=> 他也跟著變了
console.log(arr2); // [ { a: 2, b: 2 }, 2, 3, 4 ]
// 淺拷貝只拷貝一層, 如果是對象, 拷貝址
```
> - 深拷貝
```javascript=
let arr = [{a:1, b:2}, 2,3];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2.push(4);
arr2[0].a++;
console.log(arr); // [ { a: 1, b: 2 }, 2, 3 ] //=> 沒變
console.log(arr2); // [ { a: 2, b: 2 }, 2, 3, 4 ]
// 深拷貝把整個對象也都拷貝了一次, 兩個不同址
```
## 垃圾回收
> - Node, JS, java, python 等的 GC 核心邏輯:
> - 只要內存不在使用即釋放
> - 引用技術
> - C 自己手動釋放
> - 缺點: 內存泄露(忘記釋放)
## 小技巧
### Q. 如何做到 f5 的效果跟 ctrl + r 一樣, 強迫瀏覽器每次都向服務器從新獲取資料?
> - 1. 後台設置
> - 2. `<script src='1.js?v=202001011115'></script>`
> - 在引用後面加參數, 每次都改後面參數就行了
> - 讓 url 不同, 瀏覽器就會訪問伺服器
>
### Q. 服務器緩衝池?
> - 把最常用的放在內存
### 常見效果: 滑鼠移動, 背景移動
```htmlmixed=
<head>
<meta charset='utf-8'>
<style>
div {
height: 150px;
width: 150px;
position: absolute;
}
</style>
<script>
window.onload = function () {
let oDiv = document.querySelectorAll(`div`);
let divPos = Array.from(oDiv).map(div=>{
console.dir(div);
return {
left: div.offsetLeft,
top: div.offsetTop
}
})
console.log(divPos);
document.onmousemove = function (ev) {
// console.log(ev.clientX);
Array.from(oDiv).forEach((div, index)=>{
div.style.left = divPos[index].left + ev.clientX*div.style.zIndex/100 + `px`;
div.style.top = divPos[index].top + ev.clientX*div.style.zIndex/100 + `px`;
})
}
}
</script>
</head>
<body>
<div style='left: 100px; top: 100px; background: red; z-index:1;'></div>
<div style='left: 50px; top: 30px; background: blue; z-index:2;'></div>
<div style='left: 200px; top: 70px; background:yellow; z-index:3;'></div>
<div style='left: 250px; top: 200px; background: green; z-index:4;'></div>
</body>
```
## Q&A 筆記
> - Q. 頁面渲染
> #### 最早本意
> - 渲染就是把頁面的東西顯示出來, 跟繪製這個詞的意義差不多
> - 頁面渲染慢的原因
> - 頁面東西多
> - 圖片
> - 圖片還需要解碼等雜事
> - 經常做DOM操作
> - 每次操作都會改變頁面狀態而重新渲染
> #### 衍伸
> - 組裝頁面
> - 後台渲染 ejs
> - 前台渲染 vue
> - Q. 服務端渲染(server side rendering) 常用?
> - ejs
> - pug
> - vue 主要用來前端渲染
> - Q. 如何找回系統刪除的文件
> - 關鍵字: 磁盤分區表置回
>
> - Q. 如何調適
> - Debugger
> - 以網頁來說可以用開發者工具
> 
> 壓縮過的文件可以點擊左下那個 {} 來顯示
> - 這東西可以設置斷點啦之類有的沒的, 想要看某個東西的詳情,
> 可以選取後右鍵 add watch
> <img src='https://i.imgur.com/GWIe2wc.png' style='width: 250px'/><img src='https://i.imgur.com/3Sv3xfa.png' style='width: 250px'/>
> - NodeJS Debugger
> - 官方有說明, 這裡只寫開跟關
> ```shell
> % node inspect test.js
> < Debugger listening on ws://127.0.0.1:9229/a776ebd1-ebdc-4935-9b1f-37078d8b0d43
> < For help, see: https://nodejs.org/en/docs/inspector
> < Debugger attached.
> Break on start in test.js:1
> > 1 let a = 100;
> 2 console.log(a);
> 3
> debug> .exit
> ```
> - 打日誌, console, alert...
> - Q. 預編譯?
> - 前台預編譯
> - 系統自帶
> - polyfill
> - 後台預編譯
> - webpack
> #### Q. addEventListener 與 on 的區別
> - 首先 on 只能綁定一個事件, 而 addEventListener 可以綁定多個事件
> - 再來是因為 DOM2 事件(DOM Level 2)與 DOM3 事件(DOM Level 3) 的版本問題
> - DOM 既是對象(Document), 同時也是個標準
> - 早期事件都是 DOM1 & DOM2
> - HTML5 的標準都是 DOM3 & DOM4
> - 官方要求所有的 DOM3 事件 (HTML5) 都必須綁定,
> 這也是為什麼有些人稱 DOM3 為 HTML5 事件
> - 所以如果可以還是以 addEventLister 來綁定,
> 否則哪天瀏覽器廠商們遵照官方要求, 那新版本用 on 如果失效, 可能會很麻煩
>
> #### Q. blob
> - binary large object
> - 存放二進制數據的東西, 可以想像成一個大陣列, 每位是一個字節 `[Byte, Byte, Byte, ...]`
> #### Q. 推薦 發佈 NodeJS 的程序 的模塊
> - [forever](https://www.npmjs.com/package/forever)
> #### Q. 擴展名
> - 對電腦來說, 擴展名事實上沒有任何意義, 不可能音檔給flv就變影片
> - 所以有些人會選擇不加擴展名, 而是利用資料夾來分類
>
> #### Q. AJAX 刷進去的頁面性能比刷新頁面高嗎?
> - 不會, 如果要改善性能, 最好的版法是後台渲染
> - SPA 或 AJAX 主要的作用在於用戶體驗較好(一直刷新很煩)
> - SPA 主要用於 APP
> `<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />`
> - 用 IE7 的兼容模式來顯示頁面
> #### Q. 社群演算法如何完成
> - 最簡單的就是擷取你搜尋的關鍵字來推薦
> - 複雜的就是利用機器學習
> - 依據你使用的行為來加減權重
> - 例如你把某個東西看完,我就加幾分
> - 例如你看幾秒就關掉,我就減幾分
> - 例如你按檢舉,那我就爆扣
> - 例如你按收藏,那我就爆加
> - 你在網路上的一切行為都是在幫他訓練屬於你的權重機器人
>
> #### Q. vue-cli 構建的項目都是對 static 目錄下的資源引用(都是相對於服務器的根路徑),如果我不想在這個路徑下寫,怎辦?
> - nginx/ apache 目錄映射
> - webpack 目錄映射
> #### Q. 學習查看文檔?
> - 接觸一個新的東西時,先看第一頁,看他幹嘛的,運行環境,運行限制等
> - 接著看案例,把東西搞出來可以用 ( 沒有案例就別看了 )
> - 然後再回頭細看每個東西的細節