2016-08-18

Node: 如何安裝 Node.js?

如何安裝 Node.js?

結論:

// 安裝 `nvm`
> curl -o- https://raw.githubusercontent.com/creationix/nvm//install.sh | NVM_DIR= bash
// ex:
> curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.4/install.sh | NVM_DIR=/usr/local/nvm bash

// 立刻載入 `nvm`
> source ~/.bashrc

// 安裝 `node`, 沒有 會安裝最新的版本 
> nvm install node  

// 安裝完成, 檢查一下可不可以正確執行
> node -v
// v6.4.0

// 安裝新版 `node` 時一併安裝舊版的全域模組
>nvm install  --reinstall-packages-from=
// ex:
> nvm install v6.4.0 --reinstall-packages-from=v4.5.0

// 如果是剛剛安裝時的使用者, 安裝時就已經加入
// 建立各自使用者的 `nvm` 引用
> vi .bashrc
// .bashrc 加入下列, ex:  = /usr/local/nvm
>> export NVM_DIR=""
>> [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm

// 建立各自使用者內定的 `node` 版本, 不受 `nvm` default 的影響, 建立一個 `.nvmrc` 檔
> echo "4.5" > .nvmrc
> vi .bashrc
// .bashrc 加入下列
>> nvm use

## 廢話:
有 Node 卻沒有 Pipe 真是一個奇怪的事.

## 正文:
參[結論]

2016-08-17

Node: 如何安裝 PM2?

如何安裝 PM2?

結論:

需要先安裝 Node.js, 參如何安裝 Node?
// 安裝 `pm2`,
// pm2@next 目前會安裝 v2, 沒有 @next 則安裝 v1.13. 搭配 `node` v6 安裝 `pm2@next` 必較好, 但 v1.13 也相容 `node` v6 
> npm install -g pm2@next

// 定期清除 pm2 log
> pm2 install pm2-logrotate
// log 只保留 7 天
> pm2 set pm2-logrotate:retain 7
// 壓縮非現在的log
> pm2 set pm2-logrotate:compress true

// 開機啟動時, 執行 `PM2`, 但目前只支援單一使用者, 無法多使用者都在開機啟動時自動執行 `PM2`(v2.0.7)
// platform = ubuntu, centos, redhat, gentoo, systemd, darwin, amazon
> pm2 startup 
// ex:
> pm2 startup ubuntu
// user> 會提示你應該執行的 cmd
> sudo su -c "env PATH=$PATH:/usr/local/nvm/versions/node/v6.4.0/bin pm2 startup ubuntu -u  --hp /home/"
 
// 儲存目前 `PM2` 執行的程式程序
> pm2 save

// 重新載入儲存的程式程序
> pm2 resurrect

廢話:

正文:

參[結論]

2016-08-15

Shell: 如何刪除無用的 Ubuntu modules ?

如何刪除無用的 Ubuntu modules ?

結論:

> dpkg -l linux-image-[0-9]*-generic | awk '/^ii/{ print $2}' | grep -v -e `uname -r` | sort -V | head -n -1 | xargs apt-get purge -y 
> dpkg -l linux-headers-[0-9]*-generic | awk '/^ii/{ print $2}' | grep -v -e `uname -r` | sort -V | head -n -1 | xargs apt-get purge -y 

廢話:

好意也時會帶來惡果.

正文:

當你安裝完 Ubuntu Server 後, 如果沒有關掉自動更新, ubuntu 會很貼心的將他每一次的 release 都放一份映象檔到你的 Server 上. 但可喜可賀的, 假如你從不需要關機(也沒有當機), 當然你用的還是你原先安裝的那個版本. 但不幸的事總是會伴隨好意來到. 雖然每個映象檔大約也不過 200M(目前, 誰都知道系統是會胖的), 但以每個月更新 1 到 2 次的頻率, 不到半年就佔去你 1G 的空間了, 因為自動更新雖然會幫你自動安裝, 但不會幫你自動清理用不著的檔案. 下載的檔案一般會放在 /lib/modules 目錄下.
> du -hd1 /lib/modules
du -hd1 /lib/modules
// 197M    /lib/modules/3.19.0-65-generic
// 197M    /lib/modules/3.19.0-64-generic
// 197M    /lib/modules/3.19.0-66-generic
// 197M    /lib/modules/3.19.0-61-generic
// 788M    /lib/modules
但更大的不幸是你非常有專業水準的為系統分割了磁區或限定了容量, 或本來你的硬碟就不怎麼大. 所以, 準備好你的杯子, 因為悲劇發生了.
> df -h
// Filesystem      Size  Used Avail Use% Mounted on
// /dev/sda1       9.9G  3.7G  5.7G  40% /
以三個月使用 1G 來計算, 悲劇將在一年後發生.
你可以選擇直接刪除 /lib/modules 下用不著的檔案. 但你最好還是先看看你目前使用的系統模組是哪個版本. 使用 uname -r. 記得到Ubuntu release 看一下你用的版本(version)跟核心(kernel)對照. 也可以順便看一下詳細有哪些分支.
>  uname -r
// 3.19.0-65-generic
以這個例子來說, 3.19.0-64-generic 和 3.19.0-61-generic 是舊的. 通常我們會保留前一個版本, 也不安裝最新的版本, 再加上目前使用的版本, 一共有三個版本.
雖然直接刪除 /lib/modules 下用不著的檔案不失為一個簡單的方法. 但還是有很多不需要的安裝檔在其他地方. 使用 apt-get remove是比較好的方式.
> sudo apt-get remove -y --purge 3.19.0-61-generic*
// or 
> sudo apt-get purge -y 3.19.0-61-generic*
// 簡化的參數
要移除前, 先找出目前安裝過的版本.
> dpkg --list | grep  linux-image-.*-generic | awk '{print $2}'
// or
> dpkg -l | grep  linux-image-.*-generic | awk '{print $2}'
linux-image-3.19.0-61-generic
linux-image-3.19.0-64-generic
linux-image-3.19.0-65-generic
linux-image-3.19.0-66-generic
linux-image-extra-3.19.0-61-generic
linux-image-extra-3.19.0-64-generic
linux-image-extra-3.19.0-65-generic
linux-image-extra-3.19.0-66-generic
再移除,
> apt-get purge -y 3.19.0-61-generic*
// 選擇要移除的
// 或
> dpkg -l linux-image-[0-9]*-generic | awk '/^ii/{ print $2}' | grep -v -e `uname -r` | sort -V | head -n -1 | xargs apt-get purge -y 
// 使用 grep -v -e `uname -r` 去掉"目前的版本"
// 使用 sort -V 依版本先後排序
// 使用 head -n -1 去掉"最後的版本"
如果不需要 “header”, 也可以移除
> dpkg -l linux-headers-[0-9]*-generic | awk '/^ii/{ print $2}' | grep -v -e `uname -r` | sort -V | head -n -1 | xargs apt-get purge -y 
要裝回來, 就用 apt-get install,
> apt-get install linux-image-3.19.0-61-generic
最後你可以將他寫成 script, 放到開機啟動或 crontab 去執行.

2016-08-10

JS: 如何使用 gmail oauth2 寄送 email ?

如何使用 gmail xoauth2 寄送 email ?

結論:

安裝:
> npm install --save nodemail xoauth2
設定:
  • 請先登入要使用帳號的 google 控制台
  • 建立或選擇一個專案
  • 啟用 Gmail API, LeftTab p[資料庫] -> p[Gmail API] -> TopMeun p[啟用API]
  • 新增憑證 -> [OAuth 用戶端 ID], o[網路應用程式], i[已授權的重新導向 URI]="https://developers.google.com/oauthplayground". 紀錄產生的
    • 用戶端 ID
    • 用戶端密碼
  • OAuth 2.0 Playground
  • 設定 OAuth 2.0 configuration(右上的齒輪圖示)
  • p[Step 1.] Select & authorize APIs
  • p[Step 2.] Exchange authorization code for tokens
    • p[Exchange authorization code for tokens]
    • 紀錄產生的 Refresh token
程式: sendMail.js
var nodemailer = require('nodemailer');
var xoauth2 = require('xoauth2');
//將剛剛產生的 用戶端 ID, 用戶端密碼, Refresh token 填入
var xoauth2gen = xoauth2.createXOAuth2Generator({
    user: "{$使用帳號的email}",
    clientId: "{$用戶端 ID}",
    clientSecret: "{$用戶端密碼}",
    refreshToken: "{$Refresh token}"
});

var transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: { xoauth2: xoauth2gen }
});

// 寄信
transporter.sendMail({
    "from": "{$使用帳號的email}",
    "to": "{$寄送的email}",
    "subject": "{$主旨}",
    "text": "{$文字內容}"
}, function(err) {
    !err && console.log("send fin");
});

廢話:

常常能聽到需求是越安全越好, 殊不知安全是需要代價的.
Gmail 為了改善原本 POP3/SMTP 協定的安全問題, 一直做了很多工作, 但也讓程式設計師增加了工作量. 這次整個導入 oauth2 機制, 讓原本簡單的工作變複雜了.

正文:

為避免浪費時間, 在還沒往下看之前, 如果你不想改變任何程式, 請調低安全設定. that’s all.
有許多套件能幫助你作寄信這件事, 所以別自己寫. 以下是以 nodemail 作為介紹.
在 oauth2 的支援上, nodemail 使用 xoauth2. 但不知道是 Gmail API 本身不支援還是 nodemail 不支援, 我沒辦法使用 JWT, 只能使用WebServer 的方式. 而這個方法需要先建立 OAuth 2.0 Client ID 的授權. 因為沒有自己的"授權URI", 所以必須借用OAuth 2.0 Playground來產生 “Refresh Token”. 其他詳參[結論].

2016-08-07

JS:如何開始在 nodejs 使用 typescript ?

如何開始在 nodejs 使用 typescript ?

結論:

安裝:
  • typescript, 當然就是 TypeScript.
  • typings, TypeScript Definition Manager. 要用到其他第三方函式庫, 就必須要型別定義檔, 自己編寫也行, 但最好還是有管理他們的工具.
> npm install -g typescript typings
> tsc -v
//Version 1.8.10
轉譯:
  • 簡單使用 tsc .
  • 或建立 TypeScript 的專案檔 ‘tsconfig.json’, 使用 tsc -p 為專案檔所在目錄.
引用:
  • 使用 typings 安裝第三方函式庫的 TypeScript definitions.
  • 修改 ‘tsconfig.json’, 將定義檔 “typings/index.d.ts” 加入 “files” 或 “filesGlob”.
> npm install  --save
> typings install  --save
// 在新版的 TypeScript 2.0 你可以用下面的命令代替 `typings install `
> npm install --save @types/
// 你可以在[這裡](https://aka.ms/types) 查有哪些可以用
// 其實應該就是 `typings search --source npm` 的 Web 版
// 但可以省掉處理 `typings` 的 `.d.ts` 檔.
監控: 使用 tsc -w -p  來監控檔案, 在檔案有變動時進行轉譯.

廢話:

ES6 改名叫 ECMAScript 2015, 好長的名字. 我還是叫他 ES6 吧. 參考一下ES6 支援度, 目前(20160805)還沒有 100% 支援的環境, 雖然他叫 ECMAScript 2015. 想要好好寫 ES6, 還是得找個轉譯器, 但取名 babel 多不吉利阿.
TypeScript 雖然明明只是個 Script, 但卻是偶像(Anders Hejlsberg)操刀. 所以我偏心了. 我連 ES7 都不知道長甚麼樣子, TS 卻去支援 ES8 的特性, 天知道是甚麼鬼阿!

正文:

使用 npm 安裝完 typescript 與 typings 後, 開始寫第一個程式: hello.ts. 沒看錯附檔名要從熟悉的 .js 改為 .ts. 就讓我們寫一個有 typescript 風格的程式碼吧, 如下
var hello = (word: string) => {
    console.log(`Hello, ${word}`);
}
hello("TypeScript");
他會轉譯為,
> tsc hello.ts; cat hello.js
/** 
    var hello = function (word) {
        console.log("Hello, " + word);
    };
    hello("TypeScript");
 */
跟一般一樣, 執行 node hello.js 即可.
但寫程式怎麼能不用第三方函式庫? 不管是 lodash 還是 async , 沒有了, 可是會直接降低一階的生產力. 雖然 ES6 新增的 Generator 和部分方法可替代, 但畢竟是在過度期, 並不是全部的環境都支援, 熟悉與法也需要一段時間. 這時候就輪到 typings 出場. 安裝完 typings 後, 會多出一個叫 “typings”(類似 NPM 中 node_modules 的角色) 的目錄來放 TypeScript 的定義檔.
> typings init
// 會產生一個 typings.json (類似 NPM 中的 package.json)檔案.
> npm install lodash --save
// 安裝 lodash, 已經安裝了就可以略過
> typings install lodash --save
// 會將定義檔放到 "typings/modules/lodash/"
改寫一下 hello.ts, 如下 helloAnybody.ts
import * as _ from "lodash";

var hello = (compiler: string) => {
    console.log(`Hello, ${compiler}`);
}

var anybody = ["TS","JS","Node"];
_(anybody).each(hello);

為了下指令方便起見, 讓我們編輯 TypeScript 的專案檔. 先利用 tsc --init 產出一個 “tsconfig.json”, 如下
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false
    },
    "exclude": [
        "node_modules"
    ]
}
基本上 “tsconfig.json” 的檔案內容只要 {} 就可以了. 但如果是這樣所有的參數就會採內定值, 這可能不是我們要的. 其實如果是這樣有這個檔跟沒有是一樣的. 所以, 接下來就一些主要會用到的部分進行解釋與修改. “tsconfig.json” 主要有兩部分,
  • compilerOptions, 指示 tsc 應該將檔案轉譯成什麼樣的 JavaScript.
  • exclude, files, 指示 tsc 要轉譯哪些檔案.
首先是 “compilerOptions” 的部分,
  • target: 指示採用哪一版的JS. 可用的值有 “es3”, “es5”, “es6”, 內定是 “es5”. 基本上 “es5” 是一個合適的選擇, 除非你運行在一個特別舊的環境.
  • module: 指示採用哪種模組模式, 可用的值有 “none”, “commonjs”, “amd”, “system”, “umd”, “es6” 或 “es201”. 但如果 “target” 在 “es5” 以下, “es6” 或 “es201” 模式是無效的. 如果你選擇的 “target” 是 “es6”, 目前合法的模式也就只有 “es6”, 如果 “target” 不是 “es6”, 則 “module” 內定會是 “commonjs”. 因為是 node, 所以沒疑問就是 “commonjs”.
  • moduleResolution: 指示採用何種方式取得模組, 可用的值有 “node”, “classic”, 內定值是"classic". 因為我們是在 node 上開發, 所以最好是加上 {moduleResolution:"node"}. -其他就有需要時再參考文件.
再來是檔案的部分,
  • 如果都沒有指定 tsc 會轉譯所有目錄與其子目錄下的所有 TypeScript (*.ts or *.tsx) 檔案.
  • 有 “files”,  會轉譯所有吻合 “files” 的檔案. 有 “exclude”, 會轉譯所有不吻合 “exclude” 的 TypeScript 檔案.
  • 如果同時有 “exclude” 和 “files” 的條件, 則 “files” 優先.(不考慮 “exclude” 的條件)
  • “exclude” 排除的可以是目錄, 但 “files” 只能是檔案. 而且都不支援 glob.
  • “filesGlob” 支援 glob, 可以取代 “exclude” 和 “files”. 但只有 AtomTypeScript 才支援.
  • “include” 支援 glob, 但 TypeScript 要版本 2.0 以後.
新的"tsconfig.json", 如下
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "moduleResolution": "node",
        "noImplicitAny": false,
        "sourceMap": false
    },
    "files": [
        "helloAnybody.ts"
    ]
}
當你執行 tsc -p . 會出現 helloAnybody.ts(1,20): error TS2307: Cannot find module 'lodash'., 這是因為缺乏定義檔, 將之改為
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "moduleResolution": "node",
        "noImplicitAny": false,
        "sourceMap": false
    },
    "files": [
        "helloAnybody.ts",
        "typings/index.d.ts"
    ]
}
即可.
接下來安裝 async, 照例使用 typings install async --save, 卻發生錯誤. 這是因為 typings 對定義檔的 source 有區分. 大致上可以分為
  • external,
    • npm 來自 NPM, 內定使用
    • github 來自 GitHub
    • bitbucket 來自 Bitbucket
    • bower - 來自 Bower
    • common - 未知來源的基本函式庫
    • shared - 共享函式庫
  • global,
    • lib 程式環境的共享函式庫
    • env 程式環境
    • global 全域的函式庫
    • dt 來自 DefinitelyTyped
來源可以~ 或 --source 指定. 但屬於 global 類別的要再加上--global 參數. 可以在安裝前使用typings search  查詢一下.
> typings search async
/*
Viewing 9 of 9

NAME   SOURCE HOMEPAGE            SCRIPTION   VERSIONS UPDATED
async  dt     https://github.com/caolan/async 1       2016-08-04T11:33:11.000Z
......

*/
將"helloAnybody.ts"改成為非同步版本.
import * as _ from "lodash";
import * as async from "async";

var helloAsync = (compiler: string, callback: (...args: any[]) => void) => {
    console.log(`Hello, ${compiler}`);
    return callback();
}

var anybody = ["TS", "JS", "Node"];
async.each(anybody, helloAsync, (err) => { console.log('fin.') });
執行 tsc -p .後, 被轉譯如下,
> cat helloAnybody.ts
/*
"use strict";
var async = require("async");
var anybody = ["TS", "JS", "Node"];
var helloAsync = function (compiler, callback) {
    console.log("Hello, " + compiler);
    return callback();
};
async.each(anybody, helloAsync, function (err) { console.log('fin.'); });
*/
如果你使用的函式庫沒有定義檔, 而你也不在乎是否在開發環境或轉譯時檢查該函式庫的型別, 你可以更簡單的使用 --allowJs 或在tsconfig.json 加入 {compilerOptions: {allowJs : true}}. 這樣你就能告別 error TS2307.
最後, 你可以使用 -w 來使檔案變動後能自動轉譯為 JS.
> tsc -w -p .
//11:46:57 - Compilation complete. Watching for file changes.
但 -w 是一開始就會決定要監控哪些檔案, 所以就算你使用支援 glob 的 include, 後來才新增加的檔案就算符合條件也不會被監控, 要能監控新增檔案可用 gulp.watch 之類的方式來處理.

2016-08-03

JS:如何判斷真假值?

如何判斷真假值?

結論:

單值判斷:
  • false0, 空字串("", 字串長度為 0), NaNnull 以及 undefined 是為 false
  • 其他值為 true
等於(==)判斷:
  • 兩 object 間的比較只是比較其內部參照是否相同
  • 若 object 與基本資料型別 numberbooleanstring 比較, object 會先呼叫 object.valueOf(), 若是傳回 object, 則會再呼叫 object.toString() 做為比較值, 再比較
  • 可轉型的基本資料型別 numberbooleanstring 彼此比較, 如果型別不同, 則都轉成 number 後, 再比較
  • 與不轉型的基本資料型別 nullundefined 比較, 除與 nullundefined 比較結果為 true 外, 其他都是 false
  • NaN 不等於 NaN

廢話:

js 是 我學過最 容易被誤解的語言.

正文:

js 的基本資料型別是
  • number 不管整數, 浮點數, 反正是數字就算. 但 NaN(Not-a-Number)是數字喔.
  • boolean 只有兩個 true / false.
  • string 就是字串.
  • null 表示為空值的關鍵字; 也是基本型別.
  • undefined 連空值都不是的 undefined; 也是基本型別.
  • symbol 新的, 在 ECMAScript 2015(ES6) 才有. 這裡不討論.
除此之外的全都可以視為是物件(object). 其中有容易被誤解的資料包裝型別, 分別是
  • Number
  • Boolean
  • String
這不是跟上面的重複了? 錯, 請注意第一個字母是大寫. 這三個分別是numberboolean 和 string 的包裝物件. 證據如下,
> typeof 1
//'number'
> typeof new Number(1)
//'object'
>
> typeof '1'
//'string'
> typeof new String('1')
//'object'
>
> typeof true
//'boolean'
> typeof new Boolean(true)
//'object'
使用 new 來實例化基本資料型別的包裝物件, 再簡單的使用 typeof 檢查他的型別後, 可以看出他們都是物件而不是基本資料型別.
這就是第一個誤解. 有相同值的基本資料型別包裝物件會等於(===)該值.
> var str1='1'
//'1'
> var str2='1'
//'1'
> str1===str2
//true
// but 
> var strobj=new String('1')
> str1===strobj
//false
// 因為 str1 跟 strobj 型別不一樣
這是一個混用基本資料型別包裝物件跟基本資料型別時, 會出現的微妙問題, 強烈建議不要混用. 比較兩者, 使用 new 時會需要額外的資源來初始化, 且當需要 String 的方法時, js 會將基本資料型別當成型別包裝物件來使用. 所以就是說"不要再用 new String()new Number(),new Boolean() 了".
但是, 接下來就是
第二個誤會了. js 在使用 == 時會自動轉成相同型別來比較. 或 有相同基本資料值的基本資料型別包裝物件會相等
嚴格來說, 會轉型比較的只有 基本資料型別. 而如果比較的兩邊是 object 則是比較其內部參照(指標). 但在使用 == 比較時, 如果與 基本資料型別 比較的不是基本資料型別, js 會先呼叫 valueOf() 後再比較. 產生誤解的原因還是來自型別包裝物件. 型別包裝物件的原型繼承自Object , 所有 object 都會有 “valueOf” 這個方法. 根據定義, “The valueOf() method returns the primitive value of the specified object.”, 這本該傳回代表該物件的基本型別值. 但奇特的是, 實際上除了 基本資料型別包裝物件 會傳回基本型別值和 date會傳回 number 外, 其他都是傳回物件本身. 也就是, 基本資料型別包裝物件在與基本資料型別比較時會先透過 valueOf 傳回基本資料型別, 如果兩者型別還是不同, 則再將其轉型為相同型別來比較.
> var str1='1'
> var strobj=new String('1')
> str1==strobj
//true
// but
> String.prototype.valueOf=function(){ return 'this is string';}
// 修改 String 的 valueOf 方法
> str1==strobj
//false
// 因為 '1' != 'this is string'
> strobj.valueOf()
//'this is string'
這邊衍伸一個想法, 如果我想讓 object 的 valueOf 傳回可比較的值, 是不是 object 就能進行比較了? 遺憾, 物件的比較就只是比較其內部參照是否相同, 除非, 是物件與基本資料型別的比較, 物件才會呼叫 valueOf 來比較.
假如不是與基本資料型別的比較,
> var strobj1=new String('1')
> var strobj2=new String('1')
> strobj1==strobj2
//false
// 因為 strobj1, strobj1 雖然都是 String 類別, 但還都是物件. 
> typeof strobj1
//'object'
> typeof strobj2
//'object'
所以就是說"不要再用 new String()new Number()new Boolean() 了". 因為很重要, 所以說兩遍.
第三個誤解是 js 在使用 == 時會自動轉型, 因為 '0'==0true''==0是 true, 所以 '0'=='' 是?.
在基本資料型別可以分為兩類, 一種是有包裝的, 一種是沒包裝的. 沒包裝的都一樣, 有包裝的各有各的問題.
有型別包裝物件的
  • number
  • string
  • boolean
無型別包裝物件的
  • null
  • undefined
先說沒包裝的,
> null==null
//true
> undefined==undefined
//true
> null==undefined
//true
// 所以說沒包裝的都一樣
>
> 0==null
//false
> 0==undefined
//false
> ''==null
//false
> ''==undefined
//false
> false==null
//false
> false==undefined
//false
// 但他跟有包裝的不一樣
// null, undefined 也不會自動轉型為 number, string, boolean
講完沒包裝的, 剩下就是問題了.
學過數學的都知道, 單位相同才能比較. 如果有三種基本資料型別要兩兩比較, 有幾種轉型方法? 學過排列組合的都知道是 3*2 種. 但在 js 中到底是依什麼規則自動轉型, 隨機, 左邊優先, 右邊優先? 都不是. 使用 == 時自動轉型規則是型別 不同 就先轉成數字.
  • 先說 boolean 吧, 他只有 true / false 兩個值, 所以規則也很簡單. true 是 1 而 false 是 0.
  • 再來是 string, 凡是數字字串的都能轉成數字, 空字串(length=0)或空白字串(/\s+/)是0, 其他都是 NaN.
  • 然後 number 不轉型, 但 NaN 不等於 NaN.
剩下的就只是單純的數字比較罷了.
> false==0
//true
//
> true==1
//true
//
> '0'==0
//true
//
> ''==0
//true
//
> ' \t\r\n\v\f'==0
//true
//
> NaN==NaN
//false
//
> '0'==''
//false
// 兩個都是字串, 所以不轉型, 直接比較
關於轉型為 number, 有一個簡單的驗證方法是, 使用一元運算子+. 注意, 不是 二元 運算子 +. 也就是說在+之前不能有其他的運算元(operand).
> +''
//0
//
> +'0'
//0
//
> +' '
//0
//
> +false
//0
//
> +'NaN'
//NaN
// 字串 'NaN' 無法轉型為數字, 故為 NaN
> +null
//0
// 在 `==` null 是不轉型的
> +undefined
//NaN
// 在 `==` 只有 number, string, boolean 會自動轉型
一元運算子 + 的轉型規則如下,
  • 如果是 undefined 或 null, 則為 0
  • 物件會轉換成字串
  • 如果可能, 字串會轉成數字. 如果不行, 就是 NaN
  • 布林值會被當做數字, false 是 0, true 是 1
值得注意的是在單值的真假值判斷時空字串("", 長度為 0)是 false, 但空白字串(/\s+/, 正規表示式判斷為真時)是 true; 但在等於(==)判斷時, 空字串與空白字串都是 false.
> ' '==false
//true
// 因為 ' ' 轉成數字是 0, false 轉成數字也是 0
最後, 在 object 與基本資料型別比較時, object 會呼叫 object.valueOf() 方法, 以其傳回值與基本資料型別比較. 但若object.valueOf() 傳回的不是基本資料型別, 而是物件時, 則呼叫 object.toString()方法, 以其傳回值與基本資料型別比較.
以陣列來說, 會得到一些奇妙的結果.
> []==false
//true
//
> [,]==false
//true
//
> [,,]==false
//false
> [,,].toString()
//','
//
> [[],]==false
//true
//
> [[[]],]==false
//true
//
> [,[]]==false
//false
> [,[]].toString()
//','
//
> [[],[]]==false
//false
> [[],[]].toString()
//','
//
> [null,]==false
//true
//
> [null,[]]==false
// false
> [null,[]].toString()
//','
//
> [null,null]==false
//false
> [null,null].toString()
//','
// 以上的 false 全換成 0 或 "" 結果也一樣
// 
> [0,]==false
//true
//
> ['0',]==false
//true
//
> [1,]==true
//true
> [1,]==1
//true
> [2,]==true
//false
這表還能列的更長, 為了不要犯上奇怪的錯誤, 建議拿物件跟 numberbooleanstring 比較時, 請自行將物件轉為相同的型別, 別依賴自動轉型. 不然, 就把[結論]記清楚吧.

補充:

雖然 date 在等於比較(==)時是轉成字串來比較. 但在檢查是不是有效的日期上, 還是比較建議用 isNaN 來檢查.
> new Date('date')==NaN
//false
> new Date('date')=='NaN'
//false
> (+(new Date('date')))==NaN
//false
// 雖然使用`+`將日期轉成數字, 但無效日期是傳回 `NaN`, 
// 但 `NaN` 是不等於 `NaN` 的,
> (+(new Date('date')))=='NaN'
//false
// `NaN` 是 `number`, 所以右邊 'NaN' 字串要轉成數字, 可是他不是合法的數字字串, 
// 所以傳回的是 `NaN`, 然而 `NaN` 是不等於 `NaN` 的.
> new Date('date')=='Invalid Date'
//true
> isNaN(new Date('date'))
//true
如果不是特意要使用 == 的特性來處理, 在不確定結果時, 寧願多寫幾行使用先作型別檢查是否相等的 ===, 也不要有問題時, 找不到問題點.