正在顯示「 2019 年 12 月 」的所有結果
12月 24, 2019 Javascript
這裏講的 Promise 不是講的 "邦民" 的。而是講 Javascript 上的 Promise。

### 起源

自從 AJAX 技術興起後,異步的 Javascript 有了很多元化的發展,當然萬變不離其中也是使用 Callback 的方法來達成 ( 也即是 Observe Pattern 的簡化版 )。由 Async Waterfall 到現時 Promise 方法處理異步 Callback,當中經過了很多時間變化。

### 語法

現時 Promise 是一個 Javascript 的原生 Object (ES2015 後),用來處理 Javascript 異步的回傳。

```js
// create new promise
var promise1 = new Promise((resolve, reject) => {
	
	// your task here
	setTimeout(() => {
		
		// random condition
		if( Math.round(Math.random() * 10) > 5 ) {

			// success and callback data
			resolve('foo');
		} else {

			// failure and callback error
			reject(new Error('bar'));
		}
	}, 100);
});

// handle promise success
const success = data => console.log(data);

// handle promise failure
const failure = error => console.error(error);

// handle promise result
promise1.then(success, failure);
```

而當這個 Promise Object 建立的同時,自定義的功能已經在背後異步運行的了。運行完的結果可以透過使用 `resolve` 回傳出去。

我們則可以利用 `then` 功能把回傳成功的值接住。第一個參數是接著一個 function,用來處理成功時的結果,第二個參數也是接著一個 function,不過就用來接著失敗時的 error。

另外 Promise 不只是只有這些功能的,下面這個 Link 會有更多的功能介紹 !

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise
12月 24, 2019 CSS
我們可以通過 CSS `line-height` 屬性來設定文字行高。

### 語法

以用以下方法,可以把設定行高的屬性加入到 `div` 內。

```css
div.a {
	line-height: normal;
}

div.b {
	line-height: 1.6;
}

div.c {
	line-height: 80%;
}

div.d {
	line-height: 22px;
}
```

### 值的解說

以下為一部份值的解說。

|值|說明|
|---|
|inherit|承計母系容器的文字行高設定|
|normal|預設的文字行高|
|任何數值|這個數字會把文字的行高乘上這個數值,可以是小數|
|高度|一個實際的高度,是數字加上刻度,例如 `px`|
|百份比|像任何數值一樣,不過以百份比來設定|

12月 23, 2019 19 Things
經過幾個月來的更新,現在 19Site 都算是走向正正式式一個 Blog 的型態。排版上已經和一般的 Blog 大體上差不多 ( 中間經過了大量修改 ) 。

由最初時的 Blogger 然後到使用 PHP + Laravel 到現時的 NodeJS + ReactJS。可能因為本對 Javascript 也是比較熟,所以用起上來還是由頭到尾全部使用 Javascript 比較易處理。Blogger 不是不好,不過就是太過單調 (對於筆者來說)。而 PHP + Laravel 當然是十分強勁,大中小型 Project 都是十分合適的。不過如果要說到前後介面的整合性,筆者暫時感覺只有 NodeJS + ReactJS 是最易處理的。

### 共用 Library

由於大家都是 Javascript,都可以說是同聲同氣,一份 NPM 齊齊兩份用 ! 就以 Axios 為例,Server 又可以用,Client 又可以用,一個 Library 走編主客。

### 處理客戶端的 AJAX

PHP + Laravel 的強勁地方就是 Blade Template Engine 和他的 ORM 整合,絕對可以講得上是神速開發 !! 資料交互全由 ORM 負責就可以了,加上 `collect()` 功能,是處理資料的最佳 Library。不過也有其弱點,就是每次都要 Full Page Reload...

如果使用 AJAX,就要自行加載 Javascript 檔案處理。相比起 ReactJS,它原生就是以 Javascript 來管理流程,自然可以更加容易整合。

### 總結

總的來說,ReactJS 真是一套很好的工具用來處理高度 reactive 的網站。例如 Chat application 等等,就是用來處理普通如 Blog 的網站也是一樣出色的。
12月 23, 2019 19 Things
在晚上看電話有時會覺得很光亮,看得眼睛得不舒服。就算是把光暗度調較到最暗的情況下,也是太光亮了。但是 iOS 又不能好像 Android 可以裝第三方的程式 Overlay 一層在別的 Application 上 ( 也就是去藍光濾鏡 APP ),那應該要怎樣好呢?

### 比最暗還要暗

原來在 iOS 上也真的有這樣的功能,不過就要通過使用 Accessibility 來達成了 !!

筆者現時用的是 iOS 13.1.12,因為 iOS 經常更新的關係,所以常常會遇到網上圖片和現手上電話的設定位置有所不同的情況。

![](https://cdn.19site.net/files/24/83/2483475c-8709-4b57-89c2-79b003d4fc3e.png '先去 [Settings] > [Accessibility]')

![](https://cdn.19site.net/files/d6/a6/d6a6a202-c4ba-4342-b4b9-a934441ecc84.png '再找出 [Display & Text Size]')

![](https://cdn.19site.net/files/ba/a0/baa0458d-6331-433c-aea6-bbf35013766b.png '在最下可以找到 [Reduce White Point],預設是關上的')

![](https://cdn.19site.net/files/28/ae/28ae2917-c5e8-40c9-8c6f-1eefd3a00f0c.png '啟用 [Reduce White Point] 後,可以調整強度')

這個功能就是用來把減弱白色的輸出,使顯示出來的光亮下降,效果是不錯的,夜貓一族可以試試了。


12月 23, 2019 19 Things
今天在 Google Search Console 上左看看右看看時,發現了有些奇怪的事情,因為那裏可以看到人們用什麼的關鍵字去搜索網站。當筆者把關鍵字複製然後放到 Google 上 Search 時,發現了有第二個 DNS 指向了 19Site。運作也十分良好 !

![](https://cdn.19site.net/files/aa/76/aa768e69-c051-4458-ba6f-3b43198f72ad.png 'Search 19site 時,比 19Site 的排名還要高呢 !')

以下是指向 19Site 的 URL (已經修正好加入了 redirect 功能) :  
http://www.exvatravel.com

### 可能原因

可能是因為筆者用 VPS 的關係,需我用的這個 IP 是有可能是先前這家公司派給其他的戶口使用,所以他的網站把他的 DNS 指向到筆者主機的 IP 上 (他還沒有更新指向他的網站)。就有了一個像鏡像的網站了 !

> 經過調查後發現原來的 IP 是屬於一定韓國的公司 (現在公司的 BLOG 網站 : https://blog.naver.com/payanpayasia ),不知道是不是因為把網站關掉了所以 IP 流到筆者這裏來。看他們的 BLOG 也有些歷史的呢 ! 怪不得常常來了韓國來的 Traffic 。

### 解決方法

筆者這個 Site 是使用 nodejs + KOA 來 Host 的 ( 暴露設定了 !! ),所以要在中間加一重 middleware 來處理 hostname 的問題,把非法的 hostname 重新指向自行設定的 hostname 便可以了 !!

```js
// middleware
App.use(async (ctx, next) => {
	if( ctx.request.header.host === 'yourdomain.com' ) {
		return next();
	} else {
		return ctx.redirect('https://yourdomain.com');
	}
});
```

如果你用的是 nginx 或者是 apache2 的話,就更加方便了。因為只要在 site enable config 內設定好 server name 就可以了。
12月 20, 2019 MySQL
我們在使用 MySQL 時也常常會使用 Group By 功能把相同的資料整合出來,然後 Count 出結果的數目。

##### Table : people

|id|name|sex|
|---|
|1|Peter|M|
|2|Mary|F|
|3|Tom|M|
|4|Betty|F|
|5|Flex|M|
|6|Alex|M|

### 使用 count()

Count 是其中一個在 SQL 裏常用的功能,以下的 statement 可以抽出整個資料表的記錄數量。

```sql
select 
	count(*) as count 
from 
	people
```

|count|
|---|
|6|

如能配合 Group By 使用的話,就可以抽出每一個組別的記錄數量 :

```sql
select 
	count(id) as count, 
	sex 
from 
	people 
group by 
	sex
```

|count|sex|
|---|
|4|M|
|2|F|

### 使用 group_concat()

如果使用 group_concat 功能的話,就可以把欄位的值 group 起來在一格欄位轉輸出了。下面是例子

```sql
select 
	count(id) as count, 
	group_concat(id) as ids, 
	sex 
from 
	people 
group by 
	sex
```

|count|ids|sex|
|---|
|4|1,3,5,6|M|
|2|2,4|F|

### 自訂分隔符號

逗號 (Comma) 是預設的分隔符號,如果想自訂分隔符號可以使用 `separator` 語法。

```sql
select 
	count(id) as count, 
	group_concat(id separator '+') as ids, 
	sex 
from 
	people 
group by 
	sex
```

|count|ids|sex|
|---|
|4|1+3+5+6|M|
|2|2+4|F|

### 不重覆記錄

如果想 group 起來的記錄不要重覆,可以使用 `distinct` 語法。

```sql
select 
	count(id) as count, 
	group_concat(distinct id separator '+') as ids, 
	sex 
from 
	people 
group by 
	sex
```

這樣就可以保證抽出來的記錄不會重覆了。
12月 18, 2019 Programming
### Config File

設計 Config File 在寫程式時是一個必定會出現的事情,在 Javascript 的世界裏我們可以使用 JSON 檔案來儲存系統的 Config。而在使用 JSON 時又一定會使用到 Tree 的方法來儲存資料。看看以下的例子 `Config.json` : 

```json
{
	"app":{
		"port":80,
		"db":{
			"host":"db.abc.com",
			"port":3306,
			"user":"myuser",
			"password":"mypassword",
			"database":"myproject"
		}
	}
}
```

大概有 80% 的系統都會有以下的設定了,最基本的 Database 連線設定檔。

當我們要使用參數時,可以直接匯入要 JSON 檔案然後經 Object 存取資料。

```js
import Config from 'Config.json';

// connect to db
const connectDB = () => {
	
	var host = Config.app.db.host || 'default host name';
	var port = Config.app.db.port || 3306;
	var user = Config.app.db.user || 'default user';
	var password = Config.app.db.password || 'default password';
	var database = Config.app.db.database || 'default database';
};
```

以上的方法可以把資料從 Config 中讀取出來,還可以設定他們的預設值。但是有一個問題,就是如果 Config 的路徑內有其中一個環節斷掉,程式就會 throw error。例如 : 

```js
// config
const Config = {
	"port":80,
	"db":{
		"host":"db.abc.com",
		"port":3306,
		"user":"myuser",
		"password":"mypassword",
		"database":"myproject"
	}
}

// connect db
const connectDB = () => {
	
	var host = Config.app.db.host || 'default host name';
	var port = Config.app.db.port || 3306;
	var user = Config.app.db.user || 'default user';
	var password = Config.app.db.password || 'default password';
	var database = Config.app.db.database || 'default database';	
};
```

因為 Config 中沒有 app 這個屬性存在,所以當運行到 assign host 那句時,就會 throw error 了 !!!

### 解決方法

要解決問題,可以加入檢查就可以了 :

```js
// check every node of config path
var host = (Config && Config.app && Config.app.db && Config.app.db.host? Config.app.db.host: undefined);
```

以上方法可以確保萬無一失,不過又好像寫得太長 !!

### Configuration File 平整化

平整化就是把一個立體的 model 變成為一個單純的 array。我們可以通過 Database First Normalization Form 來達成目的。以下為例 :

```json
{
	"app":{
		"port":80,
		"db":{
			"host":"db.abc.com",
			"port":3306,
			"user":"myuser",
			"password":"mypassword",
			"database":"myproject"
		}
	}
}
```

平整化後可以變成為 :

```json
{
	"app":{
		"port":80,
		"db":{
			"host":"db.abc.com",
			"port":3306,
			"user":"myuser",
			"password":"mypassword",
			"database":"myproject"
		}
	},
	"app.port":80,
	"app.db":{
		"host":"db.abc.com",
		"port":3306,
		"user":"myuser",
		"password":"mypassword",
		"database":"myproject"
	},
	"app.db.host":"db.abc.com",
	"app.db.port":3306,
	"app.db.user":"myuser",
	"app.db.password":"mypassword",
	"app.db.database":"myproject"
}
```

處理好後像 Config 的 Route 檔案,一個 KEY 可以對應到一個 VALUE。在使用上便簡單得多了。

```js
import Config from 'Config.json';

// connect to db
const connectDB = () => {
	
	var host = Config['app.db.host'] || 'default host name';
	var port = Config['app.db.port'] || 3306;
	var user = Config['app.db.user'] || 'default user';
	var password = Config['app.db.password'] || 'default password';
	var database = Config['app.db.database'] || 'default database';
};
```

這樣就算其中的一層參數繼掉了,也不會 throw error,而是回傳 `undefined`。

### 如何實作平整化

由於 Config 是一種不轉變的資料,所以我們在 Build 時可以使用 Script 產生 Config 檔案。另一個方法是在 Runtime 是進行。而當然前者的效能會較為好,而後者就有更大的彈性。

```js
import Config from 'config';

/**
 * flat config
 */
const flatConfig = (config, path) => {

	// result
	var result = {};

	// init config
	config = config || Config;

	// init path
	path = path || 'Config.';

	// each config attribute
	for( var i in config ) {

		result[path + i] = config[i];

		if( typeof config[i] === 'object' ) {

			result = { 

				...result, 

				...flatConfig(config[i], path + i + '.')
			};
		}
	}

	// return
	return result;
};

/**
 * read config attribute
 */
const readConfig = path => {

	// get config
	var config = flatConfig();

	// return
	return config[path];
};

// get host
var host = readConfig('Config.app.db.host');
```

上面使用了 Recursion 的方法來處理整個 tree model。大家可以使用自己的方法來實作也可以的喔 !
12月 17, 2019 Programming
有讀過初中數學都會接觸過二進位數,如果知道什麼是二進位數的話可以先找找二進位數介紹的文章看看。

### 二進位數的特性

其實二進位和十進位也是數字,但是在某些情況下如果使用二進位數可以更易處理一些電腦上的問題。

如果有玩過 Linux 都會對 755, 777, 741 等數字很敏感 ! 沒錯,這是檔案的權限。其實在背後也是使用了二進位數的特性。

R(read) W(write) X(execute) 分別使用了三支 bit 來記錄獨立的權限。

|R(read)|W(write)|X(execute)|
|---|
|1|1|1|

而三個 bit 組成的二進位數就是它的權限代表數。其實就只有以下這 8 個組合 :

|R(read)|W(write)|X(execute)|decimal number|
|---|
|0|0|0|0|
|0|0|1|1|
|0|1|0|2|
|0|1|1|3|
|1|0|0|4|
|1|0|1|5|
|1|1|0|6|
|1|1|1|7|

0 代表完全沒有權限,7就代表最大的權限。從表格中你可以看到真的可以用 0-7 每一個數字代表一種權限的組合,而且是一定不會重覆的。以這樣的方法來管理權限系統,是可以以最少的 data size 來儲存權限資料。

### 實際使用

上面我們知道了如果把權限用數字的特情來儲存起來,但是在真正應用時我們應如何使用邏輯來判定這個數字是否有權限?

### And 特性

通過對一個二進位數使用 and 時,我們可以得最它們之間的共同值。以下作三個例子 :

|||||
|---|
||0|0|1|
|&|0|0|1|
||0|0|1|

> 001 & 001 = 001

|||||
|---|
||0|1|1|
|&|0|1|0|
||0|1|0|

> 011 & 010 = 010

|||||
|---|
||0|1|1|
|&|1|0|0|
||1|0|0|

> 011 & 100 = 000

And 特性會在兩者數值之間找出共有的保留,而非共有的就會捨去。

### 實作

使用這個特性,我們可以把系統的功能需要權限設為一個常數。

```js
// read
const READ = 0b100;

// write
const WRITE = 0b010;

// execute
const EXECUTE = 0b001;
```

然後每一個 User 有自己的權限儲存在自己的 `rights` 資料內。

```js
// new user
var user = new User();

// set user rights
user.rights = 0b100;
```

然後在 user 使用功能時,對 user 的權限進行檢查:

```js
// read function
function read(user) {

	// check permission
	if( (READ & user.rights) !== READ) {
		throw new Error('permission denied');
	}

	// do something
}
```

我們只需要使用 & 特性,就可以檢查 user 有沒有足夠的權限訪問了 !!
12月 17, 2019 NodeJS
在 WEB 的層面上使用壓縮檔案功能,無非也是要為下載檔案做好準備。一次過下載多個檔案在現今的瀏覽概念還是使用打包的方法比較好,或是如果能下載一個 FOLDER 也是一個不錯的方向 (可惜沒有)。

### 壓縮檔案

要達成把檔案壓縮,最方便也是問 NPM 大神找找有沒有人寫好的 !

https://www.npmjs.com/package/adm-zip

![](https://cdn.19site.net/files/a6/ff/a6ff26c0-7828-4116-ab06-8efaf6dedfdb.png)

過去一星期 160 萬次數下載,非常可靠的樣子。

這個 Library 可以讀取 ZIP 檔案的內容,也可以把檔案加入到 ZIP 檔案內,是一個完整雙向的 Library,而且方便易用。

當中有一個能力是非常強勢的,就是它可以把 Zip 檔案直接輸出為 Buffer。意味著可以一邊打包一邊輸出? 還需要試一試...

```js
// creating archives
var zip = new AdmZip();
    
// add local file
zip.addLocalFile("/home/me/some_picture.png");

// get everything as a buffer
var willSendthis = zip.toBuffer();

// or write everything to disk
zip.writeZip(/*target file name*/"/home/me/files.zip");
```

如果是使用 KOA 的話,就可以直接用以下語法輸出到 `response` :

```js
// zip buffer to response
ctx.body = zip.toBuffer();
```

非常方便 !!
有時候你可以需要透過使用 Ajax 的方法來下載檔案,例如使用 `bearer token` 時,所以現代的瀏覽器可以使用以下的方法達成。

### 使用 Axios

在這年代,在使用 Ajax 的大概都是在使用 Axios,這次筆者也是使用 Axios 來達成。 

```js
// use axios
Axios({
	
	// target url
	url: 'http://yourdomain/getfile'
	
	// get method
	method: 'GET',
	
	// remote response type, use 'blob' or 'arraybuffer'
	responseType: 'arraybuffer'

}).then(r => {
	
	// create object url
	const url = window.URL.createObjectURL(new Blob([r.data]));
	
	// create link to object
	const link = document.createElement('a');
	
	// set link to object
	link.href = url;
	
	// set download file name
	link.setAttribute('download', 'download.zip');
	
	// launch download
	link.click();
	
	// delete object when download finished
	window.URL.revokeObjectURL(url);
});
```

在上面 `responseType` 可以使用 `blob` 來處理圖片資料,筆者自己親身測試過用 `arraybuffer` 來處理 `zip` 檔案是可以的,但是用 `blob` 來處理 `zip` 檔案就不行了。 

以下這條 Thread 有討論過這個問題,筆者也是在當中整合出來 :

https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
12月 16, 2019 Git Github
每次開新 Project 第一件事必先是要去 GitHub 開一個 Repository 先,然後先會在 Local 開一個資料夾再用 git init 開始,但是因為常常使用 GUI 所以忘記了 Command... 這次來個整合記錄一下。

### 開始工作

正如上面所說,使用 `git init` 當然是不少得 !

```sh
# create new project folder
$ mkdir my_project

# change directory to new project folder
$ cd my_project

# git init
$ git init
```

### 加入 Remote 到你的本機 Git 項目內

接下來就要把你 GitHub 的 URL 加入到 git 內,我們可以使用 `git remote add` 來達成。

`git remote add` 會接受兩個參數。

-	名稱,例如 `origin`

-	URL,例如 `https://github.com/user/repo.git`

```sh
# add remote 
$ git remote add origin https://github.com/user/repo.git

# verify remote
$ git remote -v
> origin  https://github.com/user/repo.git (fetch)
> origin  https://github.com/user/repo.git (push)
```

### 從 GitHub 抓取資料

之後應該不難了,因為可以使用 GUI 介面去處理。或是可以使用 `git pull` 來抓取 remote 上的資料。

`git pull` 會接受兩個參數。

-	名稱,例如 `origin`

-	分支的名稱,例如 `master`

```sh
# fetch project
$ git pull origin master
```

然後改改 README.md,再 Create new Branch 為 `develop`,然後 `commit` 再 `push`。這樣就可以開始依照 Git Flow 開發你的項目了 !
12月 12, 2019 Database
### 什麼是 Database Normalization

Database Normalization 是一套方法,可以用來幫助你設定好的你的資料庫。它是由幾個規則所組成。

### First normal form

以下是 First normal form (1NF) 的特點 : 

1.	每一個欄位只能有一個單一值。

2.	沒有任何兩筆以上的資料是完全重覆。

3.	資料表中有主鍵, 而其他所有的欄位都相依於「主鍵」。

First normal form (1NF) 是第一個規則,這個規則是規定每一個資料的格子 (Cell) 不可以有多於一個記值存在。例如以下的表格 :

|StudentNumber|StudentName|SubjectName|Teacher|TeacherNumber|Score|
|---|---|---|---|---|---|
|001|Peter|Chinese, English|Mr. Chinese, Mr. English|t01, t02|80, 91|
|002|Mary|Chinese, English|Mr. Chinese, Mr. English|t01, t02|90, 77|
|003|Tom|Chinese, English|Mr. Chinese, Mr. English|t01, t02|100, 55|

在 Score、SubjectName 及 Teacher 的欄位中便有超過一個的值存在著,這樣便和 1NF 的規則違反了。我要需要對這樣的資料進行處理。

最簡單的方法是把重覆的值分別儲存到多個欄位當中。

|StudentNumber|StudentName|SubjectName1|SubjectName2|Teacher1|Teacher2|TeacherNumber1|TeacherNumber2|Score1|Score2|
|---|---|---|---|---|---|---|---|---|---|
|001|Peter|Chinese|English|Mr. Chinese|Mr. English|t01|t02|80|91|
|002|Mary|Chinese|English|Mr. Chinese|Mr. English|t01|t02|90|77|
|003|Tom|Chinese|English|Mr. Chinese|Mr. English|t01|t02|100|55|

這樣的設計就乎合了 1NF 的規則了 ! 可是這不是一個好的設計 ! 因為如果 Subject 的數量是一個不確定的數值,那麽不是要建立一堆空白的欄位來存儲資料?

要解決這個問題,我們需要把資料直向堆疊。資料表變下為以下的模樣 :

|StudentNumber|StudentName|SubjectName|Teacher|TeacherNumber|Score|
|---|---|---|---|---|---|
|001|Peter|Chinese|Mr. Chinese|t01|80|
|001|Peter|English|Mr. English|t02|91|
|002|Mary|Chinese|Mr. Chinese|t01|90|
|002|Mary|English|Mr. English|t02|77|
|003|Tom|Chinese|Mr. Chinese|t01|100|
|003|Tom|English|Mr. English|t02|55|

這樣做也是乎合了 1NF 的規則了 !

### Second normal form

Second normal form (2NF) 的規則是要每一個資料表格內的每個資料值,都要完全相依到它的主鍵 (Primary Key)。看解釋好像很難理解,看看以下實例 : 

經過 1NF 的處理後我們在得到了以下的資料格式。

|StudentNumber|StudentName|SubjectName|Teacher|Score|
|---|---|---|---|---|
|001|Peter|Chinese|Mr. Chinese|80|
|001|Peter|English|Mr. English|91|
|002|Mary|Chinese|Mr. Chinese|90|
|002|Mary|English|Mr. English|77|
|003|Tom|Chinese|Mr. Chinese|100|
|003|Tom|English|Mr. English|55|

上面的資料是用來記錄考績 (Score) 的,而考績是需要主鍵 (Student + Subject) 才能完整關聯得到。相對於 Score 欄位,我們看得到 StudentName 是相依著 StudentNumber。而 Teacher 是相依著 SubjectName 的。即是只有相依到部份的欄位上,需不是完全相依到主鍵 (Student + Subject) 上。

為了要把資料變成為符合 2NF,我們需要把資料分拆成不同的資料表 :

##### Student 資料表

|StudentNumber|StudentName|
|---|---|
|001|Peter|
|002|Mary|
|003|Tom|

##### Subject 資料表

|SubjectName|Teacher|TeacherNumber|
|---|---|---|
|Chinese|Mr. Chinese|t01|
|English|Mr. English|t02|

##### Score 資料表

|StudentNumber|SubjectName|Score|
|---|---|---|
|001|Chinese|80|
|001|English|91|
|002|Chinese|90|
|002|English|77|
|003|Chinese|100|
|003|English|55|

經過 2NF 後,產生三個資料表,分別為 Student 資料表、Subject 資料表
及 Score 資料表。

### Third normal form

Third normal form (3NF) 是要把所有的遞移相依或是間接相依的資料分割出來。什麽是遞移相依? 我們看看以下資料 : 

##### Subject 資料表

|SubjectName|Teacher|TeacherNumber|
|---|---|---|
|Chinese|Mr. Chinese|t01|
|English|Mr. English|t02|

TeacherNumber 是相依到 SubjectName,而 Teacher 可以相依到 TeacherNumber,這樣間 Teacher 也間接相依到 SubjectName。在這個情況就要進行 3NF 把資料從間接相依分割出來。

可以分割出下列資料表格 :

##### Subject 資料表

|SubjectName|TeacherNumber|
|---|---|
|Chinese|t01|
|English|t02|

##### Teacher 資料表

|TeacherNumber|Teacher|
|---|---|
|t01|Mr. Chinese|
|t02|Mr. English|

### 總結

經過使用 Normalization 方法,我們可以把最初的資料 :

|StudentNumber|StudentName|SubjectName|Teacher|TeacherNumber|Score|
|---|---|---|---|---|---|
|001|Peter|Chinese, English|Mr. Chinese, Mr. English|t01, t02|80, 91|
|002|Mary|Chinese, English|Mr. Chinese, Mr. English|t01, t02|90, 77|
|003|Tom|Chinese, English|Mr. Chinese, Mr. English|t01, t02|100, 55|

設計為以下的資料表 : 

##### Student 資料表

|StudentNumber|StudentName|
|---|---|
|001|Peter|
|002|Mary|
|003|Tom|

##### Subject 資料表

|SubjectName|TeacherNumber|
|---|---|
|Chinese|t01|
|English|t02|

##### Teacher 資料表

|TeacherNumber|Teacher|
|---|---|
|t01|Mr. Chinese|
|t02|Mr. English|

##### Score 資料表

|StudentNumber|SubjectName|Score|
|---|---|---|
|001|Chinese|80|
|001|English|91|
|002|Chinese|90|
|002|English|77|
|003|Chinese|100|
|003|English|55|

這裡有個 PowerPoint 是台灣一所大學用來講解 Normalization 的 : 

http://cc.cust.edu.tw/~ccchen/doc/db_04.pdf
12月 11, 2019 NodeJS
### What is MD5?

The MD5 message-digest algorithm is a widely used hash function producing a 128-bit hash value.

From [Wikipedia MD5](https://en.wikipedia.org/wiki/MD5)

You can simply think that there is a function called md5(), by providing a string to that function and it will return a MD5 string to you. A MD5 string is fixed length. That means whatever how much data you input to the md5() function. The string length of output will always the same.

### How to generate MD5 string?

Different programming language has their own methods / library to generate a MD5 string. 

In NodeJS, a built in module called `crypto` can help you do the staff :

```js
// import module
const crypto = require('crypto');

// input data
var data = 'hello world';

// and md5 string of 'hello world'
var md5String = crypto.createHash('md5').update(data).digest('hex');
```

There are many other digest methods that supported by `crypto` module. You can navigate to Tools page to try out the online MD5 converter.
12月 11, 2019 19 Things
Comments 功能是好基本的功能,因為先前使用的是由 Facebook 提供的 Comments Plugin。所以筆者沒有需要去自己處理 Comments。

不過由於這個 BLOG 也是需要自己寫功能才有意義,所以還是再弄了一個 Comments Block。

### URL 分類

也是傳統的分類方法,使用頁面的 URL 作為不同 Comment 的主鍵,這樣簡單地方不同的頁面插入同一個 Component 也可以自動分成不同頁的 Comments Block。 

### 使用 Google ReCaptcha 來阻擋 Robot 

在網站上填 Form 到 Database 不得不提防的是自動化的動作,一不小心你的 Database 就可以會填滿垃圾記錄,甚至可能會使網站停止運作。

Google ReCaptcha 在這方面可以為開發人員省下了不少的苦工。

Google ReCaptcha 相關資料 : https://www.google.com/recaptcha/intro/v3.html
12月 11, 2019 19 Things
Comments 功能是好基本的功能,因為先前使用的是由 Facebook 提供的 Comments Plugin。所以筆者沒有需要去自己處理 Comments。

不過由於這個 BLOG 也是需要自己寫功能才有意義,所以還是再弄了一個 Comments Block。

### URL 分類

也是傳統的分類方法,使用頁面的 URL 作為不同 Comment 的主鍵,這樣簡單地方不同的頁面插入同一個 Component 也可以自動分成不同頁的 Comments Block。 

### 使用 Google ReCaptcha 來阻擋 Robot 

在網站上填 Form 到 Database 不得不提防的是自動化的動作,一不小心你的 Database 就可以會填滿垃圾記錄,甚至可能會使網站停止運作。

Google ReCaptcha 在這方面可以為開發人員省下了不少的苦工。

Google ReCaptcha 相關資料 : https://www.google.com/recaptcha/intro/v3.html
12月 09, 2019 React
### React 中的 Reference

Reference 是在 React 中很常會使用到的一種方法,用處是可以把引入的 HTML DOM 物件回傳出來。使用時我們會用以下的語法 :

```js

class DemoComponent extends React.Component {

	constrcutor(props) {
		// create reference object
		this.divRef = React.createRef();

		// init state
		this.state = {];
	}

	componentDidMount() {
		// print <div> in console
		console.log(this.divRef.current);
	}

	render() {
		return (<div ref={ this.divRef }>hello world</div>);
	}
}
```

當 Render 事件完成後,DOM 的物件便可以通過我們建立的 Reference 取得。然後你可以直接對物件進行 DOM 的操作。

### 只有 DOM 物件才能加入 ref

要注意的是,只有 DOM 物件才可以進行 `ref` 的操作,對於非 DOM 物件加入 `ref` 會有機會產生錯誤。

```js
// parent object render function
render() {
	return (<DemoComponent ref={this.componentRef} />);
}
```

這樣是會炒車的 !

### 提取自訂物件的 DOM ref

如果想要在自訂的 React Component 取得內裏的 DOM Reference,我們可以使用以下的 Pattern 去解決 (唔知叫什麼名稱)。

```js
// add code to component did update
componentDidUpdate() {
	// assign props
	var props = this.props;

	// check has onRef event listener
	typeof props.onRef === 'function' && props.onRef(this.divRef);
}
```

然後在 parent component 使用 component 時加入 `onRef` 事件 :

```js
// on child component return ref
onDemoComponentRef(ref) {
	this.divRef = ref;
}

// parent object render function
render() {
	return (<DemoComponent onRef={ ref => this.onDemoComponentRef(ref) } />);
}
```

這樣便可以有系統地取得自訂 Component 的 Reference。
12月 06, 2019 19 Things
又到了 OU 的交功課週期,這份功課已經 extends 左時間了,所以還是專心好好做好佢。因為最近工作實在太忙,加上突然又好多東西要處理,弄得心很累 !

這個學期在 OU 報了 30 分來讀,包括有三個 10 分科。ELECS224、ELECS332 和 COMPS368 三科。感覺上 ELECS224 和 ELECS212 好像差唔多。COMPS368 就好特別,一開始頭兩課竟然是教 Routing Table !!

### DNS Iterative Resolution

這是 ELECS332 的其中一條題目的筆者自己做的答案 (交功課期已完)

在第二課 Application Layer 會教幾種常見的 Application Layer Protocol,包括有 HTTP、SMTP、DNS 等等。對於筆者這種土炮學 Programming 的人來說可是相當新奇 !! 因為自學時是用 Telnet 扮 user agent 去問 Server 拎資料,從而記錄低每個 protocol 的格式,還覺得自己 "解密" 了好利害 ! 但原來在大學裏已列印好在教科書內...... 突然發覺自己很無知。

![](https://cdn.19site.net/files/ee/b1/eeb1c4ed-fd93-4d11-b3fe-89fd80eebcc3.jpg 'DNS Iterative Resolution')
12月 05, 2019 Javascript
Base64 encode 把任何資料以一種方式轉成文字的表達,包括可以把 binary 檔案使用文定去表達出來。

以下會講解在 Javascript 上如果把文字資料轉成 Base64 encode。

### 由 Ascii 變成 Base64

```js
// encode a string
var encodedData = window.btoa('Hello, world');
```

### 由 Base64 變成 Ascii 

```js
// decode the string
var decodedData = window.atob(encodedData);
```

就是這麼簡單了 !!!

但這是解決不到 unicode 的問題啦,因為只是 ascii 的轉換。要令 unicode 也可以成功轉換就要先把 unicode 的文字轉換成為 ascii 字符能夠表連出來。

###  使用 escape 及 encodeURIComponent

我們可以透過使用 escape 及 encodeURIComponent 把 unicode 文字變成為 ascii 表達出來的文字。

```js
// ucs-2 string to base64 encoded ascii
function utoa(str) {
	return window.btoa(unescape(encodeURIComponent(str)));
}

// base64 encoded ascii to ucs-2 string
function atou(str) {
	return decodeURIComponent(escape(window.atob(str)));
}
```

用以上的方法就可以把 unicode 文字轉換為 Base64 encode。

---

以下會提供一個舊舊的語法供參考 :

```js
// encode a string
var encoded = new Buffer('string data').toString('base64');

// decode a string
var decoded = new Buffer(encoded, 'base64').toString('ascii');
```
12月 04, 2019 Javascript
上次有一篇文章是說了 Javascript 懶人語法 (OR),這次要說的是 (OR) 的朋友 (AND)。

Javascript 的懶人語法是比較多在 React 上使用的 (可能因為寫 React 的人都比較懶)。筆者也是因為 React 才寫多了這類懶人語法。

### AND 的懶人方法

相比起 OR,AND 是當前面的條件比對完後,如果前面為非 FALSE 時,就會進行第二個條件比對,一直下去。例如 : 

```js
// statement 1
1 * 0 || 2 + 1;

// statement 2
3 + 1 && 2 * 0 && 2 + 2;
```

上面 Statement 1 時,JS 會以以下流程執行 : 

1.	1 * 0 = 0

2.	0 即是 false

3.	OR 會繼續執行下一個 condition,直至全部完成或是遇上非 false

4.	2 + 1 = 3

5.	3 即是非 false

6.	完成執行

上面 Statement 2 時,JS 會以以下流程執行 : 

1.	3 + 1 = 4

2.	4 即是非 false

3.	AND 會繼續執行下一個 condition,直至全部完成或是遇上 false

4.	2 * 0 = 0

5.	0 即是 false

6.	完成執行

(會 Skip 了 2 + 2 的 condition)

### 特性

OR 的特性可以用來使用於 Assign :

```js
const getInt = i => {
	return i || 1;
]
```

而 AND 的特性是可以使用於 Execute:

```js
const getInt = (i, callback) => {
	typeof callback === 'function' && callback(i || 1);
}
```
12月 04, 2019 Webpack
最近在 build react project 時常常會發生這個問題,build 了後久也沒有回應,然後就會跳出一個 Error 137 出來。

在網路上爬了一會發覺原來是因為系統的 memory 不足夠,所會才會出現如此 Error,立時想起運得的環境是在 1 CPU 512 RAM 的 VPS 上 build .....

其他人提及到問題的相關的網址 : https://github.com/hackmdio/codimd/issues/1254

12月 04, 2019 19 Things
### Google AdSense 是什麼?

Google AdSense 是一個廣告的平台,可以在那裡下廣告。而網站的制作人如果想在你的網站上放入廣告來提升收入,這個平台也可以把廣告分發到第三方的網站上刊登。

### 不是教大家申請

這個文章不是要教大家如何申請 AdSense,而想講的是這個 19Site (到現時為止) 的  AdSense 申請又被 Block 下來了 !! 雖然也是意料中的事情呢,因為這個 Blog 實在沒有什麼內容可以顯示。

![](https://cdn.19site.net/files/6c/9b/6c9ba927-82d7-4203-9c9a-e404e063ae9d.png '這是 Block 下來的原因說明')

在日後的日子會慢慢的加入新的內容,總有一天可以試試這個功能。

12月 04, 2019 Google
今日都係要繼續了解 Google Admin SDK 如何使用。

暫時得出的結果是先要 Request [Domain](https://developers.google.com/admin-sdk/directory/v1/reference/domains) 為 OAuth2 後的第一個動作。

這個 Request 會回傳自己帳戶內的所有 doamins 記錄 (正常每一個 Google GSuite 帳戶不是只會有一個 Domain 的嗎 )。

而當你讀取記錄時就可以取得你當前帳戶的 Domain 名稱。

這個 Domain 名稱在抽取使用者時是需要填入的。

### 讀取 Domain 資料

首先使用以下的方法取得 Domain 資料 

> GET https://www.googleapis.com/admin/directory/v1/customer/my_customer/domains

### 讀取 User 資料

回傳的 JSON 可以得到 Domains 列表,然後當使用取得 User data 的 Rest 時,便可以把上面取得的 Domain 名稱填入到下面的連結 :

> GET https://www.googleapis.com/admin/directory/v1/users?domain=testingdomain.com

這樣便可以正確取得資料
12月 03, 2019 React
今天講一下 React 的 Props。

在 React 中我們會常常使用到 props 來傳遞資料,把父系的資料傳送到子系的物件上。需我們在物件上收 props 通常會是以下的格式 :

```js
// 例子 1 (直接使用 function 來建立 element)
const ReactElement = props => {

	// extract props elements
	var { style, children } = this.props;
	
	// render
	return (
		<div style={ ...style }>
			{ children }
		</div>
	);
};

// 例子 2 (使用 class 的方法建立 element)
class ReactElement extends React.Component {
	constructor(props) {		
		super(props);
	}
	
	render() {

		// extract props elements
		var { style, children } = this.props;
		
		// render
		return  (
			<div style={ ...style }>
				{ children }
			</div>
		);
	}
}
```

在例子 2 的情況下,筆者最近多了一個習慣,使用在存取 props 時更能預測資料的預定值。如下:

```js
getProps(props) {
	
	// set input object
	props = props || this.props;
	
	// return props values
	return {
		
		// check and set default value for style
		style: typeof props.style === 'object'? props.style: {},

		// check and set default value for children
		children: typeof props.children === 'object' && Array.isArray(props.children)? props.children: []
	};
}
```

使用以上的方法,當你在抽取要使用 props 的資料時,便可以機乎確定所使用的資料是在你的預測範圍內,以減少發生錯誤的可能性。

```js
var { style, children } = this.getProps();
```

在可以用於 componentDidUpdate 的情況

```js
componentDidUpdate(oldProps) {
	var oP = self.getProps(oldProps);
	var cP = self.getProps();
}
```

當然如果正統來說是使用 PropType 和 defaultProps 較為好。

相關文件 : https://zh-hant.reactjs.org/docs/typechecking-with-proptypes.html 
12月 02, 2019 19 Things
> Original URL: http://www.branbibi.com/blog/s.php?n=english-words&tid=Do_you_%E8%88%87_Are_you_%E7%9A%84%E7%94%A8%E6%B3%95%E5%B7%AE%E7%95%B0

Do you 與 Are you 的用法差異是許多剛開始學英文的朋友經常搞不清楚的問題,其實無論是 Do you 還是 Are you 都是疑問句的開頭,舉例來說,英文的"Do you want to go with me?"這句是詢問對方"你要不要跟我去?"的意思。

再來看"Are you married?"這句片語是詢問對方是否已經結婚,當你要詢問對方的時候可能會使用 Do you 開頭,也可能會使用 Are you 開頭,這兩者在使用上有一些文法上的區別。

### Do you 開頭的疑問句

用 Do you 開頭的疑問句後麵必須接原形動詞(verb)或副詞(adverb),因為 Do 的關係,以下是一些 Do you 開頭的疑例句。

-	Do you like me? → 你喜歡我嗎?

-	Do you love me? → 你愛我嗎?

-	Do you speak english? → 你會說英語嗎?

-	Do you like your job? → 你喜歡你的工作嗎?

-	Do you really love me? → 你真的愛我嗎?

-	Do you really want to know? → 你真的想知道?

-	Do you really think so? →你真得這樣想嗎?

### Are You 開頭的疑問句

用 Are you 開頭的疑問句後麵可以接的東西就多了,包含介詞(preposition)、代名詞(pronouns)、副詞(adverb)、形容詞(adjective)都可以,不過不能接原形動詞,以下是一些 Are you 開頭的疑問句。

-	Are you there? → 你在嗎?

-	Are you sure? → 你確定嗎?

-	Are you hungry? → 你餓了嗎?

-	Are you tired? → 你累了嗎?

-	Are you going to work today? → 你今天會去上班嗎?

-	Are you busy? → 你在忙嗎?
12月 02, 2019 19 Things
> 版權聲明:本文為CSDN博主「碼農翻身」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。

> 原文鏈接:https://blog.csdn.net/coderising/article/details/101731213

這是來自我的星球的一個提問:“C語言本身用什麼語言寫的?”

換個角度來問,其實是:C語言在運行之前,得編譯才行,那C語言的編譯器從哪裏來? 用什麼語言來寫的?如果是用C語言本身來寫的,到底是先有蛋還是先有雞?

### 1. 我們假設世界上不存在任何編譯器, 先從機器語言說起,看看怎麼辦。 

機器語言可以直接被CPU執行,不需要編譯器。

然後是彙編語言, 彙編語言雖然隻是機器語言的助記符,但是也需要編譯成機器語言才能執行,沒辦法隻能用機器語言來寫這第一個編譯器了(以後就不用了)。 

彙編語言的問題解決了,就往前邁進了一大步,這時候就可以用彙編語言去寫C語言的編譯器,我們說這是C編譯器的老祖宗。 

有了這個老祖宗,就可以編譯任意的C語言程序了,那是不是可以用C語言本身寫一個編譯器?隻要用老祖宗編譯一下就可以了。

OK, 這麼一層層上來,終於得到了一個用C語言寫的編譯器, 真是夠麻煩的。 

到這個時候,之前那個彙編寫的C語言編譯器就可以拋棄了。 

當然,如果在C語言之前,已經出現了別的高級語言,例如Pascal,那就可以用Pascal來寫一個C語言的編譯器。

第一個Pascal的編譯器據說使用Fortran寫的。而做為第一個高級語言的Fortran,它的編譯器應該是彙編語言寫的。

### 2. 關於編譯器,這裏邊有個有趣的傳說:

傳說Unix 發明人之一的 Ken Thompson在貝爾實驗室,大搖大擺的走到任何一台Unix機器前,輸入自己的用戶名和密碼,就能以root的方式登錄! 

貝爾實驗室人才濟濟,另外一些大牛發誓要把這個漏洞找出來,他們通讀了Unix的C源碼,終於找到了登錄的後門, 清理後門以後編譯Unix , 運行, 可是Thompson 還是能夠登錄進去。

有人覺得可能是編譯器中有問題,在編譯Unix的時候植入了後門, 於是他們又用C語言重新寫了一個編譯器,用新的編譯器再次編譯了Unix, 這下總算天下太平了吧。

可是仍然不管用, Thompson 依然可以用root登錄,真是讓人崩潰 !

後來Thompson 本人解開了秘密,是第一個C 語言編譯器有問題, 這個編譯器在編譯Unix源碼的時候,當然會植入後門, 這還不夠,更牛的是,如果你用C 語言寫了一個新編譯器,肯定也需要編譯成二進製代碼啊,用什麼來編譯,隻有用Thompson寫的那第一個編譯器來編譯,好了, 你寫的這個編譯器就會被汙染了,你的編譯器再去編譯Unix , 也會植入後門 :-)

說到這裏我就想起了幾年前的XcodeGhost 事件,簡單來說就是在Xcode(非官方渠道下載的)中植入了木馬,這樣XCode編譯出的ios app都被汙染了,這些app就可以被黑客利用做非法之事。 

雖然這個XCodeGhost和Thompson的後麵相比差得遠,但是提醒我們,下載軟件的時候要走正規渠道,從官方網站下載,認準網站的HTTPS標準,甚至可以驗證一下checksum。

### 3. 可能有人問:我用彙編寫一段Hello World都很麻煩,居然有人可以用它寫複雜的編譯器?這可能嗎?

當然可能,在開發第一代Unix的時候,連C語言都沒有, Ken Thompson 和 Dennis Ritchie 可是用彙編一行行把Unix敲出來的。   WPS第一版是求伯君用彙編寫出來的, Turbo Pascal 的編譯器也是Anders 用彙編寫出來的,大神們的能力不是普通人能想象得到的。 

對於編譯器來說,還可以采用“滾雪球”的方式來開發:

還是以C語言為例,第一個版本可以先選擇C語言的一個子集,例如隻支持基本的數據類型,流程控製語句,函數調用...... 我們把這個子集稱為C0。

然後用彙編語言寫個編譯器,隻搞定這個語言的子集C0,這樣寫起來就容易不少。

C0這個語言可以工作了,然後我們擴展這個子集,例如添加struct,指針......  ,把新的語言稱為C1。 

那C1這個語言的編譯器由誰來寫?   自然是C0。

等到C1可以工作了,再次擴展語言特性,用C1寫編譯器,得到C2。 

然後是C3, C4......  最後得到完整的C語言。

這個過程被稱為bootstraping , 中文叫做自舉。
過去文章
2025 (9)
4 (5)
3 (1)
2 (3)
2024 (25)
11 (3)
10 (3)
9 (1)
3 (18)
2022 (6)
10 (1)
6 (2)
5 (1)
3 (1)
1 (1)
2021 (21)
11 (7)
7 (1)
6 (2)
5 (2)
4 (6)
3 (2)
2 (1)
2020 (92)
12 (1)
11 (2)
10 (4)
9 (10)
8 (5)
7 (1)
6 (3)
5 (1)
4 (4)
3 (25)
2 (7)
1 (29)
2019 (57)
12 (25)
11 (7)
9 (25)