2019年9月30日 星期一

好站 : Python Learn

今天偶然逛到下面這個 PythonLearn 教學網站, 特點是提供了完整的教材, 包括簡報, Youtube 視訊教學影片, 還可直接下載 PDF 書籍, 影片, 聲音等等, 規劃得非常好. 雖然是英文解說, 或許會讓人卻步, 但影片均有英文字幕可輔助理解, 換個角度看, 學完全部課程英文也有進步豈不一舉兩得?

http://www.pythonlearn.com/index.php





這個視訊教學最棒的地方是, 左邊是簡報, 右邊是講解的老師, 他會一邊說明一邊用手寫筆在簡報上畫線寫字, 就跟實際在課堂上課那樣, 而且還更清楚. 網路上真的有蠻多寶貝, 這麼棒的教學何必花大錢趕去補習班學呢? 我要找零碎時間慢慢把他看一遍.

2019年9月29日 星期日

2019 年第 39 周記事 : 小貓收養記

昨晚在鄉下車庫操作洗衣機時, 好像聽到很微弱的貓咪叫聲, 但四處看了一下並無所獲. 今早起來卻聽到車庫傳來小貓的咪咪叫聲, 出去車庫一看, 竟然是一隻未開眼的小貓, 毛色是黃白色的, 肯定是寓居我家的那隻野貓生的, 據說母貓若聞到小貓身上有人味會不敢回來, 所以我就不管牠, 等母貓回來把牠叨走.




一直到傍晚我要回高雄前, 母貓都沒出現, 看來小貓變棄嬰了, 牠一整天在車庫到處爬, 一邊爬一邊叫, 肯定是肚子餓, 這樣下去可能會餓死. 我傳 Line 給菁菁, 她說她要養, 所以就把牠放進糖果紙盒帶回高雄, 菁菁好像把牠當人類嬰兒般照顧 :




本周除了繼續學習 Django 外, 就是看韓劇陽光先生, 已到 20 集, 快到尾聲了. 日俄即將一戰, 美國束手旁觀, 凱爾怕尤金感情用事便利用職權命令尤金返國, 高愛信知道後去跟尤金說她想隨他去美國, 其實是想在途經日本時跳船, 以便營救被武臣會綁架到日本的李正文大人. 雖然尤金說破了愛信的意圖, 他還是願意成全, 拿來偽造的結婚證書, 為愛信戴上戒指, 以夫妻身分前往日本. 來到東京後, 尤金找到了愛信父母當年拍攝結婚照的攝像館, 拍下了他們的結婚照 :





在港口送別尤金後, 愛信剛好被武臣會的浪人認出而追殺, 看到這一幕的尤金為了解救愛信沒有上船, 兩人逃到美國駐日公使館前用最後一顆子彈朝使館窗戶開槍, 頓時使館士兵立刻蜂擁而出, 他們阻擋得了武臣會浪人的追殺嗎?

我查維基並沒有武臣會, 指的應該是黑龍會才對, 這是以前日本浪人組成的秘密情報網, 首腦是日本慈善家頭山滿, 日本會走上軍國主義道路與黑龍會的泛亞洲思想脫不了干係, 甚至可說, 沒有黑龍會就沒有同盟會, 辛亥革命可能也不會成功, 因為他們認為清朝的存在阻礙了他們形塑大東亞新秩序的進程, 如果鼓動革命鬧個天翻地覆更容易亂中取勝. 黑龍會名稱來自黑龍江, 因為他們最早的目標就是要把俄國人的勢力趕回黑龍江以北.


2019-10-14 補記 :

這隻小貓在送到水某同事家找她們家剛生產的美國短毛貓當乳母, 剛開始狀況還不錯, 會找母貓的乳頭吸允, 但幾天後的早上發現一動也不動, 已經無生命現象了. 有可能他一出生就有缺陷, 所以母貓才棄養吧.

2019年9月28日 星期六

田寮一線天健行

上週老張約今天去田寮走一走, 原想應該沒甚麼事就答應了, 昨天才想到這個周末二哥要去走馬踏瀨參加迎新, 估計來回一小時車程, 必須 06:30 出發才能在 07:30 到老張家. 所幸是騎機車去比較快, 若是開車還要回家換機車肯定來不及.

我對田寮很陌生, 因為沒有親朋好友住在那裏. 這次是要去大崗山的一線天健行, 成員就大師夫婦, 老張, 我, 小琪, 還有大師的尊翁六人. 車停在朝元寺前, 從寺旁的小路往上走, 本想走到減肥洞, 無奈前陣子雨後雜草叢生, 就往回走到一線天, 然後攀了一小段階梯到觀景亭喝茶聊天.





回到山下發現還是只有我們兩台車, 這裡似乎不太有人來玩啊! 從山上觀景亭往下看, 田寮似乎都是丘陵地, 住戶並不多. 中午到大師家叨擾, 由持有中餐丙級的小琪下廚, 大師夫人又採購菜餚準備了豐盛的午餐. 由於對大師尊翁打的自家燕巢芭樂汁讚不絕口, 回家時又獲贈一瓶帶回, 呵呵, 真是一個快樂的周末.

市圖還書 15 本

今天清理了一批借書 :
  1. 微積分
  2. 網頁應用程式設計 :使用Node和Express (左新)
  3. 雙動能投資 :高報酬低風險策略
  4. 從法人手中賺到錢 :全台第一本類股籌碼分析全攻略 (陽明)
  5. 多空我都能賺 :必學7堂翻倍投資術
  6. 不好意思,我贏了 =Sorry,but I win! :掌握總經,點石成金投資法!
  7. 為你自己學Git (新興)
  8. It's django :用Python迅速打造Web應用!
  9. TensorFlow之外的好選擇
  10. 初入門紫微斗數
  11. 紫微斗數算你好工作 :科系、就業、職場通通選對!
  12. 八字氣數基礎講義
  13. 四柱推命奧秘傳(上) (中崙)
  14. 四柱推命奧秘傳(下)
  15. HTML5 & CSS3辭典 .第2版
希望能在年底前全部清完.

2019年9月26日 星期四

Python 學習筆記 : Django 2 測試 (二) : 模板基礎與靜態檔案

二月底做了初步的 Django 2.0 測試後, 準備下一步是建立包含 jQuery UI 的模板, 但卻發現以前學 jQuery UI 時趕時間沒有把筆記寫好寫滿, 就花了一段時間補寫, 接著四月又開始忙授課, 然後是陪二哥大學申請入學面試 .... 直到最近二哥上大學了, 我才又重新回到 Django 軌道 (還好婉拒了 12 月的授課).

經過了大半年, Django 版本也從 2.1.7 升到 2.2.5 所以先更新版本吧! 更新 Django 可在 pip install 後加上 -U 參數 :

pip3 install django -U

本篇測試參考了下面幾本書中的範例加以改寫 :
  1. It's django - 用 Python 迅速打造 Web 應用 (楊孟穎, 袁克倫, 碁峰)
  2. Python 新手使用 django 架站的 16 堂課 (何敏煌, 博碩)
  3. Python 新手使用 django 架站技術實作 (何敏煌, 林亮昀, 博碩)
  4. Python 網頁程式交易 App 實作 (林萍珍, 博碩)
  5. Beginning Django (Daniel Rubio, Apress)
  6. Django 2 Web Development Cookbook 3rd Edition(Jake Kronika, Packt)
  7. 一次搞定所有 Python Web 框架開發百科全書 (佳魁, 劉長龍) 
  8. 科班出身的MVC網頁開發 : 使用Python + Django (佳魁, 王友釗)
Django 2 官方教學文件參考 :

https://docs.djangoproject.com/en/2.2/

Python 學習筆記索引參考 :

Python 學習筆記索引

本系列之前的筆記參考 :

Python 學習筆記 : Django 2 測試 (一) : 請求與回應處理

線上教材參考 :

The Complete Guide To Install Django And Configure It
2019 iT 邦幫忙鐵人賽 : From Django 1.11 to Django 2.1 系列
Django 2 By Example 全书翻译、踩坑及教程

做完 Django 的請求與回應處理測試後, 本篇繼續測試 Django 2 的模版與靜態檔案功能. 由前一篇測試中可以發現, 即使可以用多行字串表示法 (三個連續引號對) 提高 views.py 中的 HTML 代碼可讀性, 但是在 views.py 的處理函數中直接用 HttpResponse 輸出網頁 HTML 字串的方式會使網頁內容與控制邏輯混在一起, 不符合 Django 鬆耦合 (llosely-coupled) 精神, 也不利於前後端之分工, 增加維護上的困難. 解決辦法就是利用 Django 的模板 (Template) 功能.

所謂的模板基本上就是一個嵌入變數與控制標籤的網頁檔, 利用 views.py 傳送變數值給模板引擎, 它會將變數以所傳送的值替換以便生成最終的 HTML 網頁, 此過程稱為渲染或繪製 (render), 如下圖所示 :




在建立網頁模板之前有一個準備工作要做, 就是建立存放模板檔案與靜態檔案的目錄, 這些都是整個網頁專案要用的, 所以要建在上層專案目錄底下.


一. 基本模板用法 : 

基本用法是指從視圖處理函數 views.py 中傳遞字典變數到模板網頁中. 首先須在專案中建立存放模板網頁與靜態檔案的目錄.

1. 新增 templates 與 static 目錄 :

在 Django 中, 所有副檔名為 htm 或 html 之網頁模板網頁模板會集中放在上層專案目錄下的一個專用子目錄中 (名稱可自訂, 例如 /templates). 另外, 網頁中可能會用到的圖檔, CSS 樣式檔, 以及 Javascript 程式等靜態檔案, 這些檔案也是集中放在上層專案目錄下的一個專用子目錄中 (名稱可自訂, 例如 /static, 這是設定檔 settings.py 中的預設值), 也可以在其底下建立三個子目錄以分類存放圖檔, 樣式檔, 以及 Javascript 程式檔. 這些目錄在新增專案時不會自動產生, 需手動建立 :

D:\Python\test\mysite>mkdir templates
D:\Python\test\mysite>mkdir static
D:\Python\test\mysite>cd static
D:\Python\test\mysite>mkdir images
D:\Python\test\mysite>mkdir css
D:\Python\test\mysite>mkdir js

這樣整個專案目錄結構如下 :


mysite (上層專案目錄)
     |____ manage.py
     |____ mysite  (下層專案目錄)
     |              |____ __init__.py
     |              |____ settings.py
     |              |____ urls.py
     |              |____ views.py  (自行添加)
     |              |____ wsgi.py
     |____ templates (網頁模板目錄)
     |____ static (靜態檔案目錄)
                    |____ images
                    |____ css
                    |____ js


這樣便可將模板網頁存放於 templates 子目錄下, 專案要用到的靜態檔案則分別置於 images, css, 與 js 下, 但在使用模版與靜態檔案之前還必須在專案設定檔 settings.py 中設定路徑, 這樣 Django 才能存取到這些手動建立的目錄下的檔案.


2. 編輯 settings.py 中的模版引擎與目錄設定 :

(1). 設定網頁模板路徑 :

開啟下層專案目錄下的 settings.py 檔, 其中的 TEMPLATES 變數的 DIRS 屬性就是用來設定模板路徑的, 需填入模版目錄的絕對路徑, 可用設定檔最前面建立的專案根目錄變數 BASE_DIR 與上面所建立的模板目錄名稱 'templates' 串接組合而成 :

os.path.join(BASE_DIR, 'templates')

此指令會將根目錄路徑與 templates 結合為模板目錄的絕對路徑 :

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中的 BACKEND 屬性用來設定所使用模版引擎, 預設為 DjangoTemplates, 也可以改用第三方引擎例如 jinja2, 這樣 BACKEND 就要改為 django.template.backends.jinja2.jinja2. 預設的模版引擎其實就很好用了.

經過上面的設定後便可以編輯網頁模板檔案了 (詳下).


(2). 設定靜態檔案路徑 :

開啟 settings.py 可發現在建立專案時, 此設定檔最底下已經預設靜態目錄 STATIC_URL 為 '/static', 在其底下再加入一個靜態目錄絕對位址變數  STATICFILES_DIRS :

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]

經過以上設定後, 就可以在網頁模板中使用靜態檔案了, 使用前須先宣告要用模板標籤載入靜態檔案, 語法如下 :

{% load static %}   

此 static 為內建之 django.contrib.staticfiles 模組所提供之指令, 在專案設定檔 settings.py 的 INSTALLED_APP 串列變數中已定義, 有些書上使用 {% load static from staticfiles %} 或 {% load staticfiles %} 都是較舊的用法, 現在應改用 {% load static %}, 參考 :

What is the difference between {% load staticfiles %} and {% load static %}

注意, load 指令只要使用一次即可, 通常放在網頁的 head 元素內. 載入宣告後才可以用模板標籤指令 static 載入靜態檔案, 語法如下 :

樣式檔 : <link rel="stylesheet" href="{% static "css/style.css" %}">
程式檔 : <script src="{% static "js/myscript.css" %}"></script>
圖片檔 : <img src="{% static "images/logo.jpg" %}">

注意, 模板標籤內的引號不影響外面屬性的引號, 不須單雙交替. 另外在 HTML 部分, link 元素使用 href 屬性連結樣式檔; 而 script 元素則用 src 屬性載入程式檔.


3. 編輯模版網頁與 views.py 處理函數 : 

經過以上設定就可以使用模板語法來編輯模板網頁了. 模板網頁中可以嵌入如下三種元素 : 變數, 標籤, 以及註解 :


 模板語法元素 說明
 {{ 變數 }} 來自 views.py 的變數用來直接替換該位置內容
 {% 標籤 %} 標籤用來控制資料顯示邏輯
 {# 註解 #} 註解會被模板引擎忽略


其中標籤語法操作的對象其實也是從 views.py 處理函數傳遞過來的變數, 這些變數是透過呼叫捷徑函數 django.shortcuts.render() 傳遞過來的, 在 views.py 中使用 render() 繪製網頁前須先匯入 :

from django.shortcuts import render

這樣便可直接呼叫 render() 函數了, 其參數有三 :

render(request, "template.htm", dict)

第一個參數 request 是包裝了客戶端請求訊息的 HttpRequest 物件; 第二個參數是要繪製的模板網頁檔案名稱 (放在 /templates 底下), 第三個參數是要傳遞給模板網頁的變數組成之字典, 例如 :

#views.py
from django.shortcuts import render
def helloworld(request):
    dict1={‘name’: ‘Tony’, ‘score’: 80}
    return render(request, ‘hello.htm’, dict1)

然後在模板網頁中直接用字典的鍵 (例如 {{ name }} 與 {{ score }}) 就可以存取所傳送的變數了, 如下圖所示 : 




如果要傳遞的變數很多, 自行組成字典較麻煩, 這時可以呼叫 locals() 函數, 它會傳回所有區域變數組成的字典, 例如 {‘name’: ‘Tony’, ‘score’: 80}, 可直接作為 render() 的第三參數, 但要注意, locals() 函數只會傳回所有區域變數組成的字典, 全域變數不包括在內. 例如 :

#views.py
from django.shortcuts import render
def helloworld(request):
    name=Tony

    score=80

    return render(request, ‘hello.htm’, locals())

此處兩個區域變數 name 與 score 都會被 locals() 組成字典傳遞給模板. 在模板網頁中同樣是直接用字典的鍵 (例如 {{ name }} 與 {{ score }}) 存取所傳送的變數.




注意, 因為 locals() 會將全部區域變數打包成字典, 因此如果區域變數本身就是字典, 物件, 串列或元組等容器類型變數, 則在模板網頁中, 必須使用物件的點運算子來存取存取元素變數. 模板引擎會以下列順序來查找所傳遞的變數 , 一找到便停止 :


 查找優先順序 相對於 Python 說明
 1. 字典鍵 {{ dict.key }} dict["key"] 字典鍵亦可用整數, 例如 {{ dict.0 }}
 2. 物件屬性 {{ obj.attr }} obj.attr attr 為物件 obj 之屬性
 3. 物件方法 {{ obj.method }} obj.method() method() 為物件 obj 之方法
 4. 串列或元組元素 {{ list.0 }} list.0 list 為串列


如果是字典變數, 則點運算子後面為 key (文字數字均可); 如果是物件變數, 點運算子後面是其屬性; 如果是串列或元組, 則點運算子後面是其索引. 下圖為字典變數的例子 :




注意, 這裡 locals() 傳遞的變數是 {'dict1': {'name': 'Tony', 'score': 80}}, 因此藥用 {{ dict1.name }} 存取 name 變數, 而非 {{ name }}.

前一篇的 Hello World 測試可以改用模板來做, 步驟如下 :

(1). 編輯模板網頁檔 :

此例需要兩個網頁檔 : hello.htm 與 home.htm (均存放於 /templates 底下), 前者顯示顯示 "Hello", 以及從 views.py 傳遞過來的人名與時間訊息, 底下有一個回首頁的連結圖檔. 關於 HTML5 網頁參考 :

網頁技術速學筆記 (一) : HTML 基礎

<--! hello.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
  {% load staticfiles %}
  <link rel="stylesheet" href="{% static "css/hello.css" %}">
  <script src="{% static "js/hello.js" %}"></script>
</head>
<body>
  <p class="msg">Hello, {{ name }}, 現在的時間是 {{ now }}</p>
  <a href="/">
    <img src="{% static "images/home.jpg" %}" alt="回首頁">
  </a>
</body>
</html>

此網頁用到三種靜態檔案資源 : 樣式檔 (hello.css), 程式檔 (hello.js), 以及圖檔 (home.jpg), 須分別存放於 /static/css, /static/js, 與 /static/images 下.

另外編輯 home.htm 首頁檔 :

<--! home.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <b>歡迎來到我的首頁!</b>
</body>
</html>

此兩檔案要放在 /templates 底下.


(2). 編輯樣式檔 hello.css :

/* hello.css  */
.msg {
  font-weight: bold;
  text-shadow: navy 3px 3px 3px;
  }

此檔案要放在 /static/css 底下.


(3). 編輯程式檔 hello.js :

其功能就是彈出一個訊息框而已 :

/* hello.js */
alert("Hello!");

此檔案要放在 /static/js 底下.


(4). 編輯 views.py 檔 :

#views.py
from django.shortcuts import render
from datetime import datetime

def hello(request, name):
    now=datetime.now()
    dict1={'name': name, 'now': now}
    return render(request, 'hello.htm', dict1)

def home(request):
    return render(request, 'home.htm', {})

視圖處理程式定義了兩個函數 hello() 與 home(). 其中 hello() 會傳遞名字與時間變數到模板網頁 hello.htm; 而 home() 則傳遞空字典到模板網頁 home.htm, 不需要傳變數第三參數就傳空字典.


(5). 編輯 urls.py 檔 : 

#urls.py
from django.contrib import admin
from django.urls import path
from mysite.views import home, hello

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/<name>/', hello),
    path('', home),
]

此檔案處理 URL 路由, 第一個 path 是建立專案時預設的, 用來管理網站後台, 另兩個 path 則是將專案 URL 交由 views.py 中的函數處理, 所以開頭必須從 mysite.views 模組匯入 hello 與 home 這個函數. 也可以用 import mysite.views 匯入整個 views.py 模組, 但這樣 path() 的第二參數 (處理函數) 就要寫成 views.hello 與 views.home 了.

在上層專案目錄下啟動開發伺服器 :

python manage.py runserver 

然後於瀏覽器網址列輸入 http://127.0.0.1:8000/hello/Tony/ 結果如下 :




網頁載入後首先會執行 hello.js, 這會彈出一個顯示 'Hello' 的訊息窗, 按底下的 Home 圖檔會回到首頁.

D:\Python\test\mysite>python manage.py runserver     
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
September 26, 2019 - 16:37:36
Django version 2.2.5, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[26/Sep/2019 16:37:41] "GET /hello/Tony/ HTTP/1.1" 200 367


二. 進階模板用法 :

進階用法是指使用模板標籤指令來控制網頁的顯示方式, 主要包含 if 判斷 (分支) 標籤與 for 迴圈標籤, 語法除了結尾多了 endif 與 endfor 外, 與 Python 是一樣的. 另外模板引擎還提供過濾器 (filter) 可對要輸出的變數進行格式化或特定運算.


1. if 判斷 (分支) 標籤 : 

利用變數運算式的真假值控制輸出何種網頁 :


 單一分支 雙重分支 多重分支
 {% if 條件式 %}
 輸出網頁
 (% endif %}
 {% if 條件式 %}
 輸出網頁1
 {% else %}
 輸出網頁2
 (% endif %}
 {% if 條件式 %}
 輸出網頁1
 {% elif %}
 輸出網頁2
 {% elif %}
 輸出網頁3
 {% else %}
 輸出網頁4
 (% endif %}


注意, endif 的 end 與 if 之間沒有空格, elif 的 el 與 if 之間也是, 若有空格會造成語法錯誤.


2. for 迴圈標籤 :

模板語言的迴圈包括 for 與 empty 標籤, 以及 forloop 物件所提供的 7 種屬性. for 標籤操作的變數是由 views.py 傳遞過來的可迭代變數, 例如串列或元組, 模板的迴圈與 Python 的 for~in 迴圈類似, 但需要 endfor 結尾, 而且不可使用 break 與 continue :


(1). for 迴圈語法 :

{% for 變數 in 可迭代變數 %}
網頁1
{% empty %}
網頁2 (迴圈為空時輸出)
{% endfor %}

注意, empty 標籤必須放在 for 迴圈的最後面 (即 endfor 標籤前面), 當迴圈為空 (即可迭代變數元素個數為 0) 時, 網頁1將不會輸出, 而是輸出夾在 empty 與 endfor 標籤中間的網頁2.

如果要做倒序迭代, 只要在可迭代變數後面加上 reversed 即可 :

{% for 變數 in 可迭代變數 reversed %}
網頁1
{% empty %}
網頁2 (迴圈為空時輸出)
{% endfor %}


(2). forloop 變數 :

為了方便控制迴圈與顯示內容, 模板引擎還提供了 forloop 物件變數, 它的 7 個屬性如下表所示 :


 迴圈變數 forloop 的屬性 說明
 forloop.counter 迴圈計數器 (1 起始)
 forloop.counter0 迴圈計數器 (0 起始)
 forloop.revcounter 倒數之迴圈計數器 (由總圈數遞減至 1)
 forloop.revcounter0 倒數之迴圈計數器 (由總圈數遞減至 0)
 forloop.first 是否為第一個迴圈 (True/False)
 forloop.last 是否為最後一個迴圈 (True/False)
 forloop.parentloop 上一層迴圈的 forloop 變數


(3). 過濾器 (filter) :

模板引擎提供了一些過濾器 (其實就是模板的內建函數), 以便在輸出變數前對變數內容或格式做修正或運算, 使最終能輸出符合需要之結果. 用法是在變數後面以管線符號 "|" 串接過濾器, 可串接多個過濾器, 模板引擎會依照順序處理 :

{{ 變數 | 過濾器1 | 過濾器2  | 過濾器3 | ... }} 

常用的過濾器如下表 :


 常用模板過濾器 說明 範例
 add 加上一個數值或做字串串接 {{ var | add:"2" }} 
 addslashes 跳脫特殊字元 (前面加上倒斜線 \) {{ var | addslashes }} 
 capfirst 將字串變數的首字元轉為大寫 {{ var | capfirst }}
 center  將字串變數冠上指定空格後置中 {{ var | center: "5" }}
 cut 從字串變數中刪除指定子字串 {{ var | cut: " " }} 刪除空白字元
 date 將 datetime 變數以指定格式顯示 {{ var | date: "D d M Y" }}
 default 當變數為空字串時輸出預設字串 {{ var | default: "預設值" }}
 dictsort 將串列變數中的字典依指定鍵遞增排序 {{ var | dictsort: "鍵" }}
 dictsortreversed 將串列變數中的字典依指定鍵遞減排序 {{ var | dictsortreversed: "鍵" }}
 divisibleby 測試數值變數是否可被指定數整除 {{ var | divisibleby: "7" }} 
 escape 跳脫字串變數內的 HTML 標籤 {{ var | escape }}
 filesizeformat 以 KB/MB 等單位顯示檔案大小 {{ var | fileformat }}
 first 取出串列變數中的第一個元素 {{ var | first }}
 join 將串列變數中的元素以指定字串串接 {{ var | join: "," }} 
 last 取出串列變數中的最後一個元素 {{ var | last }} 
 length 傳回串列變數的元素個數 (長度) {{ var | length }} 
 length_is 測試變數的長度是否為指定之長度 {{ var | length_is: "5" }} 
 linebreaks 將字串中的 \n 轉成 HTML 之 br 與 p {{ var | linebreaks }} 
 linebreaksbr 將字串中的 \n 轉成 HTML 之 br {{ var | linebreaksbr }} 
 linenumbers 將顯示之文字加上行號 {{ var | linenumbers }} 
 ljust 將字串變數冠上指定空格後置左 {{ var | ljust: "5" }}
 lower 將字串變數轉成小寫 {{ var | lower }}
 make_list 將字串拆成字元串列 {{ var | make_list }}
 random 隨機取出串列中的一個元素 {{ var | random }}
 rjust 將字串變數冠上指定空格後置右 {{ var | rjust: "5" }} 
 safe 以 HTML 格式讀取字串 (不須跳脫) {{ var | safe }} 
 slice 取出字串中的指定切片 {{ var | slice:":2" }} 
 slugify 將字串中的空白以 dash '-' 取代 {{ var | slugify }} 
 stringformat 以科學表示法顯示數字 {{ var | stringformat: "E" }} 
 striptags 移除字串中的 HTML 標籤 {{ var | striptags }} 
 title 將字串中每個字的第一字元大寫 {{ var | title }} 
 truncatechars 將字串中超出指定長度部分以 ... 取代 {{ var | truncatechars:"5" }} 
 upper 將字串轉成大寫 {{ var | upper }} 
 wordcount 傳回字串中的字數 {{ var | wordcount }} 
 yesno 依變數值為 True/False/None 轉成指定值 {{ var | yesno:"是, 否, 取消" }} 


有些過濾器有攜帶參數, 注意, 過濾器與參數之間用冒號緊密串接, 中間不可有空格, 否則會出現錯誤.

完整的過濾器用法參考 :

Built-in template tags and filters

以下為綜合分支與迴圈的測試範例, 使用 HTML 表格來彙整測試結果, 關於 HTML 表格用法參考 :

網頁技術速學筆記 (一) : HTML 基礎

模板網頁共有 6 個, 以首頁 home.htm 列舉到各項測試之超連結 :

<!-- 1. home.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <h3>模板測試 2</h3>
  <ul>
    <li><a href="/if_test_1">分支測試 1 (if)</a></li>
    <li><a href="/if_test_2">分支測試 2 (if elif else)</a></li>
    <li><a href="/loop_test_1">迴圈測試 1 (有資料)</a></li>
    <li><a href="/loop_test_2">迴圈測試 2 (無資料)</a></li>
    <li><a href="/filter_test_1">過濾器測試 1</a></li>
    <li><a href="/filter_test_2">過濾器測試 2</a></li>
    <li><a href="/filter_test_3">過濾器測試 3</a></li>
  </ul>
</body>
</html>

第一個分支測試模板網頁 if_test_1.htm :

<!-- 1. if_test_1.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>分支測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
{% for stock in stocks %}
{{ stock }}
{% if not forloop.last %}
,
{% endif %}
{% endfor %}
</body>
</html>

第二個分支測試模板網頁 if_test_2.htm :

<!-- 2. if_test_2.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>分支測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
{% if hour > 18 %}
晚安
{% elif hour < 10 %}
早安
{% else %}
午安
{% endif %}
</body>
</html>

迴圈測試模板網頁 loop_test_1.htm :

<!-- 3. loop_test_1.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>for 迴圈</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="5">
    <caption>股票列表</caption>
    <tr>
      <th>No.</th>
      <th>股號</th>
      <th>公司</th>
      <th>分類</th>
    </tr>
    {% for stock in stocks %}
    <tr>
      <td>{{ forloop.counter }}</td>
      <td>{{ stock.id }}</td>
      <td>{{ stock.name }}</td>
      <td>{{ stock.category }}</td>
    </tr>
    {% empty %}
    <tr>
      <td colspan=4>無股票資料</td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

第一個過濾器測試模板網頁 filter_test_1.htm :

<!-- 4. filter_test_1.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>過濾器測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="3">
    <caption>過濾器測試 1</caption>
    <tr>
      <th>變數</th>
      <th>過濾器</th>
      <th>輸出</th>
    </tr>
    <tr>
      <td>i1=100</td>
      <td>i1|add:"200"</td>
      <td>{{ i1|add:"200"}}</td>
    </tr>
    <tr>
      <td>i2="100"</td>
      <td>i2|add:"200"</td>
      <td>{{ i2|add:"200"}}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|add:"200"</td>
      <td>{{ s1|add:"200" }}</td>
    </tr>
    <tr>
      <td>s2="I'm Tony."</td>
      <td>s2|addslashes</td>
      <td>{{ s2|addslashes }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|capfirst</td>
      <td>{{ s3|capfirst }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|truncatechars:"6"</td>
      <td>{{ s3|truncatechars:"6" }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|wordcount</td>
      <td>{{ s3|wordcount }}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|center:"5"</td>
      <td>{{ s1|center:"5" }}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|ljust:"5"</td>
      <td>{{ s1|ljust:"5" }}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|rjust:"5"</td>
      <td>{{ s1|rjust:"5" }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|cut:" "</td>
      <td>{{ s3|cut:" " }}</td>
    </tr>
    <tr>
      <td>now=datetime.now()</td>
      <td>now|date:"Y-m-d"</td>
      <td>{{ now|date:"Y-m-d" }}</td>
    </tr>
    <tr>
      <td>s4=""</td>
      <td>s4|default:"空字串"</td>
      <td>{{ s4|default:"空字串" }}</td>
    </tr>
    <tr>
      <td>d1=[{"id":"2330"},{"id":"2412"}]</td>
      <td>d1|dictsort:"id"</td>
      <td>{{ d1|dictsort:"id" }}</td>
    </tr>
    <tr>
      <td>d1=[{"id":"2330"},{"id":"2412"}]</td>
      <td>d1|dictsortreversed:"id"</td>
      <td>{{ d1|dictsortreversed:"id" }}</td>
    </tr>
  </table>
</body>
</html>

第二個過濾器測試模板網頁 filter_test_2.htm :

<!-- 5. filter_test_2.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>過濾器測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="3">
    <caption><b>過濾器測試 2</b></caption>
    <tr>
      <th>變數</th>
      <th>過濾器</th>
      <th>輸出</th>
    </tr>
    <tr>
      <td>i1=14</td>
      <td>i1|divisibleby:"7"</td>
      <td>{{ i1|divisibleby:"7" }}</td>
    </tr>
    <tr>
      <td>i2=100</td>
      <td>i2|divisibleby:"7"</td>
      <td>{{ i2|divisibleby:"7" }}</td>
    </tr>
    <tr>
      <td>i3=1024</td>
      <td>i3|filesizeformat</td>
      <td>{{ i3|filesizeformat }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|first</td>
      <td>{{ list1|first }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|last</td>
      <td>{{ list1|last }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|join:"/"</td>
      <td>{{ list1|join:"/" }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|slice:":2"</td>
      <td>{{ list1|slice:":2" }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|length</td>
      <td>{{ list1|length }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|length_is:"4"</td>
      <td>{{ list1|length_is:"4" }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|random</td>
      <td>{{ list1|random }}</td>
    </tr>
    <tr>
      <td>b1=True</td>
      <td>b1|yesno:"是, 否, 取消"</td>
      <td>{{ b1|yesno:"是, 否, 取消" }}</td>
    </tr>
    <tr>
      <td>b2=False</td>
      <td>b2|yesno:"是, 否, 取消"</td>
      <td>{{ b2|yesno:"是, 否, 取消" }}</td>
    </tr>
    <tr>
      <td>n1=None</td>
      <td>n1|yesno:"是, 否, 取消"</td>
      <td>{{ n1|yesno:"是, 否, 取消" }}</td>
    </tr>
  </table>
</body>
</html>

第三個過濾器測試模板網頁 filter_test_3.htm :

<!-- 6. filter_test_3.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>過濾器測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="3">
    <caption><b>過濾器測試 3</b></caption>
    <tr>
      <th>變數</th>
      <th>過濾器</th>
      <th>輸出</th>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|linebreaks</td>
      <td>{{ s1|linebreaks }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|linebreaksbr</td>
      <td>{{ s1|linebreaksbr }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|linenumbers</td>
      <td>{{ s1|linenumbers }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|lower</td>
      <td>{{ s1|lower }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|upper</td>
      <td>{{ s1|upper }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|make_list</td>
      <td>{{ s1|make_list }}</td>
    </tr>
    <tr>
      <td>s2="<b>Hello</b>"</td>
      <td>s2|safe</td>
      <td>{{ s2|safe }}</td>
    </tr>
    <tr>
      <td>s2="<b>Hello</b>"</td>
      <td>s2</td>
      <td>{{ s2 }}</td>
    </tr> 
    <tr>
      <td>s3="hello world"</td>
      <td>s3|title</td>
      <td>{{ s3|title }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|slugify</td>
      <td>{{ s3|slugify }}</td>
    </tr>
    <tr>
      <td>f1=0.00047108</td>
      <td>f1|stringformat:"e"</td>
      <td>{{ f1|stringformat:"e" }}</td>
    </tr>
    <tr>
      <td>s2="<b>Hello</b>"</td>
      <td>s2|striptags</td>
      <td>{{ s2|striptags }}</td>
    </tr>
  </table>
</body>
</html>

處理 URL 之路由檔 urls.py 內容如下 :

#urls.py
from django.contrib import admin
from django.urls import path
import mysite.views as views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('if_test_1/', views.if_test_1),
    path('if_test_2/', views.if_test_2),
    path('loop_test_1/', views.loop_test_1),
    path('loop_test_2/', views.loop_test_2),
    path('filter_test_1/', views.filter_test_1),
    path('filter_test_2/', views.filter_test_2),
    path('filter_test_3/', views.filter_test_3),
    path('', views.home),
]

視圖處理函數 views.py 內容如下 :

#views.py
from django.shortcuts import render
from datetime import datetime

def if_test_1(request):
    stocks={"台積電","中華電","統一超"}
    return render(request, 'if_test_1.htm', locals())

def if_test_2(request):
    now=datetime.now()
    hour=now.timetuple().tm_hour
    return render(request, 'if_test_2.htm', locals())

def loop_test_1(request):
    stock1={"id":"2330", "name":"台積電", "category":"半導體"}
    stock2={"id":"2412", "name":"中華電", "category":"通信網路"}
    stock3={"id":"2912", "name":"統一超", "category":"貿易百貨"}
    stocks=[stock1, stock2, stock3]
    return render(request, 'loop_test.htm', locals())

def loop_test_2(request):
    stocks=[]
    return render(request, 'loop_test.htm', locals())

def filter_test_1(request):
    i1=100
    i2="100"
    s1="A"
    s2=s2="I'm Tony."
    s3="hello world"
    s4=""
    d1=[{"id":"2330"},{"id":"2412"}]
    now=datetime.now()
    return render(request, 'filter_test_1.htm', locals())

def filter_test_2(request):
    i1=14
    i2=100
    s1="<b>Hello</b>"
    i3=1024
    list1=['Python','Java','C++','Perl']
    b1=True
    b2=False
    n1=None
    return render(request, 'filter_test_2.htm', locals())

def filter_test_3(request):
    s1="Hello\nWorld"
    s2="<b>Hello</b>"
    s3="hello world"
    f1=0.00047108
    return render(request, 'filter_test_3.htm', locals())

def home(request):
    return render(request, 'home.htm', {})

以 python manage.py runserver 指令啟動開發伺服器後, 在伺服器網址列輸入 localhost:8000 拜訪首頁如下 :




點首頁中的超連結即可測試各個功能. 在分支測試 1 中, 傳遞到模板的串列變數為 :

stocks={"台積電","中華電","統一超"}

在模板中使用了 for 迴圈來取出串列元素, 並使用 if 標籤來判斷是否要在元素後面加逗號, 在最後一個元素時 forloop.last 為真, 因此不會加逗號, 網頁輸出為 :

台積電 , 統一超 , 中華電

在分支測試 2 中, 傳送給模板的是目前的時數 hour (0~23) :

now=datetime.now()
hour=now.timetuple().tm_hour

在模板網頁中利用 if~elif~else 分支來判斷要輸出晚安, 早安, 還是午安.

在迴圈測試 1, 傳遞給模板網頁的是一個字典串列 stocks :

stock1={"id":"2330", "name":"台積電", "category":"半導體"}
stock2={"id":"2412", "name":"中華電", "category":"通信網路"}
stock3={"id":"2912", "name":"統一超", "category":"貿易百貨"}
stocks=[stock1, stock2, stock3]

在模板中用 for 迴圈與 forloop 變數於表格中顯示股票資訊. 而在迴圈測試 2 則是傳遞空串列變數 stocks 給相同的模板 loop_test.htm, 利用 empty 標籤來顯示無資料訊息, 結果如下 :




過濾器部分因太多, 所以分成三部分測試, 結果如下 :






關於過濾器摘要如下 :
  1. wordcount 是計算 "字" 數而非 "字元" 數, 亦即以空格為單位做區隔, 例如 "您好嗎?" 的 wordcount 是 1; 而 "您 好 嗎 ?" 的 wordcount 是 4. 
  2. 若直接輸出變數 {{ var }}, 模板引擎會自動將 HTML 標籤的角括號轉成 > 與 <, 但可以用 safe 過濾器阻止自動轉換. 
  3. linebreaksbr 會將字串變數中的跳行字元 "\n" 改為 "<br>", 例如 "Hello\nWorld" 會被轉成 "Hello<br>World". 而 linebreaks 則除了做此轉換外還會在外面套上 p 標籤, 例如 "Hello\nWorld" 會被轉成 "<p>Hello<br>World</p>".
  4. date 過濾器格式與 PHP 的極類似, 如果要製作 "2019-10-03 08:43:12" 這樣的輸出, 則其格式為 date:"Y-m-d H:i:s", 參考 :
    https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#date
  5. slice 的切片用法與 Python 字串或串列切片的用法一樣. 
十年前玩 GAE 時用的也是幾乎一樣的模板語言, 雖然 GAE 當時只支援 Python 2.x 的 Django 0.9 版, 但模板部分那時就很成熟了, 因此也沒甚麼變, Tsung's blog 有整理一篇很不錯的模板語言用法摘要, 參考 :

Google App Engine 初學(8) - 共用 Template (Layout)

兩篇機器學習應用於市場預測之論文

今天找到兩篇機器學習應用在市場預測的研究論文, 值得參考 :
  1. Application of Deep Learning to Algorithmic Trading
  2. Application of Machine Learning: Automated Trading Informed by Event Driven Data
第一篇出自史丹佛大學, 研究者實作了 LSTM 神經網路 (時間序列版的深度學習) 來預測英特爾 (NASDAQ: INTC) 公司的股價, 結果發現 LSTM 模型能更準確預測次日股價, 特別是在盤整的時候.

第二篇是麻省理工學院電機系的一篇碩士論文, 研究者以近十年的 S&P500 指數為對象, 比較了神經網路 (NN), 隨機森林 (Random Forest), 以及支持向量機 (SVM) 三種模型, 發現 SVM 模型有最高的夏普比 (Sharpe Ratio), 即每承受 1% 的波動風險, 可創造 3.5% 的報酬率.

這兩篇深度夠, 但廣度不夠, 因為研究結果只來自於個別標的的訓練結果, 如果能對其他標的重複相同研究, 相信能獲得較一般化的結論.

2019年9月23日 星期一

找回 ESP32 板子收納盒

這兩天發覺平常放在書袋上方的深紫色 ESP32 板子收納盒不見了, 昨晚找遍家裡, 還書袋, 椅子底下, 櫃子全都找過, 就是不見蹤影. 晚上打電話請爸到我書房瞧瞧, 也是沒有. 問說裡面有放甚麼寶貴的東西嗎? 我說就是一些電路板, 算算價值應該有 1500 元吧?

晚上去捷運站載二哥時, 想說先到車上找看看, 雖然早上有大致瞄了一下沒有, 但往裡面摸索很興奮地就在中間走道上尋回了這個紫色盒子, 哈哈哈, 實在太開心了 :




裡面有五塊板子, 真的幸好沒掉在路上. 雖說目前還沒時間回來玩 ESP32, 但若真掉了還得再花千把塊來買就很浪費了, 立馬在盒子上寫上姓名電話, 萬一掉了有心人還能找到失主.

2019年9月22日 星期日

2019 年第 38 周記事

時間過得真快, 輾轉明日就是秋分了, 氣溫也漸漸轉涼, 這幾天晚上睡覺都不開電扇, 還要穿長褲避免著涼了.

早上正準備要去市集時, 聽到有人在門外喊, 出去一看原來是國中同學阿錦來訪, 還帶來一台割草機, 我一看即認出是最近常打廣告的東林電動割草機, 帶來給我看的目的是分享他的創新發想-割草機器人, 他希望我能研究研究 (上回是野蓮收割機), 順便邀我 10 月上雲林參觀農機展. 傍晚打完籃球本想去跑山, 剛好已退休過著雲遊四海好日子的工專同學騎著帥氣的重機來訪, 天南地北一聊就過了六點, 他急忙要趁天黑前返回高雄也沒留下來吃飯. 這鄉居生活有客來訪真的非常難得.

本周陽光先生已看到第 17 集, 具東魅在糖果店前被人開黑槍, 在嚥氣前想到高愛信之前的講過她會毫不猶豫, 難道他認為殺他的是高愛信嗎? 不解一. 另外當鋪的兩位先生幫工藤陽花畫好母親畫像時, 陽花叫他們拿去燒掉, 因為已經不可能找到了, 是早已經死亡嗎? 李正文大人只是說已找到了, 卻沒說已死啊! 不解二. 接下來日本人會施加更多壓力逼迫朝鮮, 尤金的挑戰會越來越大了.





這是第 16 集中尤金與高愛信去釣魚的畫面, 因為高愛信問尤金說 love 從認識, 握手, 擁抱, 思念之後是甚麼? 尤金回答說 : 釣魚.

2019年9月21日 星期六

921 地震二十周年

今天是 921 地震 20 周年紀念, 時間過得好快, 20 年一晃就過去了. 記得那時姊姊才 8 個月大, 我還在中山大學外文系進修, 9 月 20 日晚上我正在熬夜寫英文作文作業, 看看快兩點了正要準備停筆, 發覺怎麼頭有點暈, 是熬夜太晚精神不繼嗎? 我馬上就意會到這是地震, 先呆坐在椅子上兩秒不知該怎麼辦, 這時越搖越大,  我衝去臥室叫水某, 抱起嬰兒床上的姊姊, 但還來不及跑到陽台, 長達 10 幾秒的天搖地動就停了. 我覺得搖這麼大不尋常, 可能還有餘震, 就背了書包抱著姊姊, 一家人走樓梯下到大樓中庭, 這時已聚集許多住戶了. 待了 20 分鐘覺得應該穩定了才上樓看新聞, 原來中部災情慘重.

今天也是母親的五周年忌日.

參加菁菁學校的家庭教育日

今天早上去菁菁學校參加家庭教育日 (就是班親會), 我主要是想認識一下班導, 她說學校樂儀隊老師希望菁菁能參加儀隊, 我馬上敲 Line 問, 勸說明年國慶要上台北表演, 結果她說 No, 因為怕曬黑, 真是傻眼. 會中老師詢問家長擔任家長會代表意願, 還好沒被點到, 我最怕開會了. 哈, 父女都有各自怕的東西, 真是傻眼.

中午回家順路去永茂, 在高醫門口等紅綠燈看到印尼外配在賣印尼便當, 以前路過都沒時間, 今天就下車特地看看她們印尼便當的菜色是甚麼, 大概以炸雞腿與雞翅居多, 我買了一個 50 元雞翅便當回去, 裡面一個雞翅, 一些小魚乾, 辣椒, 還有小黃瓜等等, 感覺印尼人口味偏好辣 :




以前照顧大阿姨的兩位印尼看護妹妹是我最早遇見的印尼人, 每個禮拜拿菜去都會見到, 第一位是家中有許多弟妹, 為了幫助父母維持家計來台灣工作; 第二位則是拋下年幼孩子給父母, 夫妻分赴大馬與台灣工作, 為了家庭經濟她們真的犧牲很多, 而且回教徒很善良, 我們應該將心比心善待他們. 台灣到處都看得到印尼人的身影, 如果沒有他們, 台灣的工程與長照就做不下去了.

翠華路的迷宮

昨天快下班時接到二哥電話, 說教科書發下來了, 紙箱很重, 要我去校門口接他. 以前去高雄大學我都是走博愛路轉崇德路到翠華路的台 17 線, 這回開車我就設定導航, 看看它會怎麼走. 結果看來是要走大中路, 但那裏路網好亂不熟, 所以到崇德路時還是照原路線走.

但是回程就麻煩了, 騎機車可以在大中路高架橋下走地下道, 但汽車不行, 我上回是要直接回鄉下, 所以直走翠華路到蓮池潭後轉回來上國十快速道路, 但這回是要回高雄家. 走台 17 快接近大中路時導航說到橋下靠右上快速道路, 然後靠左下博愛路, 咦, 靠右有閘道嗎? 結果依導航走真的在高架橋下右邊有一個上國十的閘道, 但上去後要靠左才能從中間閘道下博愛路, 靠右是往鳳山與國十.

翠華路與高鐵這邊的路網我一直都覺得很複雜, 平時又幾乎不會走這邊, 要不是二哥讀大學, 我可能一直都搞不清楚要怎麼走. 下次在開車去高雄大學就照導航走看看唄.

2019年9月20日 星期五

市圖還書 6 本

本周市圖還書如下 :
  1. 高手的養成 :股市新手必須知道的3個祕密
  2. 養錢練習 :10大理財好習慣讓你有錢花
  3. 全新!New TOEIC新多益聽力題庫解析
  4. 大學不迷茫
  5. 微積分
  6. 文科生也看得懂的資料科學
大學不迷茫是幫二哥借的, 不過他說沒時間看 (或許沒興趣). 多益也是暑假二哥準備考多益時借的, 後來覺得應該買新的, 就在明儀買了五冊. 我的英文近 10 幾年沒在用心功力大退, 是該補救補救了.

好書 : Computational Physics

因為二哥讀物理, 最近幫他找了幾本 Python 計算物理學的書, 但現在學期中課較忙沒時間看, 先記下來等寒暑假再找來看 :



Source : 博客來


此書是計算物理學書中的上上之選, 內容包括微分, 積分, 解微分方程, 矩陣計算, 傅立葉分析, 動力學,熱力學模擬, 分子動力學, 偏微分方程, 波動方程式, 流體動力學, 量子電動力學, 以及蒙地卡羅算法等等. 只是博客來賣 5000 多元也太貴了, Amazon 才賣 27 元美金耶! 


2. Effective Computation in Physics (Oreilly, 2015)


Source :Oreilly


此書是為物理學相關 (天文, 地理, 大氣科學, 生物, 核工等) 的研究者而寫的 Python 程式工具書. 電腦與數學對物理學而言都只是工具, 沒有他們雖然同樣可以搞物理, 但有了他們就如同猛虎添翼. 此書比較偏向物理人的 Python 資訊處理工具書 (例如 Numpy 陣列, 正規表達式, HDF5 資料結構, 版本控制等), 而不是實際介紹物理方面的應用, 書中不會介紹如何用 Python 解波動方程式. 這本有簡體翻譯版, 參考 :

Python物理學高效計算




Source : CRC


此書與上面第一本內容類似, 包含物理學各領域, 但著重在如何從理論式轉換到程式化的過程, 每章末尾附習題, 屬於嚴謹教科書.

另外我在下面這篇找到五本書, 但其中只有第一本 Mark Newman 寫的 Computational Physics 使用 Python :

5 Highly Recommended Computational Physics Textbooks

4. Computational Physics (2012, CreatedSpace)


Source : Amazon


此書為 2012 年出版, 應該是使用 Python 2.x 版.

總之, Python 在計算物理學上的應用幾乎是建立在 Numpy, Scipy, Pandas, 與 Matlablib 這四個重要套件上, 事實上, 不只是物理學, 只要是量化計算, 精通這四個套件是基本要求.

我想找中文 Python 計算物理學的書, 除了 "Python物理學高效計算" 這本檢體翻譯書外竟然付之闕如. Amazon 倒是有一本日文的 :

計算物理学II ―物理現象の解析・シミュレーション― (実践Pythonライブラリー)

其實英文的本就不多了, 何況是中文.


2019-09-20 補充 :

找到一個 Scipy 線上教材 :

Scipy Lecture Notes

2019年9月19日 星期四

好站 : Python Cookbook 3rd 線上中譯版

今天找到一個不錯的 Python 線上教材 :

https://python3-cookbook.readthedocs.io/zh_CN/latest/preface.html

此為 Python Cookbook 第三版的簡體中譯, 原書為 Oreilly 出版 :

https://www.amazon.com/Python-Cookbook-Third-David-Beazley/dp/1449340377

一般來說歐萊里 Cookbook 類的書主要是進階教材, 並不適合初學者.

2019年9月18日 星期三

上面種電下面種瓜

今天看到今周刊這篇農電, 魚電複合式經營的文章, 覺得綠色能源過去是因為不做, 所以讓擁核者頻頻用缺電當作裹脅人民的工具, 明明就沒有一個鄉鎮願意當核廢料掩埋場, 偏偏就一堆腦袋進水的要推核能. 只要政府政策明確, 配套法規都能完善, 南臺灣炙熱的太陽就能轉化為源源不絕的能源, 底下又能進行能耕, 不影響農地農用原則.

上面種電、下面種瓜 他年收3千萬 台灣太陽能兆元商機爆發

大約三年前就得到屏東向陽公司的資訊, 一直想找時間去參訪都未成行. 一公頃土地電廠年收入 500 萬元, 農作收入 250 萬元, 業者六公頃年收就超過四千萬元, 不過這是自己投資建設才是如此, 裝置成本應該很可觀. 如果只是租給維運商一公頃收入多少要再探究.

韓語字母表

這幾天看韓劇 Mr.Sunshine (陽光先生), 昨日已看到第 12 集, 尤金與高愛信和好後學韓文文字書寫, 終於了解高愛信在書頁上寫的 "我想你了", 而愛信在學堂的英文也有進步, 兩人約定透過藥房的魚腥草藥盒交換書信, 感情迅速增溫.  

看過這麼多韓劇, 只有這部劇情有帶到韓文書寫系統, 還有多個畫面特寫書信內容, 讓原本不太想接觸韓文拼寫的我也有了點興趣. 既是拼音系統, 則記憶子音母音符號不可免, 下面這個語言學習網站 Glossika 整理得非常好 : 


學會拼音後看歌詞就知道怎麼發音了. 

2019年9月16日 星期一

網頁技術速學筆記 (三) : Javascript 基礎

在學過 HTML 與 CSS 基礎後, 應該先學習 Javascript 基本語法再來學習表單元件, 表單是互動網頁的主角, 而 Javascript 則是互動網頁的靈魂, 也是網頁前端 (瀏覽器) 上唯一的程式語言 (以前 IE 上除了 Javascript 外還可以用 VBScript, 但 IE11 以後微軟已經放棄在 IE 上支援 VBScript 了).




本系列之前的筆記參考 :

申請免費 PHP 虛擬主機 000a.biz 與 000webhost.com
網頁技術速學筆記 (一) : HTML 基礎
網頁技術速學筆記 (二) : CSS 基礎

此筆記主要是參考下列書籍文章整理而成 :
  1. Speaking Javascript (歐萊禮)
  2. 網頁設計必學的程式實作技術第二版 (博碩)
  3. 重新認識 JavaScript
關於 Javascript 程式的背景摘要整理如下 :
  1. Javascript 源自 1995 年網景工程師 Brendan Eich 為該公司的 Netscape 瀏覽器所設計的解譯式程式語言, 並於 1997 年被 ECMA 當作基礎制定了 ECMAScript 規範 ECMA-262. Brendn 後來創立了 Mozilla 基金會.
  2. Brendan Eich 在極短的時間 (約 10 天) 內寫出了 Javascript 架構, 目的是避免 Netscape 採用其它的程式語言. 語法 (基本型別與物件) 主要參考 Java; 字串, 陣列, 與正規表達式參考 Python 與 Perl; 一級函數 (first class function) 參考 Scheme; 原型繼承則來自 Self. 
  3. Javascript 的正式名稱為 ECMAScript, 事實上 Javascript 目前是 Oracle 擁有的註冊商標 (Oracle 購併 Sun 而取得), Mozilla 是少數獲准可公開使用 Javascript 名稱的單位. 
  4. Javascript 是一個動態 (dynamic) 與弱型態 (weakly-typed) 語言, 即定義一個變數時不需宣告其資料型別, 變數僅是一個指向記憶體的容器, 變數的資料類型與其值相關, 可以隨時改變所參考之資料型態. 動態語言是在執行時才進行變數之資料型別檢查, . 
  5. Javascript 是一個直譯語言 (Interprete), 原始碼不須經過編譯, 而是透過瀏覽器提供的執行環境 (即內建之 Javascript 直譯器) 即時將原始碼解譯成可執行之機器碼. 直譯語言通常無法獨立執行而是依賴於執行環境, 其效能取決於直譯器之速度. 
  6. Javascript 是一個物件導向 (Object-oriented) 語言, 但與一般以類別為基礎的物件導向語言 (例如 Java 與 C++ 等) 不同之處是, Javascript 的物件是以物件原型為基礎 (Prototype-based) 的, 它是以現成的 Prototype 物件來建立其他物件, 而不是以類別 (抽象的資料型態), 亦即在 Javascript 中類別的功能是用 Prototype 物件實例來達成. 
  7. Javascript 中的任何物件都有一個 protptype 屬性, 其值為一個 Prototype 物件, Javascript 所有物件均繼承自 Prototype 物件, 其 prototype 屬性就是用來擴充物件之屬性與方法. 採用這種方式的原因是可以減少大量物件耗用記憶體空間的情形. 
參考 :

https://zh.wikipedia.org/wiki/JavaScript
https://en.wikipedia.org/wiki/JavaScript
編譯語言 VS 直譯語言
動態型別的衰退
https://zh.wikipedia.org/wiki/類型系統


執行 Javascript 有兩種方式 :
  1. 瀏覽器 :
    瀏覽器是 Javascript 最早也是現成的執行環境, 只要在網頁中嵌入 Javascript 程式碼或連結外部 .js 檔即可. 
  2. Node.js 命令列 :
    需安裝 Node.js, 它提供一個命令列 (CLI) 介面可用互動方式執行 Javascript 程式碼, 無需依靠網頁與瀏覽器. 
Node.js 是使 Javascript 成為網站後端執行語言的功臣, 參考 :

Node.js 學習筆記 (一) : 安裝 Node.js

以下的範例如果與瀏覽器無關為了不占篇幅會使用 Node.js REPL 來測試, 否則即用網頁來測試.


一. Javascript 的載入與執行 : 

在網頁中載入 Javascript 的方法有三 :
  1. 在網頁標頭 head 內以 link 元素連結外部 js 檔 
  2. 在網頁標頭 head 內以 script 元素嵌入
  3. 在網頁主體 body 內 (也可以在 body 外) 以 script 元素嵌入 
注意, 外部 .js 檔純粹只是 Javascript 程式碼, 不可含有 <script>  </script> 標籤.

<!DOCTYPE html>
<html>
<head>
  <title>CSS 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <link src="js/myscript.js">      
  <script>
    <!-- Javascript codes --> 
  </script>
</head>
<body>
  <script>
    <!-- Javascript codes --> 
  </script>
</body>
</html>

Javascript 程式碼的執行方式有兩種 :
  1. 依序執行 (sequential) :
    按照載入順序決定執行先後, 在 head 中嵌入或連結的 Javascript 程式碼會在網頁主體載入完成之前被執行, 而嵌入在 body 中的程式碼則要等到網頁被載入後才執行.
  2. 事件驅動 (event-driven) :
    程式碼寫在函式中, 當使用者在網頁中因輸入觸發滑鼠或鍵盤事件呼叫函式時執行. 
互動式網頁就是使用表單搭配事件驅動達成的.


二. 註解與輸出 : 

Javascript 的註解方式有兩種 :
  1. 單行註解 : 在 // 後面的程式碼會被視為註解而不被解譯
  2. 多行註解 : 在 /* 與 */ 之間的程式碼會被視為註解而不被解譯
善用註解可使程式碼易於維護.

程式在開發階段除錯常需要輸出相關變數之值, Javascript 可透過下列四種方法在瀏覽器中輸出訊息 :


 Javascript 輸出方式 說明
 alert(str) 在輸出視窗中顯示變數 str 之值
 document.write(str)  在網頁內容 (body) 輸出變數 str 之值
 document.getElementByID(myID).innerHTML(str) 將 id=myID 的網頁元素內容以 str 取代
 console.log(str) 在瀏覽器主控台輸出變數 str 之值


其中 alert() 與 document.write() 最方便, 因為可直接使用, 不像 innerHTML 需要在網頁中準備一個具 id 屬性的元素; 而 console.log() 則要開啟瀏覽器的主控台, 在 Chrome 瀏覽器按 F12 即可開啟 Console 主控台.

這裡用到了瀏覽器 DOM 樹狀模型的最上層物件 window, 它代表了瀏覽器視窗, 而其子物件 document 則代表了網頁文件 (.htm). alert() 是 window 物件的方法, 正確來說應該用 window.alert(), 但使用最上層物件的方法與屬性可省略 window 不寫.

呼叫 document.getElementById() 可取得指定 id 的元素物件, 其 innerHTML 屬性即為元素之內容 (即夾在開始與結束標籤之間的字串). 呼叫  document.write() 會在網頁主體 body 內插入元素.


範例 : 註解與輸出測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <p id="output"></p>   <!--先繪製-->
  <script>
    /* 輸出測試 */
    document.write("<b>Hello World!</b>"); //後繪製
    document.getElementById("output").innerHTML="Hello World!"; //動態更改
    alert("Hello World!");  //彈出視窗
    console.log("Hello World!");   //主控台
  </script>
</body>
</html>




此例網頁中有一個 id=output 的 p 元素, 載入時並無內容, 而是利用元素的 innerHTML 屬性填進去的. 此 p 元素是 body 內原有的, 因此會顯示在上面, 而用 document.write() 寫進 body 內的會排在後面.


三. 變數 : 

1. 識別字 (identifier) : 

Javascript 的變數 (函數與物件名稱也適用) 名稱稱為識別字有如下限制 :
  • 有分大小寫, hello 與 Hello 是不同的識別字. 
  • 第一個字元必須是英文字母, 底線 _, 或錢號 $, 其餘字元也必須英文字母, 底線或數字, 不可用其他字元 (例如特殊字元). 
  • 不可使用保留字, 例如 for, if 與 then 等.
Javascript 的保留字有下列 44 個 :


 arguments break case catch
 class const continue debugger
 default delete do else
 enum export extends false
 finally for function  if
 implements import in instanceof
 interface let new null
 package private protected public
 return static super switch
 this throw true try
 typeof var void while


另外下列三個特殊值雖非關鍵字, 但也不能拿來當識別字用 :
  • NaN (非數字)
  • undefined (未賦值之變數值)
  • Infinity (無限大)
簡言之, 識別字不可用數字開頭, 字元中間不可有空白, 也不能使用特殊字元例如 -, @, $, #, %, ? 或句點等 (句點是物件的運算子). 最好避免使用英文字母 O, L 的小寫與 I 因其與數字的 0, 1 容易混淆.

下面都是不合法的變數名稱 :

2at (數字開頭)
chi nese (有空白)
interest% (有特殊符號)
my.name (有特殊符號)

Javascript 使用 var 宣告變數, 可在宣告時同時賦值, 沒有賦值的變數其值為 undefined. Javascript 是動態 (dynamic) 與弱型別 (weakly-typed) 語言, 宣告變數時不需指定所儲存之資料的型別, 且變數可隨時改為不同之資料型態, 例如 :

var a;                       //未賦值預設為 undefined
var b=100;              //b 參考數值
b="hello world";    //b 參考字串


2. 變數的有效範圍 (scope) : 

Javascript 的變數有效範圍以函數為界, 在函數外面的最上層變數稱為全域變數 (global variable), 它可以在程式中的任何地方被存取; 函數之參數以及用 var 宣告的變數均為區域變數, 只能在函數內存取, 例如 :

> var a=10;
> function foo(b) {
... var c=100;
... return a+b;                //a 在函數內找不到, 就往外找到全域變數 a=10
... }
> foo(30)                  //傳回 10+30=40
40
> b                            //函數外無法存取函數內之區域變數
ReferenceError: b is not defined     //b 是區域變數 (參數) 在外部為未定義
> c                            //函數外無法存取函數內之區域變數
ReferenceError: c is not defined     //c 是區域變數在外部為未定義

在函數內用 var 宣告的變數為區域變數, 若與外部變數同名, 則函數將存取區域變數而非全域變數, 例如 :

> var a=10;
> function foo(b) {
... var a=20;      //用 var 宣告為區域變數, 不會影響外部全域變數
... return a+b;    //取用區域變數 a, b
... }
> foo(30)      //傳回 20+30=50
50
> a               //全域變數不受影響仍為 10
10 

函數內的區域變數最好都用 var 宣告, 否則它會被向上提升至全域等級, 若同名將影響外部全域變數之值, 可能使程式出現意外的結果, 例如 :

> var a=10;
> function foo(b) {
... a=20;              //沒有用 var 宣告會被提升, 可能影響全域變數之值
... return a+b;
... }
> foo(30)
50
> a                  //全域變數 a 被函數內的 a=20 改變了
20

此例在在 foo() 內執行 a=20 時因為沒有用 var 宣告 a, 因此會向上提升存取外部的全域變數, 使得全域變數 a 的值也被改成 20, 這有點像是 Python 中在前面加一個 global, 變成 global a=20 的效果.


四. 資料型別 : 

Javascript 的資料型別共 6 種, 可分為基本型別與參考型別 2 類 :


 Javascript 資料型別 說明
 基本型別 (primitive) 數值 (number), 字串 (string), 布林 (boolean), undefined
 參考型別 (reference) 陣列 (array), 物件字面值 (object literal), 正規表達式, null


參考型別也稱為物件型別, 事實上在 Javascript 中基本型別以外的都屬於物件型別. Javascript 的基本型別設計來自 Java. 基本型別與物件主要的差別在於, 基本型別是不可變的 (immutable), 而物件是可變的 (mutable), 因為它們儲存在不同的記憶體分區裡, 因此比較時基本型別是比較其值, 而物件則是比較其參考.

以記憶體儲存位置來說, 基本型別資料儲存在堆疊記憶體 (stack memory) 區, 變數的位址儲存的就是變數的值, 因此變數與其值是在一起的; 而參考型別與 null 則儲存在動態指配的堆積記憶體 (heap memory) 區, 其位址 (稱為參考指標) 會傳到堆疊區內的變數中儲存, 因此變數與資料是分離的, 亦即堆積中的物件資料是透過堆疊中的參考指標間接存取的. 因為存在這種差別, 呼叫函數時, 若引數是基本型別則採用傳值呼叫, 傳到函數內的是外部變數的複本; 若引數是參考型別則是傳參考 (址) 呼叫, 在函數內會存取到同一份資料.

Javascript 有兩個特殊的值 : null 與 undefined, 它們都是無的意思, null 表示 no object (無物件), 而 undefined 表示 no value (無值). null 是一個特殊的 object 型別, 因為它並未指向堆積中的任何物件 (無物件). 而 undefined 則是用來代表未初始化的變數, 函數缺少的參數, 或者物件中缺少的屬性. 另外, 函數也被視為是一種特別的物件, 雖然 typeof 會傳回 "function".

變數的型別可用內建函數 typeof() 或保留字 typeof 檢視, 例如 typeof(a) 或 typeof a, 傳回值為字串, 參考 :

https://pjchender.blogspot.com/2016/07/javascript-typeof.html

Javascript 所有資料型別的 typeof() 傳回值摘要如下表 :


 資料型別 範例 typeof a 傳回值 (字串)
 數值 (整數) var a=123; number
 數值 (浮點數) var a=3.14159; number
 數值 (浮點數) var a=6.62607015e-34 number
 字串 var a="Hello World" string
 布林值 var a=true; boolean
 布林值 var a=false; boolean
 物件 var a={}; object
 物件 var a=new Object(); object
 陣列 var a=[]; object
 陣列 var a=new Array(); object
 函數 var a=new function() {}; function
 undefined var a; undefined
 null var a=null; object


注意 null 的 typeof 傳回值為 'object', 但它並未指向堆積中的任何物件, 這是 Javascript 中的一個無法修正的 bug.


範例 : 資料型別的 typeof 測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <p id="output"></p>
  <script>
    /* 資料型別的 typeof 測試 */
    var a;    //未初始化之變數
    document.write("typeof a=" + typeof a + "<br>");
    document.write("typeof 123=" + typeof 123 + "<br>");
    document.write("typeof 3.14159=" + typeof 3.14159 + "<br>");
    document.write("typeof 6.62607015e-34=" + typeof 6.62607015e-34 + "<br>");
    document.write("typeof 'Hello World'=" + typeof 'Hello World' + "<br>");
    document.write("typeof true=" + typeof true + "<br>");
    document.write("typeof false=" + typeof false + "<br>");
    document.write("typeof {}=" + typeof {} + "<br>");
    document.write("typeof new Object()=" + typeof new Object() + "<br>");
    document.write("typeof []=" + typeof [] + "<br>");
    document.write("typeof new Array()=" + typeof new Array() + "<br>");
    document.write("typeof function(){}=" + typeof function(){} + "<br>");
    document.write("typeof undefined=" + typeof undefined + "<br>");
    document.write("typeof null=" + typeof null + "<br>");
  </script>
</body>
</html>




可見未初始化 (宣告但未賦值) 的變數, 其值為 undefined. 陣列, 物件, 以及 null 三者 typeof 都會傳回 "object", 事實上用 new 產生的物件都是 "object". 其中 {} 與 new Object() 都是 Javascript 建立物件的方式, 而 [] 與 new Array() 則是建立陣列的方式.

基本資料型別 (primitives) 有三種 :


 資料型別 說明 對應物件
 數值 (number) 整數例如 123, 0x7b 浮點數例如 .001 或 0.001 或 1.23e-19 Number
 字串 (string) 可用雙引號貨單引號, 例如 "hello" 或 'hello' String
 布林 (boolean) true (=1) 或 false (=0) Boolean


基本型別資料有如下特點 :
  • 值與屬性是不可變的 (immutable)
  • 比較兩個基本型別變數時是比較其值
基本型別的屬性是來自其包裹器物件所附加的, 例如字串的包裹器物件為 String, 它會將字串包裹起來成為物件而擁有 String 物件之屬性與方法 :

> var str='hello'
> str.length
5
> str[0]
'h'
> str[0]='k'         //企圖將第一字元改成 'k' : 無效
'k'
> str                   //str 仍然是 'hello', 沒有改變
'hello'                   
> str.length=3    //企圖將 length 屬性改變 : 無效
3
> str.length     
5     

兩個不同基本型別變數, 只要值相同, 則 == 與 === 比較運算都會傳回 true :

> var a='Tony'
> var b='Tony'
> a==b
true
> a===b
true
> var a='123'    //數值字串
> var b=123
> a==b             //a 會被強制轉型為數值 123 後再比較
true 
> a===b           //型別不同傳回 false
false

數值字串則會自動強制轉型後再比較其值, 因此 == 也會傳回 true, 但 === 則傳回 false, 因為型別不同.


(1). 數值 :

Javascript 的數值雖然分為整數與浮點數, 但其實都是浮點數, 使用全等運算子 === 比較 10 與 10.0 傳回 true :

> 10 === 10.0
true

Javascript 有一個包裹器物件 Number() 可用來將其它資料型別轉成數值, 例如數值字串可轉成數值, 但非數值字串則無法轉換而傳回 NaN (not a number), 例如 :

> Number("3.14159")
3.14159
> typeof Number("3.14159")    //傳回 "number" 型別
'number'
> Number("abc")          //非數值
NaN
> Number("abc123")    //字串必須全為數值才能轉換
NaN

整數可用不同基底 (base) 的進位制來表示 :


 進位基底  說明
 十進位 由 0~9 數字表示, 不需冠碼
 二進位 以 0b 或 0B 開頭其餘為 0 與 1, 例如 0B1100 為 10 進位的 12
 八進位 以 0 開頭其餘為 0~7, 例如 012 為 10 進位的 10
 十六進位 以 0x 或 0X 開頭其餘為 0~9 與 a/A~f/F, 例如 0x7B 等於 10 進位的 123


關於數值用法摘要如下 :
  • 不論整數字面值 (literal) 用哪一種進位表示, 經過運算後都是傳回 10 進位值, 例如 alert(0xFF) 會傳回 255 . 
  • 整數變數可用 Number 物件的 toString(base) 方法來轉換成其他基底進位制來表示, 只要將目標基底 base 當參數傳進去即可 (未傳參數預設是傳回 10 進位). 
  • toString() 只能用於變數, 不可用於字面值 (literal), 亦即 123.toString(16) 是不允許的, 字面值必須先放到小括弧內強制運算後才能呼叫 toString(), 例如 (123).toString(16).
  • ECMAScript 不支援八進位值, 因為常會被誤解為十進位數, 故為了安全性盡量不要用八進位表示數值
下面為整數在不同基底之間的互轉範例 :


範例 : 整數基底轉換測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <p id="output"></p>
  <script>
    /* 整數基底轉換測試 */
    var a=123;
    document.write(a + "(10進位)=" + a.toString(2) + "(2進位)<br>");
    document.write(a + "(10進位)=" + a.toString(8) + "(8進位)<br>");
    document.write(a + "(10進位)=" + a.toString(16) + "(16進位)<br>");
    var a=0b1100;
    document.write(a + "(10進位)=" + a.toString() + "(10進位)<br>");
    document.write(a.toString(2) + "(2進位)=" + a.toString(10) + "(10進位)<br>");
    var a=0x7b;
    document.write(a + "(16進位)=" + a.toString() + "(10進位)<br>");
    document.write(a.toString(16) + "(16進位)=" + a.toString(10) + "(10進位)<br>");
    document.write("0xFF(16進位)=" + (0xFF).toString(10) + "(10進位)<br>");
  </script>
</body>
</html>




可見 toString() 沒有傳參數的話預設是轉成 10 進位.

浮點數可使用科學表示法, 其中指數基底用 e 或 E 均可, 後面為冪次, 表示為 10 的幾次方, 例如 1.23e2 等於 1.23*100=123. 但是由於 10 進位的浮點數在轉成 2 進位數時有些是無限循環, 儲存時需捨去或進位而造成些許誤差.


範例 : 數值資料測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <p id="output"></p>
  <script>
    /* 數值測試 */
    document.write(123 + "<br>");
    document.write(3.14159 + "<br>");
    document.write(6.62607015e-34 + "<br>");
    document.write(6.62607015E-34 + "<br>");
    document.write("0.1+0.1=" + (0.1+0.1) + "<br>");
    document.write("0.1+0.2=" + (0.1+0.2) + "<br>");
    document.write("0.1+0.3=" + (0.1+0.3) + "<br>");
    document.write("0.2+0.4=" + (0.2+0.4) + "<br>");
    document.write("0.1+0.7=" + (0.1+0.7) + "<br>");
  </script>
</body>
</html>




可見某些浮點數相加結果與預期有些微誤差, 解決辦法是將浮點數利用字串處理分成整數與小數部分, 全部用整數來處理, 參考 :

詳解Javascript中被你忽略的浮點數運算的坑,來學習吧


(2) 字串 : 

字串可放在單括號或雙括號內, 下列情況需要同時使用單括號與雙括號 :
  • 字串中含有 apostrophe 例如 We're 或 I'm 等情形需使用雙括號, 例如 :
    "We're the world"
    "Tony's car"
  • 字串中含有屬性值時可用單或雙括號嵌套, 例如 :
    var html="<div class='blabla'></div>";
    var html='<div class="blabla"></div>';
除了交替使用單括號與雙括號外, 還可以用倒斜線 \ 跳脫括號, 這樣便能只使用一種括號, 例如 :
  • 含有 apostrophe :
    "We\"re the world"
    "Tony\"s car"
  • 字串中含有屬性值 :
    var html="<div class=\"blabla\"></div>";
    var html='<div class=\'blabla\'"></div>';
Javascript 語法定義了若干逸出序列 (escape sequence), 某些特定字元前面加上倒斜線即表示字元本身或特別之意義, 這些跳脫字元如下表所示 :


 逸出序列  說明
 \0 Null 字元 (=\u0000)
 \b 後退 (backspace) 字元 (=\u0008)
 \t 水平定位字元 (tab, =\u0009)
 \n 跳行字元 (=\u000A)
 \v 垂直定位字元 (=\u000B)
 \f 換頁字元 (=\u000C)
 \" 雙括號 (=\u0022)
 \' 單括號 (=\u0027)
 \\ 倒斜線 (=\u005C)
 \uXXXX Unicode 字元 (X 為 16 進位數)


但倒斜線僅對上表中的特殊字元有逸出效果, 對於一般字元則無作用, 倒斜線會直接被忽略.

字串可用 "+" 運算子串接, 例如 :

var str="Hello" + "World";

得到的字串 str 為 "HelloWorld".


範例  : 字串逸出序列測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 字串逸出序列測試 */
    document.write("\0" + "<br>");   //不可見
    document.write("\b" + "<br>");   //不可見
    document.write("\t" + "<br>");    //不可見
    document.write("\n" + "<br>");   //不可見
    document.write("\v" + "<br>");   //不可見
    document.write("\f" + "<br>");    //不可見
    document.write('\"' + "<br>");      //雙括號
    document.write("\'" + "<br>");     //單括號
    document.write("\\" + "<br>");     //倒斜線本身
    document.write("\u3042" + "<br>");     //日文
    document.write("\uC5FD" + "<br>");   //韓文
    document.write("\u79AA" + "<br>");    //中文
  </script>
</body>
</html>


可見自 \0 至 \f 均不可見, 注意雙括號與單括號交替出現的技巧.

淺談JS中String()與 .toString()的區別


(3). 布林值 :

布林值用來表示邏輯中的真假, 因此它只有兩個字面值 : true 與 false. 下列的運算子的運算結果為布林值 :
  • 邏輯運算子
  • 關係運算子
Javascript 有一個包裹器物件建構子 Boolean() 可將其它資料型別轉成布林值, 除了下面的 6 個值傳回 false 外, 其餘均傳回 true :
  • undefined
  • null
  • false 
  • 0
  • NaN
  • '' (空字串)

例如 :

> Boolean(undefined)
false
> Boolean(null)
false
> Boolean(false)
false
> Boolean(0)
false
> Boolean(NaN)
false
> Boolean('')
false
> Boolean({})   //空物件
true
> Boolean([])    //空陣列
true


(4). null 與 undefined :

資料型別 undefined 與 null 都是字面值, 型別分別為 undefined 與 object, 兩者之真值均為 false, 亦即 if (null) {} 與 if (undefined) {} 均不會執行, 因為用 Boolean() 包裹器建構子強制轉型為布林值均傳回 false :

> Boolean(null)
false
> Boolean(undefined)
false

用 Number() 包裹器建構子強制轉型為數值時, Number(null) 會傳回 0, 而 Number(undefined) 則傳回 NaN (非數值) :

> Number(null)
0
> Number(undefined)
NaN

unndefined 會出現的場合如下 :
  • 未賦值的變數
  • 未傳入的參數
  • 無 return 的函數傳回值
  • 不存在的物件屬性
null 則出現在用正規式搜尋字串卻不匹配時, 例如 /a/.exec('xyz') 傳回 null.

檢驗一個變數是否為 null 或 undefined 要用嚴格相等性運算子 === 或 !=== :

if (a === undefined) {}
if (a === null) {}


範例 : 特殊資料型別 undefined 與 null 測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* undefined 與 null 測試 */
    var a;
    document.write("a=" + a + "<br>");
    document.write("Number(undefined)=" + Number(undefined) + "<br>");
    document.write("Boolean(undefined)=" + Boolean(undefined) + "<br>");
    document.write("a===undefined :" + (a===undefined) + "<br>");
    function foo(i) {return i;}
    document.write("foo()=" + foo() + "<br>");
    var obj={};
    document.write("obj.name=" + obj.name + "<br>");
    function bar() {var b=1;}
    document.write("bar()=" + bar() + "<br>");
    var ret=/a/.exec('xyz');
    document.write("/a/.exec('xyz')=" + ret + "<br>");
    document.write("Number(null)=" + Number(ret) + "<br>");
    document.write("Boolean(null)=" + Boolean(ret) + "<br>");
    document.write("/a/.exec('xyz')===null:" + (ret===null) + "<br>");
  </script>
</body>
</html>

結果如下 :

a=undefined
Number(undefined)=NaN
Boolean(undefined)=false
a===undefined :true
foo()=undefined
obj.name=undefined
bar()=undefined
/a/.exec('xyz')=null
Number(null)=0
Boolean(null)=false
/a/.exec('xyz')===null:true

可見當一個函數無傳回值時, 它就傳回 undefined.


五. 運算子 : 

Javascript 的敘述由運算子與運算元構成, 運算元就是上面介紹的資料型別, 而運算子就是計算的主體, 主要是算術與邏輯運算.


1. 算術運算子 : 

算術運算子即加減乘除求餘數等四則運算 :


 算術運算子 說明
 a + b 加法運算或字串串接, + 亦為正號運算子, 例如 +3
 a - b 減法運算, - 亦為負號運算子, 例如 -3
 a * b 乘法運算
 a / b 除法運算, 例如 5/2=2.5
 a % b 餘數運算, 即 a/b 之餘數, 例如 5%2 得 1
 ++a 遞增運算, 先遞增再賦值
 a++ 遞增運算, 先賦值再遞增
 --a  遞減運算, 先遞減再賦值
 a-- 遞減運算, 先賦值再遞減


算術運算子中比較需要注意的是 + 與 ++/-- 運算子 :
  • + 具有三種運算, 當運算元都是數值時為加法運算, 又可做為正數符號, +3 即 3, 還有字串串接運算功能. 如果運算元有數值與字串, 則會自左向右先進行數值計算直到遇到第一個字串運算元時, 會將前面與後面所有數值轉型為字串, 進行字串串接運算.
  • ++ 與 -- 為單元運算, 放在變數前面是先增減 1 再進行運算; 放在變數後面則是先以現值運算後再增減 1. 

範例 : 算術運算子測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 算術運算子測試 */
    document.write("10+3=" + (10+3) + "<br>");
    document.write("10-3=" + (10-3) + "<br>");
    document.write("10*3=" + (10*3) + "<br>");
    document.write("10/3=" + (10/3) + "<br>");
    document.write("10%3=" + (10%3) + "<br>");
    var a=10;
    document.write("a=" + a + "<br>");
    document.write("++a=" + (++a) + "<br>");
    document.write("a=" + a + "<br>");
    document.write("--a=" + (--a) + "<br>");
    document.write("a=" + a + "<br>");
    document.write("a++=" + (a++) + "<br>");
    document.write("a=" + a + "<br>");
    document.write("a--=" + (a--) + "<br>");
    document.write("a=" + a + "<br>");
  </script>
</body>
</html>

結果如下 :

10+3=13
10-3=7
10*3=30
10/3=3.3333333333333335
10%3=1
a=10
++a=11        //a 先增量為 11 再做 () 運算
a=11     
--a=10          //a 先增量為 10 再做 () 運算
a=10
a++=10        //a 先做 () 運算再增量
a=11             //a 已增量為 11
a--=11          //a 先做 () 運算再減量
a=10             //a 已減量為 10

可見 ++a 與 --a 都是先做增減量再做運算, 因此運算時都是增減後的新值; 而 a++ 與 a-- 都是先做運算再增減量, 故運算時都用原來的舊值.


2. 關係 (比較) 運算子 :

關係運算子又稱比較運算子, 用來比較運算元的值或型別, 運算元可以是基本型別或物件, 陣列等, 運算結果為 boolean 型別 (true/false), 主要用於迴圈與條件敘述中做為執行條件判斷 :


 關係運算子 說明
 a < b 小於
 a > b 大於
 a == b 等於 (值相等為 true)
 a <= b 小於等於
 a >= b 大於等於
 a != b  不等於 (值不相等為 true)
 a === b 全等於 (值與型別相等才 true)
 a !== b 不全等於 (值不相等者型別不同為 true)


關係運算中的運算元通常是數值資料, 主要是用來比大小; 但也可以是任何型別資料, 其中基本型別資料會自動被轉成數值或編碼後再進行比較, 規則如下 :
  • 數值字串會自動呼叫 parseInt() 或 parseFloat() 轉成數值後再比較. 
  • 兩個字串相比則是依序以字元表中的編碼值大小比較 (這會耗費較多時間). 
  • 布林值會轉換成數值再比較, false 轉成 0, 而 true 轉成 1. 
  • 物件與陣列 (參考型別) 則是比較參考位址是否相同. 
全等運算 === 除了兩個運算元值要相等外, 其型別或參考位址也必須相等才會傳回 true.


範例 : 關係運算子測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 關係運算子測試 */
    document.write("10 > 20 " + (10 > 20) + "<br>");
    document.write("10 < 20 " + (10 < 20) + "<br>");
    document.write("10 == 20 " + (10 == 20) + "<br>");
    document.write("10 >= 20 " + (10 >= 20) + "<br>");
    document.write("10 <= 20 " + (10 <= 20) + "<br>");
    document.write("10 != 20 " + (10 != 20) + "<br>");
    document.write("10.0 == 10 " + (10.0 == 10) + "<br>"); //值相同, true
    document.write("false > 1 " + (false > 1) + "<br>");  //false=0, false
    document.write("false == 0 " + (false == 0) + "<br>"); //值相同, true
    document.write("false === 0 " + (false === 0) + "<br>"); //型別不同, false
    document.write("true == 1 " + (true == 1) + "<br>"); //true=1, true
    document.write("'10' < 20 " + ('10' < 20) + "<br>");  //轉成 10 < 20, true
    document.write("'10' == 10 " + ('10' == 10) + "<br>"); //值相同, true
    document.write("'10' === 10 " + ('10' === 10) + "<br>"); //型別不同, false
    document.write("'10.2' > 10.1 " + ('10.2' > 10.1) + "<br>");
    document.write("'a' < 'b' " + ('a' < 'b') + "<br>"); //'a'=61,'b'=62
    var a={};
    var b={};  //a, b 指向不同物件, 參考位址不同
    var c=a;  //c, a 指向相同物件, 參考位址相同
    document.write("a === b " + (a === b) + "<br>"); //參考位址不同, false
    document.write("c === a " + (c === a) + "<br>"); //參考位址相同, true
  </script>
</body>
</html>

結果如下  :

10 > 20 false
10 < 20 true
10 == 20 false
10 >= 20 false
10 <= 20 true
10 != 20 true
false > 1 false
false == 0 true
true == 1 true
'10' < 20 true
'10.2' > 10.1 true
'a' < 'b' true

此例數字字串與數值相比時, 會自動將字串用 parseInt() 或 parseFloat() 轉成數值再相比; 兩個字串則直接用其 ASCII 或 unicode 相比.


3. 邏輯運算子 : 

邏輯運算子用來計算邏輯運算式, 其傳回值理論上是 true 與 false :


 邏輯運算子 說明
 a && b 邏輯 AND 運算 (a, b 均為 true 才為 true)
 a || b 邏輯 OR 運算 (a, b 任一為 true 即為 true)
 !a 邏輯 NOT 運算 (true 變 false, false 變 true)


不過因為運算元可以是任何資料型別, 不一定是布林值, 二元邏輯運算子 && 與 || 實際上是以所謂的短路 (short circuiting) 的方式計算傳回值的 :
  • && : 如果第一個運算元為 false 就傳回它, 否則傳回第二個運算元.
  • || : 如果第一個運算元為 true 就傳回它, 否則傳回第二個運算元.
亦即只要從第一個運算元的 true/false 值就決定了傳回值, 例如 :

> NaN && 123            //NaN 為 false 故傳回 NaN
NaN
> 123 && undefined   //123 為 true 故傳回 undefined
undefined
> 123 || 'hello'               //123 為 true 故傳回 123
123
> '' || 'hello'                   //空字串 '' 為 fasle 故傳回 'hello'
'hello'


範例 : 邏輯運算子測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 邏輯運算子測試 */
    document.write("2<3 && 5>4 " + (2<3 && 5>4) + "<br>");
    document.write("2>3 || 5>4 " + (2>3 || 5>4) + "<br>");
    document.write("2>3 || 5>4 " + !(5>4) + "<br>");
  </script>
</body>
</html>

結果如下 :

2<3 && 5>4 true
2>3 || 5>4 true
2>3 || 5>4 false


4. 位元運算子 : 

位元運算子是將運算元以二進位表示後對每一個位元進行左移或右移, 要注意的是, 左移時右邊空出的位子是補 0; 而右移時左邊空出的位子是補 1, 但無號右移是補 0, 對於正數而言, 有號與無號右移結果相同; 但對於負數而言, 有.

左移一位元在算術上等於乘以 2, 而右移一位元則是除以 2 (但只取商).


 位元運算子 說明
 a & b 將 a, b 對應位元進行位元 AND 運算
 a | b 將 a, b 對應位元進行位元 OR 運算
 a ^ b  將 a, b 對應位元進行位元 XOR 運算
 ~a  將 a 的每一位元進行位元 NOT 運算 (反相)
 a << b  將 a 向左移位 b 個位元 (右邊補 0)
 a >> b  將 a 向右移位 b 個位元 (左邊補符號位元 : 正補 0, 負補 1)
 a >>> b 將 a 向右無號移位 b 個位元 (左邊補 0)


範例 : 位元運算子測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 位元運算子測試 */
    document.write("0b0011 & 0b0110 = " + (0b0011 & 0b0110) + "<br>");
    document.write("0b0011 | 0b0110 = " + (0b0011 | 0b0110) + "<br>");
    document.write("0b0011 ^ 0b0110 = " + (0b0011 ^ 0b0110) + "<br>");
    document.write("~0b0000 = " + (~0b0000) + "<br>");
    document.write("16 << 1 = " + (16 << 1) + "<br>");
    document.write("16 << 2 = " + (16 << 2) + "<br>");
    document.write("16 >> 1 = " + (16 >> 1) + "<br>");
    document.write("16 >> 2 = " + (16 >> 2) + "<br>");
    document.write("-16 >> 2 = " + (-16 >> 2) + "<br>");
    document.write("5 >> 2 = " + (5 >> 2) + "<br>");
    document.write("-1 >>> 1 = " + (-1 >>> 1) + "<br>");
    var minus_one=0b01111111111111111111111111111111;
    document.write("-1 >>> 1 = " + minus_one.toString() + "<br&
  </script>
</body>
</html>

結果如下 :

0b0011 & 0b0110 = 2
0b0011 | 0b0110 = 7
0b0011 ^ 0b0110 = 5
~0b0000 = -1
16 << 1 = 32
16 << 2 = 64
16 >> 1 = 8
16 >> 2 = 4
-16 >> 2 = -4
5 >> 2 = 1
-1 >>> 1 = 2147483647
-1 >>> 1 = 2147483647

此例兩個整數 3 (0b0011) 與 6 (0b0110) 做 AND 運算 0b0011 & 0b0110 結果為 0b0010, 上面說過任何運算結果預設都是以 10 進制表示, 因此結果為 2. 這兩個數做 OR 運算結果為 0b0111=7; 做 XOR 運算 (不同才為 1) 得 0b0101=5. 整數 0 (0b0000) 做反相結果為 0b1111, 這在 2 的補數表示法裡為 10 進位的 -1 (將 1111 反向得 0000, 加 1 得 1, 故為 -1).

移位部分, 16 (=0b10000) 左移 1 位得 0b100000=32 (16 乘以 2), 左移 2 位得 0b1000000=64 (16 乘以 4); 16 (=0b10000) 右移 1 位得 0b01000=8 (16 除以 2), 右移 2 位得 0b00100=4 (16 除以 4); -16 (=0b110000) 右移 2 位得 0b111100=-4 (-16 除以 4, 負數右移左方補 1 故得 0b111100=-4, 因反相加 1 為 4 之故). 5 (0b0101) 右移 1 位為 0b0010=2, 此為 5/2 之商.

最後是無號右移, 對於正數而言, 有號與無號右移結果是一樣的, 但對負數而言, 無號右移結果會變成很大的正數, 因負數的 MSB (最左位元) 必為 1, 而無號右移左邊空出的位子是補 0, 此例中 -1 以 32 位元來說, 先從 +1 取反相再加 1 即得 -1 的 2 的補數表示 :

+1=0b00000000000000000000000000000001
1 的補數=0b11111111111111111111111111111110
2 的補數=0b11111111111111111111111111111111 (即 1 的補數加 1)
右移1 位=0b01111111111111111111111111111111 (此即程式中的 minus_one)

用 minus_one.toString() 檢驗結果是一樣的.

參考 :

javascript中負數算術右移、邏輯右移的奧祕探索


5. 指定運算子 : 

指定運算子用來將右邊的值指派給左邊的變數, 還可以與算術運算子, 邏輯運算子, 以及位元運算子結合為複合指派運算子 :


 指定運算子 說明
 a=2 指定 a 變數之值為 2
 a += 2 將變數 a 加上 2 後結果再指定給 a, 相當於 a=a + 2
 a -= 2 將變數 a 減掉 2 後結果再指定給 a, 相當於 a=a - 2
 a *= 2 將變數 a 乘以 2 後結果再指定給 a, 相當於 a=a * 2
 a /= 2 將變數 a 除以 2 後結果再指定給 a, 相當於 a=a / 2
 a %= 2 將變數 a 除以 2 取餘數再指定給 a, 相當於 a=a % 2
 a &= 2 將變數 a 與 2 做位元 AND 後再指定給 a, 相當於 a=a & 2
 a |= 2 將變數 a 與 2 做位元 OR 後再指定給 a, 相當於 a=a | 2
 a ^= 2 將變數 a 與 2 做位元 XOR 後再指定給 a, 相當於 a=a & 2
 a <<= 2 將變數 a 左移 2 位元後再指定給 a, 相當於 a=a << 2
 a >>= 2 將變數 a 右移 2 位元後再指定給 a, 相當於 a=a >> 2
 a >>>= 2  將變數 a 無號右移 2 位元後再指定給 a, 相當於 a=a >>> 2


範例 : 指定運算子測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 指定運算子測試 */
    var a=2;
    document.write("a = 2; " + "<br>");
    document.write("a += 2; //a=" + (a += 2) + "<br>");
    document.write("a -= 2; //a=" + (a -= 2) + "<br>");
    document.write("a *= 2; //a=" + (a *= 2) + "<br>");
    document.write("a /= 2; //a=" + (a /= 2) + "<br>");
    document.write("a %= 2; //a=" + (a %= 2) + "<br>");
    document.write("a &= 2; //a=" + (a &= 2) + "<br>");
    document.write("a |= 2; //a=" + (a |= 2) + "<br>");
    document.write("a ^= 2; //a=" + (a ^= 2) + "<br>");
    document.write("a <<= 2; //a=" + (a <<= 2) + "<br>");
    document.write("a >>= 2; //a=" + (a >>= 2) + "<br>");
    document.write("a >>>= 2; //a=" + (a >>>= 2) + "<br>");
  </script>
</body>
</html>

結果如下 :

a = 2;
a += 2; //a=4
a -= 2; //a=2
a *= 2; //a=4
a /= 2; //a=2
a %= 2; //a=0
a &= 2; //a=0
a |= 2; //a=2
a ^= 2; //a=0
a <<= 2; //a=0
a >>= 2; //a=0
a >>>= 2; //a=0


6. 條件運算子 (三元) : 

此為 Javascript 唯一的三元運算子, 其實它是 if else 分支邏輯條件式的迷你表示法, 它有兩種用法,  第一種為有三個運算式, 其中第一個運算式必須為布林運算式 :

b ? x : y 

依據布林式 b 的值為 true/false 選擇要執行運算式 x (true) 或 運算式 y (false). 這相當於 :

if (b) {x;}
else {y;}

還有另外一種用法是 x, y 為值或有傳回值的函數而非運算式 :

r=b ? x : y

這相當於 :

if (b) {r=x;}
else {r=y;}


 條件運算子 說明
 (a >= 60) ? r="pass" : r="fail" ? 前面的條件式為真時執行 ? 後面的運算式, 否則執行 : 後面的運算式
 r=(a >= 60) ? "pass" : "fail" ? 前面的條件式為真時傳回 ? 後面的值, 否則傳回 : 後面的值


範例 : 條件運算子測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 條件運算子測試 */
    var s1=59;
    var s2=60;
    var r1, r2;
    (s1 > 60) ? r1="pass" : r1="fail";
    r2=(s2 >= 60) ? "pass" : "fail";
    document.write("s1=" + s1 + " result=" + r1 + "<br>");
    document.write("s1=" + s2 + " result=" + r2 + "<br>");
  </script>
</body>
</html>

結果如下 :

s1=59 result=fail
s1=60 result=pass


7. 運算子優先順序 :

以上之運算子混合在一起運算時有先後之分, 其優先順序如下表 :


 運算子 說明
 1. () 括號運算子
 2. !, -, ++, -- 邏輯 NOT 運算子, 負號, 遞增, 遞減運算子
 3. *, /, % 乘, 除, 求餘數運算子
 4. +, -  加, 減法運算子
 5. <<, >>, >>> 左移, 右移,  無號右移運算子
 6. >, >=, <, <= 大於, 大於等於, 小於, 小於等於比較運算子
 7. ==, != 等於, 不等於比較運算子
 8. & 位元 AND 運算子
 9. ^ 位元 XOR 運算子
 10. | 位元 OR 運算子
 11. && 邏輯 AND 運算子
 12. || 邏輯 OR 運算子
 13. ? :  三元條件運算子
 14. =, op= 指定運算子


運算式的執行順序是由上而下, 由左向右依據上面的優先等級進行計算. 在算術運算中基本上就是先乘除後加減, 若要改變這個順序可用優先等級最高的小括號. 例如 :

7*3 >= 17-2 || 11+6*2 < 9

因算術運算子比邏輯與關係運算子優先, 因此會先進行算術運算 :

21 >= 15 || 11+12 < 9 

21>= 15 || 23 < 9

true || false

最後得到 true.

參考 :

運算式與運算子


六. 流程控制 (flow control) :

流程控制有兩種 : 分支 (branch) 與迴圈 (loop), 分支是一種選擇結構 (decision/selection structure), 利用檢查條件式傳回值為 true 或 false 來決定程式的執行方向; 而迴圈則是重覆結構, 用來重覆執行一段敘述.


1. 分支 (branch) : 

Javascript 的分支語法有 if else 與 switch case 兩種結構 :


 分支敘述 語法 1 語法 2 說明
 單一分支 if (條件式) {
    敘述;
    }
 if (條件式)  敘述; 單一敘述時可省略大括號
 雙重分支 if (條件式) {
    敘述1;
    }
 else {
    敘述2;
    }
 if (條件式)  敘述1;
 else 敘述2;
 單一敘述時可省略大括號
 多重分支 if (條件式1) {
    敘述1;
    }
 else if (條件式2) {
    敘述2;
    }
 else if (條件式3) {
    敘述3;
    }
 else {
    敘述4;
    }
 switch (表達式) {
     case 條件式1 : {
         敘述1;
         break;
         }
     case 條件式2 : {
         敘述2;
         break;
         }
     case 條件式3 : {
         敘述3;
         break;
         }
     default : {
         預設敘述;
          }
     }
 switch 的表達式可傳回 :
 1. 數值
 2. 字串
 3. 布林
 當全部 case 均不符時執行
 default 之預設敘述.
 每一個 case 須以 break 結束,
 否則將執行 default 預設



2. 迴圈 (Loop) :

Javascript 的迴圈有 for, while, 與 do while 三種結構 :


 語法 說明
 for (初始值; 條件式; 迭代式) {
  敘述;
  }
 從初始值開始, 若條件式為真則執行迭代式與迴圈內敘述.
 for (鍵 in 物件) {
  敘述;
  }
 迭代物件中的鍵執行迴圈中的敘述. 
 for (元素 in 陣列) {
  敘述;
  }
 迭代陣列中的元素執行迴圈中的敘述. 
 while (條件式) {
    敘述;
    } 
 條件式為真時執行迴圈中的敘述, 直到條件式為假.
 do {
    敘述;
    }
 while (條件式);
 先執行敘述再判斷條件式, 為真繼續執行迴圈直到條件式為假.
 此結構迴圈至少會執行一次


迴圈內可用 break 與 continue 保留字來停止迴圈或繼續下一迴圈.


七. 函數 :

函數是一組被封裝起來可以重複使用的程式碼區塊 (block), 是模組化設計必要的功能. 函數將其內部程式碼的變數與運算隱藏起來, 外部必須使用函數名稱呼叫, 函數會將運算傳回.

Javascript 是以物件為基礎的語言, 函數可以說是 Javascript 的全域方法 (global method). 函數雖然用 typeof 檢驗會傳回 'function', 其實函數也是一種物件, 一種可以被呼叫的特殊物件, 又被稱為可呼叫的值 (callable values). 函數也可以作為物件的屬性, 這時稱為該物件的方法 (method).

Javascript 的函數被稱為第一級函數 (first class function), 意思是 :
  • 函數可以存放在變數, 陣列, 與物件中
  • 可以當作一個參數傳遞給另一個函數
  • 可以當作另一個函數的回傳值
  • 函數具有屬性 (因為函數是一種特殊物件)
這些特性使得函數跟基本型別的資料類型沒兩樣.


1. 自訂函數 :

Javascript 自訂函數之方法有三 :
  • 函數宣告 (function declaration)
  • 函數運算式 (function expression)
  • 使用 new Function() 建構子函數建立
函數名稱命名規則與變數識別字規則相同, 傳入參數若有多個則以逗號隔開, 如果沒有傳入參數則小括弧內是空的.

函數若有回傳值則在函數最後面用關鍵字 return 傳回, 但只能傳回一個值, 若有多個值要傳回須用陣列或物件. 執行完 return 後函數即將控制權交回給呼叫者 (caller), 後面的敘述不會被執行, 因此 return 是函數最後一個敘述. 如果沒有回傳值就不需要 return, 或者只用 return 即可.

函數內的變數若用 var 宣告屬於區域變數, 其作用域只限於函數內, 函數外無法存取. 在函數內可以存取外部變數 (全域變數), 但若同名時會存取到函數內的區域變數, 而非外部變數.


 函數建立方式 語法 範例
 函數宣告 function 函數名([參數]) {
   敘述;
   [return 傳回值]
   }
 function sum(a, b) {
    return a+b;
    }
 函數運算式 var 變數名=function [函數名]([參數]) {
   敘述;
   [return 傳回值]
   };
 var s=function(a, b) {return a+b};
 var s=function sum(a, b) {return a+b};
 new Function() var 變數名=new Function(['參數'], '敘述'); var sum=new Function('a', 'b', 'return a+b');


這三種定義函數的方法在作用域上不同, 只有用函數宣告建立的函數可在宣告前呼叫, 用函數運算式與 new Function() 所建立的函數, 必須在建立函數後才能呼叫, 例如 :


範例 : 函數的範疇測試

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <p id="output"></p> 
  <script>
    /* 函數的 scope 測試 */
    document.write("sum1(10,1)=" + sum1(10,1) + "<br>");
    function sum1(a,b) {
      return a+b;
      }
    document.write("sum1(10,1)=" + sum1(10,1) + "<br>");
    //document.write("sum2(20,2)=" + sum2(20,2) + "<br>"); //sun2 未定義
    var sum2=function(a,b) {
      return a+b;
      }
    document.write("sum2(20,2)=" + sum2(20,2) + "<br>");
    //document.write("sum3(30,3)=" + sum3(30,3) + "<br>"); //sun3 未定義
    var sum3=new Function('a', 'b', 'return a+b');
    document.write("sum3(30,3)=" + sum3(30,3) + "<br>");
  </script>
</body>
</html>

執行結果如下 :

sum1(10,1)=11
sum1(10,1)=11
sum2(20,2)=22
sum3(30,3)=33

此例中的 sum2() 使用函數運算式建立的, sum3() 則是用 new Function() 建立的, 這兩個都必須在函數定義之後才能呼叫. 如果將上面被註解掉的兩行分別取消, 則會出現 "TypeError: sumx is not a function" 的錯誤 :





呼叫函數時若未傳入參數, 則缺漏的參數值為 undefined, 例如 :

> function foo(a) {console.log(a);}    //定義函數
> foo(2)       //傳入參數 2 輸出 2
2
undefined
> foo()         //未傳入參數輸出 undefined
undefined


(1). 位置性參數 : 

Javascript 函數的參數具有位置對應, 沒有傳入之參數其值為 undefined, 進行數值運算會得到 NaN, 例如下列求兩數和函數 :

> function sum(a, b) {    //求兩數和
... return a+b;
... }
> sum()              //參數 a, b 缺漏, 值均為 undefined, 和為 NaN
NaN
> sum(10)          //參數 a 缺漏, 10 + undefined= NaN
NaN
> sum(10, 20)    //無參數缺漏
30


(2). arguments 物件 :

Javascript 為函數提供了一個 arguments 物件, 讓我們可用陣列的索引方式來存取所傳入之參數. 此 argument 物件雖然狀似陣列, 但只是一個附有索引的物件而已, 除了索引功能外不能當一般陣列使用.


範例 : 自訂函數測試 : 等差數列和

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 自訂函數測試 */
    function sum(n) {
      var total=0;
      for (var i=0; i<=n; i++) {total += i;}
      return total;
      }
    var n=100;
    document.write("1+2+3+ ... +" + n + "=" + sum(n) + "<br>");
  </script>
</body>
</html>

結果如下 :

1+2+3+ ... +100=5050

此例傳入參數為基本型別 (數值), 因此屬於傳值呼叫.


遞迴函數 (recursive function) 是指在函數內呼叫自己的函數結構, 例如計算階乘 (factorial) 時就會用到遞迴, 所謂階乘是指一個正數連乘比自己小 1 的數直到 1 為止之乘積 :

n!=n*(n-1)*(n-2)*(n-3)* ..... *2*1

由於階乘運算具有重複性, 因此用遞迴方式最簡潔. 但注意 : 遞迴會耗費 CPU 與堆疊記憶體資源, 除非問題適合用遞迴 (例如階乘或盒內塔問題) 解決, 否則盡量少用.


範例 : 自訂函數測試 : 用遞迴求階乘

<!DOCTYPE html>
<html>
<head>
  <title>Javascript 測試</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <script>
    /* 自訂函數測試 */
    function factorial(n) {
      if (n<=0) {return 1;}
      return n*factorial(n-1);     //呼叫自己
      }
    var n=5;
    for (var i=1; i<=20; i++) {
      document.write(i + "!=" + factorial(i) + "<br>");
      } 
  </script>
</body>
</html>

結果如下 :

1!=1
2!=2
3!=6
4!=24
5!=120
6!=720
7!=5040
8!=40320
9!=362880
10!=3628800
11!=39916800
12!=479001600
13!=6227020800
14!=87178291200
15!=1307674368000
16!=20922789888000
17!=355687428096000
18!=6402373705728000
19!=121645100408832000
20!=2432902008176640000

參考 :

# 預設參數( Default parameters )


2. 內建函數 : 

Javascript 本身已內建了一些常用的函數如下表, 均為全域方法 (類似 Java 的類別方法), 可直接呼叫, 不須透過物件 :


 Javascript 常用內建函數 說明
 parseInt(str [, base]) 將字串 str 轉換成基底為 base 的整數
 parseFloat(str) 將字串 str 轉換成浮點數
 typeof(obj) 傳回物件 obj 的資料型別 (字串), 也可用 typeof obj 
 escape(str) 將傳入之字串進行 URL 編碼後傳回 (URL 字串)
 unescape(URLstr) 將傳入之 URL 字串解碼為原始字串後傳回
 isFinite(exp) 檢查運算式 exp 是否可用 eval() 運算, 傳回 true/false
 isNaN(x) 檢查變數 x 是否為非數值, 傳回 true/false
 eval(exp) 計算算術運算式 exp
 void(func) 使函數 func 的傳回值無效, 例如 void(0) 使超連結無效


其中 parseInt() 與 parseFloat() 用來將字串轉換成整數與浮點數 :

另外, 瀏覽器的 wndow 物件提供了四個計時器方法 :


 常用 window 物件方法 說明
 setTimout("func", ms) 設定計時器在 ms 毫秒後呼叫函數 func 一次, 傳回 timerID
 clearTimeout(timerID) 清除計時器 timerID
 setInterval("func", ms) 設定計時器每 ms 毫秒呼叫函數 func 一次, 傳回 timerID
 clearInterval(timerID) 清除計時器 timerID


這四個方法正常要用 window.setTimeout() 呼叫, 但因為 window 物件為最上層物件, 可省略物件名稱 window 直接呼叫方法, 故常被認為是 Javascript 的全域方法.


八. 物件 :   

Javascript 的資料不是基本型別 (primitive) 就是物件. 物件包括下列三種 :
  • 物件字面值 : 例如 obj={a:1, b:2, c: 3}
  • 陣列 : 例如 arr=[1, 2, 3]
  • 正規表達式 : 例如 /abc/
物件之特性如下 :
  • 屬性是可變的 (mutable)
  • 以參考 (位址) 進行比較
這與基本型別是截然不同的, 物件內容預設是可變更的, 例如 :

> var a={};             //空物件
> var b={};             //空物件
> a.name='Tony';    //可任意新增屬性
'Tony'
> a.name
'Tony'
> a.name='Kelly';   //可隨時更改屬性值
'Kelly'
> a.name
'Kelly'

儲存區域也不同, 基本型別資料存放在堆疊區, 比較的是值 (內容), 只要型別相同, 值也相同, 則全等比較 (===) 會傳回 true :

> var a='Tony';
> var b='Tony';
> a==b               //值相同
true
> a===b             //值 & 型別相同
true

物件則存放於堆積記憶區, 在堆疊區中的物件變數儲存的是指向此物件的參考位址, 因不同物件位址不同, 因此 == 與 === 比較均傳回 false, 但若與複製參考變數比較則傳回 true :

> var a={name:'Tony'}    //a 儲存物件之參考位址
> var b={name:'Tony'}    //b 儲存物件之參考位址
> a==b          //兩個不同物件, 參考位址不同
false
> a===b        //兩個不同物件, 參考位址不同
false
> var c=a      //c 儲存與 a 一樣的參考位址 (複製參考)
> a==c          //a 與複製參考 c 內容相同 (指向同一物件)
true
> a===c        //a 與複製參考 c 內容與型別相同 (指向同一物件)
true


1. 物件字面值 (literal) :

物件封裝了多個資料與函數於一體, 物件的字面值用 {} 定義, 例如下面的物件定義了兩個屬性 firstName 與 lastName, 以及一個方法 getFullName() :

var obj={
  firstName:'Tony',                   //屬性
  lastName:'Huang',                 //屬性
  getFullName: function() {    //方法
  return this.firstName + ' ' + this.lastName;
  };

屬性與方法可用 . 運算子存取, 例如 obj.firstName 會傳回 'Tony', 而 obj.getFullName() 會傳回 'Tony Huang'. 以下是在 Node.js 的 REPL 介面的測試結果 :

> var obj={
... firstName:'Tony',
... lastName: 'Huang',
... getFullName: function() {
..... return this.firstName + ' ' + this.lastName;
..... }
... };
undefined
> console.log("%j", obj)     //%j 為以物件格式輸出
{"firstName":"Tony","lastName":"Huang"}
undefined
> console.log("%s", obj.firstName)     //%j 為以字串格式輸出
Tony
> console.log("%s", obj.lastName)
Huang
undefined
> console.log("%s", obj.getFullName())
Tony Huang
undefined


2. 陣列 : 

陣列是索引與值的映射 (mapping), 其中索引是零起始的整數 (範圍 0~2**32-1), 而值稱為陣列的元素 (element), 元素必須為同質, 亦即需為相同資料型別. 陣列是一種物件, 可用 length 屬性存取陣列長度, 例如 arr.length; 存取陣列元素則使用中括號與索引, 例如 arr[0].

(1). 建立陣列的方法 : 

建立陣列的方法有三 :
  • 陣列字面值 : 將元素以逗號隔開放在中括號內, 例如 :
    var arr=['a', 'b' ,'c'];
    var arr=[];   //建立一個長度為 0 的空陣列
    arr[0]='a';    //新增元素
    arr[1]='b';    //新增元素
    arr[2]='c';    //新增元素
  • 呼叫 Array() 函數 :
    var arr=Array(3);    //建立一個長度為 3 的空陣列
    arr[0]='a';    //新增元素
    arr[1]='b';    //新增元素
    arr[2]='c';    //新增元素
  • 使用 new Array() 建構子 :
    var arr=new Array(3);
    arr[0]='a';    //新增元素
    arr[1]='b';    //新增元素
    arr[2]='c';    //新增元素
陣列是一種物件, 物件是可變的 (mutable), 可任意調整其長度或增減其元素, 因此可先建立空陣列後再新增元素.

使用字面值建立陣列時, 最後元素後面的一個逗號會被忽略, 但若有一個以上的逗號卻會被認為是空元素, 例如 :

> var arr=['a', 'b', 'c',]      //'c' 後面的逗號會被忽略
> arr.length
3
> var arr=['a', 'b', 'c', ,]    //'c' 後面的兩個逗號會被認為有一個空元素
> arr.length
4 

其次, 雖然呼叫 Array() 或使用 new Array(), 也可以像字面值那樣直接將元素傳入 () 內來初始化陣列, 例如 :

var arr=Array('a', 'b', 'c');  或者 var arr=new Array('a', 'b', 'c');

但最好不要這樣子用, 因為如果只傳入一個參數時必須為正整數, 因為ㄏJavascript 引擎會認為這是在設定陣列長度而非元素, 但若傳入兩個以上參數時卻會被解讀為元素, 因此用法上很容易造成混淆 :

> var arr=Array(3)      //傳入一個正整數 : 陣列長度
> arr.length
3
> var arr=Array(3, 5)  //傳入兩個以上整數 : 陣列元素
> arr.length
2
> var arr=Array(-3)    //傳入一個參數時, 必須為正整數
RangeError: Invalid array length   

建立二維陣列字面值的方式是將一維陣列當作其元素即可, 例如 3*3 陣列 :

var arr=[[1, 2, 3], [4, 5, 6], [7, 8, 9]];

或者用兩層 for 迴圈 :

var arr=[];
var n=0;
for (var i=0; i<3; i++) {
   arr[i]=[];
   for (var j=0; j<3; j++) {
       arr[i][j]=++n;
       }
    }
   
存取二維陣列用雙重中括號, 例如 arr[1][2].   

參考 :

# 從 ES6 開始的 Javascript 學習生活 (電子書)
JavaScript 程式設計與應用:用於網頁用戶端 (電子書)
你不可不知的 JavaScript 二三事系列 (iT 幫幫忙教材)
# switch 語法
有號數字表示法
預設參數( Default parameters )
JavaScript 中利用typeof 檢驗運算元所代表的型別
你懂 JavaScript 嗎?
[Javascript] NaN是什麼?
Decimal to Hexadecimal converter
淺談JS中String()與 .toString()的區別
Javascript 基礎打底系列 (二) - null、undefined、NaN 的差異與檢查方式