JavaScript-Promise

JavaScript是單執行緒的(single threaded)程式語言,也就是一次只能做一件事情,然而在前端會需要處理許多耗時的非同步事件,例如:ajax call API、定時器(setTimeout)、事件監聽(eventListener),一般我們很常會使用callback回調的方式,然而一層包一層的callback就會出現回呼地獄(callback hell),層次太多的巢狀callback,讓程式變得複雜、難以追蹤。

為了解決這個問題,Promise由此而生。

Event Queue

首先,要理解JavaScript是如何運作的之前,要先認識事件佇列(Event Queue),當JavaScript在執行時,會先將「setTimeout」、「ajax」、「click」等非同步事件放入Event Queue,並且先執行其他Execution context的事件後,才會回過頭去執行Event Queue中的事件。

以下方程式碼舉例,第8行的setTimeout為非同步事件,會先被放入Event Queue中,等待其他事件都處理完畢後,才會回過頭來執行Event Queue中的事件,因此第8行的setTimeout的執行順序在最後面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function doWork(){
var mom = "媽媽";

//立即函式
(function(){
console.log('打給媽媽');//執行順序1

setTimeout(function(){
console.log(mom+'回應')//執行順序3
},3000)
})();

//立即函式
(function(){
console.log('洗碗')//執行順序2
})();
}

doWork(); //執行

//執行結果:
//打給媽媽
//洗碗
//媽媽回電

這裡在第10行 setTimeout 的時間修改成 0 ,即使等待時間為 0 秒,不過因為 setTimeout 本身為非同步事件會被放入Event Queue,仍然會等所有執行堆中的事件都處理完之後,才執行Event Queue的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function doWork(){
var mom = "媽媽";

//立即函式
(function(){
console.log('打給媽媽');//執行順序1

setTimeout(function(){
console.log(mom+'回應')//執行順序3
},0)
})();

//立即函式
(function(){
console.log('洗碗')//執行順序2
})();
}

doWork(); //執行

//執行結果:
//打給媽媽
//洗碗
//媽媽回電

設定一個Promise

Promise 是一個用來表示非同步事件執行結果為成功或失敗的物件,預設的情況有兩個 callback 參數,resolve 代表成功時會執行的函式, reject 代表失敗時會執行的函式。這兩個callback皆會在該非同步事件執行完畢之後呼叫,成功用 .then() 承接,失敗則用 .catch() 承接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 先定義一個非同步事件
const newFunc=(value)=>{
//設定一個Promise物件
return new Promise((resolve,reject)=>{
if(value){
resolve('成功');//成功則執行resolve callback方法
}
else{
reject('失敗');//失敗則執行reject callback方法
}
});
};

//呼叫執行非同步事件,結束後用.then()及.catch()承接
newFunc(value).then((response)=>{
console.log('執行結果:'+response);//執行結果:成功
}).catch((error)=>{
console.log('執行結果:'+error);//執行結果:失敗
});

實際執行結果

鏈結Promise

Promise 的出現是為了解決回呼地獄 (Callback Hell),所以他強大的地放是用於串連,也就是依序呼叫兩個以上的非同步事件,稱作 Promise Chain。

只要在 Promise chain 中的任一個事件出現錯誤,而執行呼叫 reject callback, Promise chain 的執行會直接中斷,進到catch中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//先定義一個非同步方法,其回傳值為一個 Promise 物件
const newFunc = (time) => new Promise((resolve,reject)=>{
//setTimeout 事件會被放進佇列中,其執行順序會在if之後
setTimeout(()=>{
resolve(`${time/1000}秒 成功`);
},time);

if(time === 0){
reject(new Error(`${time/1000}秒 失敗`));
}
});

//執行 Promise chain
//首個 newFunc resolve 的回傳值由 response1 承接
newFunc(1000).then((response1)=>{/
console.log('執行一:'+response1);
return newFunc(1000);//第二個 newFunc resolve 的回傳值由 response2 承接
}).then((response2)=>{
console.log('執行二:'+response2);
return newFunc(0);//第三個 newFunc resolve 的回傳值由 response3 承接
}).then((response3)=>{
console.log('執行三:'+response3);
return newFunc(1000);//第四個 newFunc resolve 的回傳值由 response4 承接
}).then((response4)=>{
console.log('執行四:'+response4);
}).catch((error)=>{//catch 承接每次 newFunc reject 拋出的 error
console.log('失敗:'+error)
})

實際執行結果

鏈結接續在catch後方

在 catch 之後,仍然可以接續 Promise chain。

1
2
3
4
5
6
7
8
9
10
11
12
13
newFunc(1000).then((response1)=>{/
console.log('執行一:'+response1);
return newFunc(1000);//第二個 newFunc resolve 的回傳值由 response2 承接
}).then((response2)=>{
console.log('執行二:'+response2);
return newFunc(0);//第三個 newFunc resolve 的回傳值由 response3 承接
}).then((response3)=>{
console.log('執行三:'+response3);
}).catch((error)=>{//catch 承接每次 newFunc reject 拋出的 error
console.log('失敗:'+error)
}).then(()=>{
console.log('最終執行');
})

執行結果

Webpack-在vue專案中引用SCSS

vue-loader 默認只支持 SASS,如果要是想要使用 SCSS,必須安装 node-sasssass-loader,並修改相關 webpack 配置。

step1.安裝dependency

  • sass-loader:將 .scss 檔案轉換成 css
  • node-sass:因相依性,需搭配使用
    1
    npm install node-sass sass-loader -D

如果尚未安裝style-loader(或 vue-style-loader)css-loader也須一併安裝

1
npm install style-loader ss-loader -D

step2.修改webpack配置

概念上,是先透過sass-loader.scss 檔案轉換成 css,再交給css-loader,最後再交給style-loader處理,所以依照使用loader
的順序,將loader寫入rules中。

打開webpack.config.js, 在module里的rules中加上:

1
2
3
4
5
module: {
rules: [
{ test: /\.scss$/, use: ['vue-style-loader', 'css-loader','sass-loader'] },
],
},

step3.vue文件中的style使用scss

在 style處聲明:

1
2
3
<style rel="stylesheet/scss" lang="scss">
//...內容
</style>

如果僅限於當前組件有效,建議要加上 scoped,避免組件間的樣式互相影響:

1
2
3
4
<style rel="stylesheet/scss" lang="scss" scoped>
//...內容
</style>
复制

如果想要將SCSS獨立於另一個檔案中,則可以在<script>開頭用import將檔案引入

1
2
3
4
<script scoped>
import './style.scss';
//...
</script>

或是使用<style>tag,用src引入SCSS檔案(提示:一個.vue可以有多個<style> tag)

1
<style lang="scss" src="./style.scss"></style>

補:在Vue專案中引用bootstrap.scss

  • lang="scss" 告訴 webpack 我們這段 style 開發是使用 SCSS 語言,要使用 sass-loader 編譯。
    1
    <style lang="scss" src="./assets/css/bootstrap/stylesheets/_bootstrap.scss"></style>

或是直接在main.js 直接引入 scss(如果是.vue組件檔案,則是在組件中的script用import引入)

1
import './assets/css/bootstrap/stylesheets/_bootstrap.scss';

參考資料

Webpack 4 打包 Sass to Css 範例

vue & vuex 26 - 使用 SASS 管理 CSS - webpack sass-loader

vue里怎么用scss

Vue-cli透過webpack來加載使用pug/scss及BootStrap4

How to use SCSS with Vue.js Single File Components

FontAwesome-在Vue專案中使用

step1.安裝

  • 在 terminal 使用 npm 安裝 @fortawesome/fontawesome-svg-core@fortawesome/vue-fontawesome

    1
    2
    npm install @fortawesome/fontawesome-svg-core --save
    npm install @fortawesome/vue-fontawesome --save
  • 再依照各自使用 font-awesome的版本及需要的樣式作安裝,有Free, Pro, Brands, Solid, Regular

    1
    2
    3
    4
    5
    6
    $ npm install @fortawesome/free-solid-svg-icons --save
    $ npm install @fortawesome/free-regular-svg-icons --save
    $ npm install @fortawesome/free-brands-svg-icons --save

    //Pro
    $ npm install @fortawesome/pro-regular-svg-icons --save

step2.配置

  • main.js中,先從fontawesome-svg-core叫出library
  • 接著import會使用到的 icon
  • 並將 icons 加入 library中

    1
    2
    3
    4
    5
    import {library} from '@fortawesome/fontawesome-svg-core';
    import { faAd } from '@fortawesome/free-brands-svg-icons';
    import { faUsers } from '@fortawesome/free-brands-svg-icons';

    library.add(faAd,faUsers);
  • 最後登記物件,命名為’font-awesome-icon’,即可在HTML或<template>中用<font-awesome-icon>呼叫使用

    1
    2
    3
    import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';

    Vue.component('font-awesome-icon', FontAwesomeIcon);

補充:也可以將整個Style下的所有icons全部引入,但不建議此方法,會增加不必要的載入

1
2
3
import { fab } from '@fortawesome/free-brands-svg-icons';

library.add(fab);

step3.呼叫使用

  • 可以直接在HTML中
    1
    2
    <font-awesome-icon icon="ad" />
    <font-awesome-icon icon="users" />

或是指定樣式前綴詞

1
2
<font-awesome-icon :icon="['fa', 'ad']" />
<font-awesome-icon :icon="['fab', 'users']" />

可以再加上size,size清單可參考官網說明

1
<font-awesome-icon icon="ad" size='lg'/>

參考資料

Webpack- Single-file components(.vue)

在 Webpack 4 之後的版本,將 Webpack 及 Webpack-cli 拆開了,因此在載用 webpack 時,也要安裝 webpack-cli。

在搭建(scaffold)一個專案時,最推薦使用框架的命令行介面(command lin interface, CLI),提供一個入口檔案及配置文件,這個鷹架(scaffolding)會透過開發建置流程,最終將檔案打包出一個生產版本。

一般使用 Vue.js 僅需要用<script>標籤引入 CDN,不需要開發建置過程,專案中即可使用 Vue.js。但是,如果要使用 Vue single-page components(SFCs),就一定要使用開發建置工具,將.vue檔案轉換為瀏覽器可以解讀的 HTML、CSS、JavaScript。

Vue-cli 提供預設好的開發建置流程,但是,如果想要自己定義,則可以使用 Webpack 來搭建(scaffold)專案。

Webpack

Webpack 是一個模組打包工具,他可以將多個檔案中的程式碼合併成一個。在 Webpack 出現之前,必須要透過<script>標籤將多個 JavaScript 引入到一個檔案中。

此外,Webpack 也透過 loader 和 plugin 來轉換程式碼,例如將 ES6+ 轉換為 ES5,或是將 SCSS 轉為 CSS。

用 SPC 實作一個 Vue 專案

Step 1.專案結構建置

最基本的專案應當要有一個 HTML、一個 JavaScript、一個 Vue 檔案(.vue),通常會將這些檔案放置在 src 的資料夾中,用以區分這些是我們寫的程式碼,避免跟 Webpack 打包建置後的檔案搞混。

我們會使用到 Webpack ,因此需要一個 Webpack 的配置檔案,通常命名為 webpack.config.js

此外,我們會需要 Babel 將 ES6 編譯為 ES5,Babel 本身也是 Webpack 的附加插件,因此需要一個 Babel 的配置檔案,命名為.babelrc.js

所以先依照上述說明建置如下專案結構:

1
2
3
4
5
6
src(forder)
|_myApp2.vue
|_index.html
|_main.js
webpack.config.js
.babelrc.js

接著,我們會使用 NPM 來做依賴(dependencies)管理,需要透過指令將專案初始化,並跟著 terminal 視窗中的提示問題完成創建:

1
npm init

初始化之後,會發現專案中自動多出了一個 package.json 檔案。所以目前應該會看起來像這樣:

Step 2.安裝套件依賴

以下是幾個我們目前會使用到到的套件依賴:

  • vue: JS 前端框架
  • vue-loader & vue-template-compiler:將 vue 檔案轉換為 JavsSript
  • webpack:轉換並打包程式碼
  • webpack-cli:用以執行 Webpack 命令
  • webpack-dev-server:雖然在這裡示範的小專案不會用到 HTTP request,但使我們會需要開發伺服器來服務這個專案
  • babel-loader:將 ES6 語法轉換為 ES5(需要搭配下面兩個插件依賴使用)
  • @babel/core & @babel/preset: Babel-loader 本身無法作用,必須搭配這兩個插件才能將 ES6 語法轉換為 ES5
  • css-loader:會將.vue檔案中的 CSS 或是任何引入的 CSS,載入到 JavaScript 檔案中,並且解析路徑。
  • vue-style-loader:從 css-loader 取得 CSS 之後,會再透過 vue-style-loader 引入到 HTML 中,會在 HTML 的 head 中,自動生成出一個<style> 標籤。簡單來說,css-loader 是單純將 css 作載入 js;style-loader 則是作解析的動作,讓頁面能讀懂 css,所以它們倆合體就可以達成目的了。
  • html-webpack-plugin:將 index.html 還有打包後的 js 引入到<head>,並且複製到 dist 資料夾中。
  • rimraf:讓我們可以從命令行中刪除檔案

接著就在命令行中輸入指令,開始安裝依賴:分開發依賴(指令添加-S)、生產依賴(指令添加-D)

1
npm install vue -S
1
npm install vue-loader vue-template-compiler webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env css-loader vue-style-loader html-webpack-plugin rimraf -D

安裝完畢之後,可以在 package.json 中查看,另外,專案中也會自動產生一個 node_modules 的資料夾,目前專案看起來會像這樣:

Step 3.檔案創建

myApp.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div id="app">
{{ message }}
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello World',
};
},
};
</script>

<style>
#app {
font-size: 18px;
font-family: 'Roboto', sans-serif;
color: blue;
}
</style>

index.html

1
2
3
4
5
6
7
8
<html>
<head>
<title>Vue Hello World</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

main.js

1
2
3
4
5
6
7
import Vue from 'vue';
import myApp from './myApp.vue';

new Vue({
el: '#app',
render: (h) => h(myApp),
});

解釋一下第 6 行程式碼render: h => h(myApp)的縮寫流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 完整原始程式碼
render: function (createElement) {
return createElement(myApp);
};
// 簡化
render(createElement){
return createElement(myApp);
};
// 再簡化
render(h){
return h(myApp);
};
// 最終簡化
render(h) => h(myApp);

// createElement函式是用來生成HTML DOM元素
// 也就是生成HTML結構中的 script 腳笨
// 所以用Hyperscript取h作為程式碼中createElement的簡寫。

babelrc.js

1
2
3
module.exports = {
presets: ['@babel/preset-env'],
};

Step 4.配置 Webpack

到目前為止,Webpack 需要訪問的檔案都備齊了,接著剩下最後兩件事情:告訴 Webpack 做什麼,並且執行 Webpack。

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
entry: './src/main.js',
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' },
{ test: /\.vue$/, use: 'vue-loader' },
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader'] },
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
new VueLoaderPlugin(),
],
};
  • 第 1、2 行,import 下面會用到的 plugin。注意!loader 不需要 import!(不過在這個案例中,第 9 行的 vue-loader 會需要用到第 17 行的 plugin 才能運作,但是像 babel-loader 就不需要。)
  • 第 4 行,將配置(configuration)以物件的型態 export,這也是執行 Webpack 命令的入口。
  • 第 5 行,這是入口模組(在 Webpack 中,每個檔案都可視為一個模組),Webpack 需要一個起始點,所以 Webpack 會從main.js開始查找去梳理所有的程式碼。
  • 第 7 行,用rules陣列來告訴 Webpack 在梳理程式碼時要遵循的規則。
  • 第 8 行,這條規則指示 Webpack 當遇到以.js結尾的檔案時,要使用babel-loader去轉換程式碼。
  • 第 9 行,這條規則指示 Webpack 當遇到以.vue結尾的檔案時,要使用vue-loader(需要搭配地 17 行的插件)去轉緩程式碼。
  • 第 10 行,這條規則是指示 Webpack:先用css-loader.vue檔案中拿出 CSS,再用vue-style-loader轉以<style>tag 的形式放進 HTML 中。(有些檔案會需要兩個 loader 去執行,在 Webpack 中的處理流程有點違反直覺,通常都是從左到右,但是 Webpack 是從右到左。)
  • 第 13 行,用plugins陣列,放入我們需要用到的 plugin
  • 第 14 行,HtmlWebpackPlugin這個 plugin 會獲取index.html檔案的位置,並且將打包後的 JavaScript 檔案以<script>標籤放入其中。此外,這個 plugin 也會複製 HTML 檔案到 build 後的資料夾中。
  • 第 17 行,VueLoaderPlugin這個 plugin 會搭配第 9 行的vue-loader解析.vue檔案。

HtmlWebpackPlugin 獲取 index.html 文件的位置,並通過腳本標記將捆綁的 JavaScript 文件添加到其中。

Step 5.執行 Webpack

上述所有的配置都完成之後,最後只要設定好package.json檔,就可以執行 Webpack 了。在理想的情況下,當我們修改內容,會希望瀏覽器自動更新,因此我們需要webpack-dev-server

webpack-dev-server的功能簡而言之,就是當檔案有修改儲存,重新 bundle 之後,開發人員不需要按下重整按鈕,就會通知瀏覽器執行畫面重整。但是要注意,webpack-dev-server僅適用於開發階段,它產生出的bundle.js並不會出現在我們專案中的 dist 資料夾,而是暫存在記憶體中,所以如果要上正式發布環境,請參考 Step7。

package.json

  • 在第 7 行scripts的地方刪掉原先預設的test,改以serve取代之。(命名為 serve 並非一定,可以自己隨心改變)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"name": "vue-single-file-components",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"serve": "webpack-dev-server --mode development"
},
"author": "",
"license": "ISC",
"dependencies": {
"vue": "^2.6.10"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/preset-env": "^7.4.5",
"babel-loader": "^8.0.6",
"css-loader": "^3.0.0",
"html-webpack-plugin": "^3.2.0",
"rimraf": "^2.6.3",
"vue-loader": "^15.7.0",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.34.0",
"webpack-cli": "^3.3.4",
"webpack-dev-server": "^3.7.1"
}
}

接著就可以在 terminal 或 command line 裡面輸入指令啟用 Webpack:

1
npm run serve

或是

1
webpack-dev-serve --mode development

如果一切正確無誤,會看到「Compiled successfully」的字樣:

接著使用瀏覽器打開http://localhost:8080,可以看到畫面正確顯示:

Step 6.熱加載

有個狀況是,當我們在瀏覽器輸入資料後,又編輯檔案並存檔,此時瀏覽器中會重新刷新,剛剛輸入的資料都消失,所以我們需要使用 Webpack 內建的 Hot Module Replacement。

先修改myApp.vue模擬這個狀況:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div id="myApp">
<input type="text" v-model="message" placeholder='Enter here'></input>
<h1>{{ message }}
</h1>
<h3>this is h4</h3>
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello',
};
},
};
</script>

<style>
#myApp {
font-size: 18px;
color: brown;
background-color: antiquewhite;
}
</style>

其實截至目前為止,在配置文件中我們都尚未引入 Webpack 物件,所以我現在們要來引入它,這樣我們才能 access 它的 plugin。

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');

module.exports = {
entry: './src/main.js',
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' },
{ test: /\.vue$/, use: 'vue-loader' },
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader'] },
],
},
devServer: {
open: true,
hor: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
new VueLoaderPlugin(),
new webpack.HotModuleReplacementPlugin(),
],
};
  • 第 3 行,引入 webpack
  • 第 23 行,因為我們在第 3 行引入了 webpack,所以可以使用webpack.HotModuleReplacementPlugin這個插件達到熱加載的功能
  • 第 14 行,sevServer 這是額外丟給 webpack 的 option。
  • 第 15 行,設定hottrue
  • 第 16 行,設定opentrue,當我們執行 nom run serve 時,會自動打開瀏覽器視窗

接著輸入執行指令

1
npm run serve

可以看到瀏覽器視窗自動打開了,且 console 的地方可以看到「Hot Module Replacement enabled」熱加載的功能已被成功啟用,此時編輯檔案中的內容再儲存,瀏覽器並不需要重新刷新,內容就會相對被更新。

Step 7.打包建置專案

前面 Step 5.提到在開發階段可以使用webpack-dev-server讓專案打包後可以在瀏覽器上被檢視,可是 bundle 後的檔案僅暫存於記憶體中,如果需要將專案打包壓縮後發布,則還是需要改用 webpack 指令。

所以進入 package.json 檔案中,在 scripts 中我們要新增幾個指令:

1
2
3
4
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && webpack --mode production"
},

因為每次執行 webpack 時,都會創造出 dist 資料夾以及其中的檔案,如果每次都要手動刪除 dist 資料夾在執行很麻煩,因此我們這邊使用 rimraf 的指令設定。

  • 第 2 行,前面專案中已經安裝 rimraf 依賴,在這邊可以使用它的指令,因此當我們在命令列輸入 npm run clean ,會獨立執行刪除的動作,rimraf dist告訴命令列刪除 dist
  • 第 3 行,這裡實際上有兩個指令用 && 分隔出來,會先執行npm run clean接著再執行webpack --mode production,是告訴 Webpack:先執行刪除動作,接著跑 webpack 建置(production 模式)動作

完整的package.json會長得像這樣:

接著,輸入建置指令:

1
npm run build

參考 terminal 中的提示,可以看到先執行了 rimraf dist 動作,接著成功完成建置打包的動作。

在專案中可以看到左邊專案結構自動生成了一個 dist 資料夾,裡面有打包後的 js 檔案,以及從 src 中複製過來的 html 檔案。

因為我們的 html 中沒有 HTTP request 動作,所以可以直接用瀏覽器打開 dist 中的 index.html。如果專案中有 HTTP request ,則需要將專案放到 server 環境中才能正常運作。

用瀏覽器打開index.html,並且檢視開發者工具,可以看到 element 的地方多出了一些內容,這些內容去比對index.html檔案是沒有的,比如說 <style>tag,因為這些內容是在生成過程中透過 JavaScript 動態放入的。

參考資料

Webpack-引入模組運用

環境建置

1. 首先在專案中加入 npm

1
npm init

or 加上-y可以快速建立,略過中間自定義中間自定義的項目輸入

1
npm init -y

完畢之後,會看到多了一個 package.json 檔案,並產生一些基本的內容

2. 安裝「開發環境」套件依賴(視需求安裝)

1
npm install webpack webpack-cli babel-loader babel-core css-loader vue-loader vue-template-compiler -D

指令後方加上-D--save-dev的縮寫,表示會將安裝的套件記載在 package.json 中的 devDependencies

  • webpack:轉換並打包程式碼
  • webpack-cli:用以執行 Webpack 命令
  • babel-loader:將 ES6 語法轉換為 ES5 需要搭配下面兩個插件依賴使用)
    • @babel/core & @babel/preset: Babel-loader 本身無法作用,必須搭配這兩個插件才能將 ES6 語法轉換為 ES5
  • css-loader:將撰寫的 css 檔,使用 imports 和 url(…) 來回傳到 JavaScript 檔案中,通常都還會搭配 style-loader 作解析的動作。
  • vue-loader:將 vue 檔案轉換為 JavsSript
  • vue-template-compiler:將 vue 檔案轉換為 JavsSript

3. 安裝「生產環境」套件依賴,例:vue(視需求安裝)

1
npm install vue -P

指令後方加上-P 是預設值可以不加,表示會將安裝的套件記載在 package.json 中的 dependencies

or 在指令後方加上-S

1
npm install vue -S

指令後方加上-S--save的縮寫,表示會將安裝的套件記載在 package.json 中的 dependencies

  • vue: JS 前端框架

Webpack 概念說明

首先定義一個入口 js 檔案,而讓其他所有的 js 檔案都通過這個入口檔案,去執行底下的腳本。

而 Webpack 的執行入口也就是這個入口檔案,通過打包之後,最終會產生出 bundle 後的檔案。

最後,在 htmlscript 讀取的檔案,則是打包後的檔案。

專案結構建置

創建一個 src 資料夾,代表其中的文件為 sourse 來源,在其中有一個主要的 index.js 檔案(命名不限,通常是 main.js 或 index.js),另外,有個 components 資料夾,裡面放著各種可以重複被利用的模組、函式 js 檔案。

1
2
3
4
5
6
src
|_index.js
|_components
|_add.js
|_minus.js
|_ ...其他模組.js

完整的專案結構應該會長得像下方這張圖:

流程說明

接下來會依序修改幾個檔案:

  • index.html
  • index.js
  • add.js
  • webpack.config.js
  • package.json

index.html

</body>前引入打包後的 js 檔案,路徑參考實際專案中的資料夾結構

1
<script type="text/javascript" src="./dist/index.bundle.js"></script>

index.js

寫入一些內容,其中 import 是 ES6 語法,透過 import 將其他 component 引入使用

1
2
import add from './components/add';
console.log(add(1, 2));

add.js

透過 export 導出函式內容

1
2
3
export default function(a, b) {
return a + b;
}

webpack.config.js(Webpack 配置重點)

  • 首先要有一個入口檔案,在第 2 行的 entry 屬性中,定義入口檔案的路徑
  • 第 3 行是打包後的轉出文件,在 filename 中定義轉出後的檔案名稱
  • 第 6 行,watch 屬性是定義是否實時自動偵聽,可以省去每次修改都重新手動打包的手續
  • 第 7 行,是定義打包出來的內容是否要壓縮
1
2
3
4
5
6
7
8
9
10
module.exports = {
entry: './src/index.js',
output: {
filename: 'index.bundle.js',
},
watch: true,
optimization: {
minimize: false,
},
};

上述案例,執行 Webpack 打包,會自動新增一個 dist 資料夾,並將編譯後的檔案存於其中。

webpack.config.js-自定義轉出路徑

若想自定義轉出路徑,可以新增 path,改寫如下:

  • 第 6 行的意思,
    • __dirname:在當前的資料夾
    • resolve:搜尋
    • build:名稱為 build 的資料夾
  • 第 7 行的意思,
    • filename:打包後的檔案命名為’index.bundle.js’,
1
2
3
4
5
6
7
8
9
10
11
12
13
var path = require('path'); // <---- 引入path

module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'), // <---- 自定義資料夾路徑
filename: 'index.bundle.js',
},
watch: true,
optimization: {
minimize: false,
},
};

webpack.config.js-打包多個入口檔案

  • 第 4 行的入口,改為物件寫法,多個”key/value” pairs,
  • 第 10 行,轉出檔案的命名以原檔案的命名[name]去做自動命名,方便辨識
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var path = require('path');

module.exports = {
entry: {
index: './src/index.js',
index2: './src/index2.js',
},
output: {
path: path.resolve(__dirname, 'build'), // <---- 自定義資料夾路徑
filename: '[name].bundle.js',
},
watch: true,
optimization: {
minimize: false,
},
};

package.json

在 script 屬性中新增 build 及 dev,並且定義指令訊息內容

1
2
3
4
5
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production",
"dev": "webpack --mode development"
},

執行指令

1
npm run build

或是

1
node_modules/.bin/webpack

執行結果

參考資料

Vue-單一元件檔(Single-file components)

優缺點

在許多 Vue 專案中,可以先定義一個「全域組件」Vue.component,接著再創建一個「Vue 實體」new Vue({el:'#app'}),並且將全域組件指定到頁面中的一個容器元素中。

這種方式卻僅適合中小型的專案,但是當專案更於複雜,會出現一些缺點:

  • 全域定義問題(Global definitions):每個組件(component)的命名不能重複。
  • 字串模板問題(String templates):在元件中的template屬性只能用字串,當使用編譯器(例:vscode)撰寫內容時,無法有效地以文字顏色區分、辨別內容
  • CSS 不支援問題(No CSS support):在元件中只能提供 HTML 及 JavaScript,無法支援 CSS
  • 預處理問題(No build step):元件預設中只能使用 HTML 及 ES5,不能使用預處理工具,例:Babel

單一元件檔(Single-file component)」就可以解決上述問題,它同時包含 HTML、JavaScript 及 CSS,一個.vue檔案就代表一個組件,且支援使用 Webpack 或 Browserify 等打包建置工具。

檔案樣板結構

.vue檔案的基本內容結構:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<!--HTML內容 -->
</div>
</template>

<script>
// JAvaScript內容
</script>

<style>
/*CSS內容 */
</style>

若不喜歡 HTML、JavaScript 與 CSS 三者合一,也可以改為將 JavaScript 及 CSS 分離成獨立的檔案再載入的寫法:

1
2
3
4
5
6
<!-- my-component.vue -->
<template>
<div>This will be pre-compiled</div>
</template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>

templete 區塊

<template>樣板裡的內容會被編譯為元件中 templete屬性的值,樣板中只能有一個根元素,也就是說所有內容要包在一個<div></div>中。

預設使用 HTML,但可以使用 lang 屬性指定為其他樣板系統,例:

1
<template lang='jade'>...</template>

script 區塊

這個區塊中只能有一個,且最終必須匯出一個 Vue 物件。

預設為 JavaScript,要使用其他 JS 套件可以使用 require()。如果有安裝 babel-loader 在支援 ES2015 的環境中,則可以使用 import 及 export 語法。例:

1
2
3
4
5
import Vue from 'vue';
import loadingFunc from '../Loading';
export default {
<!--component content -->
};

這裡記得 from 的部份,如果使用的是 node_modules 也就是由 npm / yarn 安裝的套件,直接寫套件的名稱;而如果是自己的 JavaScript 檔,則要在檔名前加上 ./。

style 區塊

一個.vue檔案可以有零到多個<style>,且可以是全域或區域的<style>區塊。

<style>的預設為全域的,如果想要限制為區域作用(只在該組件中有效),可以加上scoped屬性:

1
<style scoped>...</style>

預設為 CSS,也可以更換為 SCSS:

1
<style lang="sass">...</style>

簡易範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<p>{{ greeting }} World!</p>
</div>
</template>

<script>
module.exports = {
data: function () {
return {
greeting: 'Hello'
}
}
}
</script>

<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>

撰寫.vue 檔案

先確定專案的結構,根目錄會有一個index.html作為執行 Vue 的入口;接著在 src 資料架,主要的程式碼都會放在這裡;main.js是程式的進入點,也可以透過 webpack.config.js 設定檔修改、自定義配置;myApp.vue則是這裡的重點,最終會匯出一個 vue 組件。

1
2
3
4
5
6
7
8
project根目錄
|_index.html
|_src
|_main.js
|_component
|_myApp.vue
|_build
|_webpack.config.js

index.html

放置在根目錄底下的index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
  • 第 8 行,這是 Vue 的進入點。
  • 這個檔案中只有最基本的 HTML 內容,沒有 CSS、JavaScript,第 9 行,建置後的檔案會自動引入。

myApp.vue

component 資料夾下方放置所有的 vue 組件,示範一個myApp.vue組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div>
<h1>Hello</h1>
<p class="hello">{{ msg }}</p>
</div>
</template>

<script>
export default {
data () {
return {
msg: '這是一個 Vue 組件檔!'
}
}
}
</script>

<style scoped>
h1 {
color: green;
}

.hello {
font-size: 1.5em;
color: blue;
}
</style>

main.js

myApp.vue組件建立好之後,接著就可以引入到main.js
main.js。是程式的進入點,也可以透過 webpack.config.js 設定檔修改、自定義配置。

基礎的內容:

1
2
3
4
5
6
import Vue from 'vue';

new Vue({
el: '#app',
template: '<h1>Hello Vue</h1>',
});

如果要引入myApp.vue組件,則要添加一些內容,如下:

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue';
import MyApp from './myApp';
import MyApp2 from './myApp2';

/* eslint-disable no-new */
new Vue({
el: '#app',
components: { myApp, myApp2 },
template: '<div><myApp></myApp><myApp2></myApp2></div>',
});
  • 第 1 行import'vue'是使用 node_modules 由 npm / yarn 安裝的套件,所以可以直接寫套件的名稱。
  • 第 2 行import'./MyApp'是自己的 JavaScript 檔,則要在檔名前加上 ./
  • 第 8 行,為 vue 實例添加components屬性,其值為引入組件,如有多個組件則用逗號分隔。
  • 第 9 行,這樣就可以將 templete 樣板中改寫為<myApp></myApp>,立即使用引入的組件,但切記只能有一個<div>根元素,所以所有內容都要包在其中。

組件加在組件中

除了上述將所有組件都引入到 main.js 中的做法,也可以把 myApp2 組件加在 myApp1 組件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<h1>Hello</h1>
<p class="hello">{{ msg }}</p>
<myApp2></myApp2>
</div>
</template>

<script>
import myApp2 from './myApp2'

export default {
data () {
return {
msg: '這是一個 Vue 組件檔(myApp1)!'
}
},
components: { myApp2 }
}
</script>
  • 在第 10 行中,將 myApp 組件檔import進來
  • 接著在第 18 行,將 myApp 組件 assign 到 component 屬性中
  • 最後,在第 5 行,就可以在<template>中使用了

組件命名及使用

不論 vue 組件的命名方式為何,在 html 中使用時,一律為 kebab-case (不分大小寫,一律轉為「小寫並加上 dash 線」)

假設 vue 組件為MyApp,在 HTML 呼叫使用時,需轉換為<my-app></my-app>

1
2
3
4
5
// 三種命名方式

my-first-component //kebab-case
myFirstComponent //camelCase
MyFirstComponent //PascalCase

參考資料

JavaScript-嚴格模式"use strict"

在 JavaScript 中有許多鬆散的寫法,雖然因此讓 JS 的彈性受到讚賞,卻也導致 debug 的困難度。 "use strict" 是新加入的標準,目的在於讓「開發具備更穩定的 JS」更容易,當開發者使用不穩定的語法時,即會跳出警告,讓開發者避免這些寫法。

使用 strict mode (嚴格模式) 這個功能,會強迫開發者在開發過程中更為謹慎,用以限制鬆散的 JavaScript 寫法,相比之下也比較不會出錯。

優點與缺點

優點:

  • 撰寫出更為嚴謹的 JS 可以避免不可預知的意外狀況,特別是合作開發時,在 debug 他人撰寫的程式碼會更為簡易。

缺點:

  • 嚴謹模式限制 JS 的彈性
  • 若專案中使用很多第三方撰寫的 plugin 套件,可能因為嚴格模式而無法運作,會需要修改很多程式

建議及解套方法:
嚴格模式除了可以寫在全域,也可以用在 function 中,如此一來就不會全域都嚴謹模式,避免影響既有的程式碼或 plugin 套件。

使用方式

全域

"use strict"直接寫在 JS 全域位置的最上方:

1
2
3
4
5
6
'use strict'; //使用 strict mode (嚴格模式)

function testFun() {
var value = 123;
return value;
}

區域

"use strict"寫在函式的開頭位置:

1
2
3
4
5
6
function testFun() {
'use strict'; //使用 strict mode (嚴格模式)

var value = 123;
return value;
}

如果不是寫在函式的開頭位置,則無效。

1
2
3
4
5
function testFun() {
var value = 123;

('use strict'); //嚴格模式無效
}

使用嚴格模式的受限

  • 變數未宣告直接賦值
  • 物件中有重複的屬性名稱
  • 刪除已經宣告的錯誤
  • 不能使用 ‘with’ 語法
  • argument, eval 不能作為變數名稱
  • 保留字不得作為變數名稱,例:implements, interface, let, package, private, protected, static, yield

瀏覽器支援

  • Chrome13 之後都有支援
  • IE10,11 有支援
  • FF4 有支援
  • Safari5.1 之後有支援

CSS-Reset

在每一個瀏覽器,都會有瀏覽器自帶的 CSS 樣式檔案,用以呈現各個瀏覽器特有的預設外觀。雖然有預設樣式的好處是,當開發者沒有特別定義樣式時,至少會有基礎的排版、樣式效果;可是壞處是,當開發者自定義樣式時,經常會因此而出現不可預期的狀況。
所以通常在做網頁切版時,都會用 CSS Reset,將瀏覽器預設的樣式通通歸零,包含行距離、字距、margin、padding⋯ 等。

Reset 版本

最普遍常用的 CSS-Reset 為 Eric Meyer 版本 ,下方也以此版本說明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* 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 之後,都是統一無樣式的版本。而開發過程中,也不會因為 各個瀏覽器各自的預設值,而影響到開發者重新自定義的樣式,進而發生不可預知的狀況。

如何 Reset?

Eric Meyer 版本 寫好的程式碼內容,全部複製一份到個人專案中的一個 CSS 檔案中,可以命名為 reset.css 接著在專案中的每個 html 都引入 reset.css ,這樣就可以確定每個頁面在一開始的樣式都被歸零。

重點提醒!

一定要在一開始就引入 CSS Reset,避免 CSS Reset 反而蓋掉開發者自行定義的樣式。

資料來源

金魚都能懂網頁設計入門 : CSS Reset

小事之 CSS Reset 與 CSS normalize

JavaScript-BOM與DOM

檔案物件模型(Document Object Model, DOM)是瀏覽器的執行規範,提供了結構化的標準方法,將網頁的程式碼及其他程式語言結合起來。開發人員即可以通過文件中物件的屬性、方法和事件來掌控、操縱和建立動態的網頁元素。

DOM(Document Object Model)

每個 DOM 的基礎建置在一個(D)文件上(也就是以 document 作為根基),而由 document 中的每個(O)物件作為出一個個節點,最後由這一個個節點,組成為一個 DOM Tree。

  • D:(文件)瀏覽器當前載入的 document
  • O:(物件)將 document 中的所有元素視為物件,可以調用其屬性、方法
  • M:(模型)所有的物件在該結構中都是節點,這些眾多節點組成一個樹狀結構

將 HTML 分解為 DOM:

DOM 節點有:

  1. 元素節點:上圖中<html><body><p>標籤皆是元素節點。
  2. 文本節點:向用户展示的内容,如<li>...</li>中的 JavaScript、DOM、CSS 等文本。
  3. 屬性節點:標籤中的屬性,如<a>標籤中的連結屬性href="http://www.baidu.com"

查找節點的方法

方法 說明
getElementById() 獲取特定 ID 元素的節點
getElementsByTagName() 獲取相同元素的節點列表
getElementsByName 獲取相同名稱的節點列表
getAttribute() 獲得特定元素節點屬性的值
setAttribute() 設置特定元素節點屬性的值
removeAttribute() 移除特定元素節點的属性

節點的屬性

屬性 說明
nodeName 返回一个字串,其内容是節點的名字
nodeType 返回一个整數,這個數值代表给定節點的類型
nodeValue 返回給定節點的當前值
tagName 獲取元素節點的標籤名
innerHTML 獲取元素節點的内容

DOM 操作

用 DOM API 可以輕鬆地删除、添加和替換節點。
| DOM 操作 |说明 |
| ————- |:————-:|
|creatElement(element) |創建一个新的元素節點|
|creatTextNode() |創建一个包含給定文本的新文本節點|
|appendChild() |指定節點的最後一個節點列表後添加一个新的子節點|
|insertBefore() |將一個給定節點节点插入到一个給定元素節點的給定子節點的前面|
|removeChild() |从一个给定元素中删除子节点|
|replaceChild() |把一个给定父元素里的一个子节点替换为另外一个节点|

BOM

瀏覽器物件模型(Browser Object Model,BOM)是瀏覽器提供的附加對象,用於處理 document 以外的所有内容,主要處理瀏覽器窗口和框架,不过通常瀏覽器特定的 JavaScript 也會被看作 BOM 的一部分。

區別:BOM 描述了與瀏覽器進行交互的方法和接口,DOM 描述了處理網頁内容的方法和接口。

  • window 物件包含属性:document、navigator、frames、history、location、screen

  • document 根節點包含子節點:forms、location、anchors、images、links

參考資料

JavaScript 学习总结(三)BOM 和 DOM 详解
知乎-BOM 和 DOM

WebSocket-簡易APP應用

完整 Demo:參考github 程式碼

安裝 nodemon

持續監視程式碼,一旦我們做了修改並儲存,nodemon 就會自動重新啟動 Node.js 程式,這樣刷新瀏覽器就能看到變更。

1
npm install -g nodemon


安裝完畢之後,輸入指令,就會啟用「自動偵聽並重啟」的功能:

1
nodemon index.js

nodemon 啟動之後,以可以看到提示字元,如果想要人工手動重啟,也可以輸入rs指令。

前端頁面功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//建立一個WebSocket實體,建立完成自動連線到server
var sock = new WebSocket('ws://localhost:5001');

//定義完成連線時的回呼函式
sock.onopen = function(event) {
setTimeout(function() {
document.getElementById('status').setAttribute('class', 'status_connected');
document.getElementById('status').innerHTML = '<b>Connected</b>';
}, 3000);

//設定按鈕監控click事件
document.querySelector('button').onclick = function() {
var date = new Date();
var data = {
user: document.getElementById('user').value,
msg: document.getElementById('inputBox').value,
time: date.getTime(),
};

//傳送訊之前要先將物件轉為JSON格式
sock.send(JSON.stringify(data));
document.getElementById('inputBox').value = '';
};

//定義收到訊息時的回呼函式
sock.onmessage = function(event) {
console.log(event.data);

//將JSON格式先轉為物件
var newEvent = JSON.parse(event.data);

//判斷資料並分類做處理
switch (newEvent.type) {
case 'request':
var time = new Date(newEvent.time).toLocaleTimeString();
var logArea = document.getElementById('chatArea');
var newArea = document.createElement('div');
newArea.innerHTML =
"<div id='logArea'><div class='card'>" +
'<p>' +
newEvent.user +
':<b>' +
newEvent.msg +
'</b><small>' +
time +
'</small></p></br></div></div>';
logArea.appendChild(newArea);
break;

case 'response':
var time = new Date(newEvent.time).toLocaleTimeString();
var resArea = document.getElementById('chatArea');
var newArea = document.createElement('div');
newArea.innerHTML =
"<div id='resArea'><div class='card'>" +
'<p>' +
newEvent.user +
':<b>' +
newEvent.msg +
'</b><small>' +
time +
'</small></p></br></div></div>';
resArea.appendChild(newArea);
break;

case 'notify':
var time = new Date(newEvent.time).toLocaleTimeString();
var resArea = document.getElementById('chatArea');
var newArea = document.createElement('div');
newArea.innerHTML =
"<div id='resArea'><div class='card'>" +
'<p>' +
newEvent.user +
':<b>' +
newEvent.msg +
'</b><small>' +
time +
'</small></p></br></div></div>';
resArea.appendChild(newArea);
break;
default:
break;
}
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div>
<div id='status' class="status_disconnected"><b style="color: white">Disconected</b></div>
<div id='content'>
<span>User:</span>
<select id='user'>
<option>Amy</option>
<option>Bora</option>
<option>Cherry</option>
</select>
</div>
<br/>
<div>
<textarea id='inputBox' type='text' placeholder="Input message here"></textarea>
</div>
<button>Send</button>
<div id='chatArea'>
<!-- 在這裡面處理appendChild() -->

</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#chatArea {
border-top: 3px solid white;
/* margin-top:10px; */
background-color: #f1f1f1;
position: relative;
width: 100%;
margin: 0 auto;
}

#logArea {
position: relative;
width: 50%;
left: 0;
border-right: 3px solid white;
}

#resArea {
position: relative;
width: 50%;
left: 50%;
border-left: 3px solid white;
}

.card {
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}

後端處理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//建立server實體
var webSocketServer = require('ws').Server;
var wss = new webSocketServer({ port: 5001 });

//設計一個計數器,計算當前連線數量
var count = 0;

//定義每當一個連線建立的執行動作
wss.on('connection', function(ws) {
var user = '';
count++;
console.log('Curent clients:' + count);
console.log('[Server] connected!');

//當某個建立連線的ws發送出message,會執行的動作
ws.on('message', function(data) {
//將JSON格式先轉為物件
var newData = JSON.parse(data);
console.log(`[Server] received ${data}`);
user = newData.user;

//將訊息同步傳給「所有」的用戶端瀏覽器,達成訊息同步
wss.clients.forEach((client) => {
//判斷用戶端是否為發出訊息的自己,而做不同的處理
if (client == ws) {
newData.type = 'request';
} else {
newData.type = 'response';
}
client.send(JSON.stringify(newData), (err) => {
if (err) {
console.log(`[Server] error: ${err}`);
}
});
});
});

//當某個用戶端斷線,所執行的動作
ws.on('close', function() {
count--;
console.log('[Server] Lost a client.');
console.log('Online clients:' + count);
var date = new Date();
newData = {
type: 'notify',
user: user,
msg: '[Left chat room]',
time: date.getTime(),
};

//將斷線訊息同步給所有其他用戶
wss.clients.forEach((client) => {
if (client != ws) {
client.send(JSON.stringify(newData), (err) => {
if (err) {
console.log(`[Server] error: ${err}`);
}
});
}
});
});
});
© 2020 Leah's Blog All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero