# 前端其他 ## 基本知識 ### 瀏覽器 > - 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 就好了 > ![](https://i.imgur.com/z2krukG.png) > - 但是這個路徑是直接從 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 中 > ![](https://i.imgur.com/vyCANbg.png) > - 因為在 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` : 重定向緩存 > ![](https://i.imgur.com/kLv6sas.jpg) > - 如果再次向服務器要同一個檔名, 服務器會判斷時間版本是否最新 > - 如果是最新的, 返回 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 > - 以網頁來說可以用開發者工具 > ![](https://i.imgur.com/wosUZRW.png) > 壓縮過的文件可以點擊左下那個 {} 來顯示 > - 這東西可以設置斷點啦之類有的沒的, 想要看某個東西的詳情, > 可以選取後右鍵 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. 學習查看文檔? > - 接觸一個新的東西時,先看第一頁,看他幹嘛的,運行環境,運行限制等 > - 接著看案例,把東西搞出來可以用 ( 沒有案例就別看了 ) > - 然後再回頭細看每個東西的細節