命名記錄 1

Карта Одинокой Горы

記錄一下這兩年自己覺得還不錯的命名,本來是想累積更多再貼的。

Herculus

海克力士,用在測試服務名稱上,是希臘神話中的大力士,曾經完成十二偉業,這兩年還有部電影,取名源由其實是 Fate/stay night 中,他做為 Berserk 的 Servant 所持有的寶具,稱為 十二の試練,試練就聯想到 test ~

Erebor

用在資料庫相關的服務,哈比人歷險記的孤山的精靈語,因為孤山裡面有很多矮人的寶物,資料庫內的資料某種層面來說也算是個寶物。

Olympus

應該不用介紹來源了,用在 client 端自己寫的 API helper,取這個名字的原因是覺的客戶端跟 server 端要資料就如同在跟眾神請求一樣。


SmooshGate

之前應該沒在這邊提過這件事,總之就是之前 TC39 有個 flatten 的 proposal,搞一搞後,發現這東西實做啟用後 MooTools 會爛掉,詳細的原因 Google 那邊有一篇文章解釋,標題就是 SmooshGate,Smoosh 這個單字其實有點少見,我大概查一下,雖然有說是 flatten、squash 的意思,不過目前覺得應該是作畫時把顏料抹平那種動作感覺最對吧,總之,當初為了這個問題第一個提案的改名就是smoosh,不過這個單字整體感覺和 flatten 差距實在太大,所以出現一堆聲音,有的是建議加上"use es2019"的 statement 來開啟flatten,像是 stirct mode 一樣,有的建議其他名字,當然也有些人是覺得管 MooTools 去死的,不過因為 Don't Break the Web 的大原則所以還是要處理這個問題。

其實我覺得當初提案 smoosh 的人搞不好是故意挑這個字的,藉此增加話題性引發討論和建議,效果其實很好,在 Twitter 上還出現了#SmooshGate這樣的 hashtag,不過,總之在最近一次五月的 TC39 會議,確定 proposal 改新的名稱:flat,雖然詞性不同,不過大家都還蠻可以接受,或許也可能是相較於 smoosh 來說很可以接受吧。


ESLint Plugin 入門

ESLint

最近寫了個簡單的 ESLint plugin,來記錄一下一些基礎知識,我做的 plugin 很簡單,叫做eslint-plugin-no-parameter-e,這個 ESLint plugins 做的事情只是檢查所有 function 的參數,然後如果有任何一個參數名是e的話就警報,這條 rule 其實是為了避免把errorevent簡寫成e,會容易混淆。

接下來進入正題,ESLint 基本上就是透過ESPree這個 parser 先把程式碼轉為ESTree相容的 AST,EStree 是個 de facto standard,是從 Mozilla Spider Monkey 用的 AST 演化而來,現在幾乎做 JavaScript 工具,會需要轉 AST 的話都會用這個格式;有了 AST 後,才來分析 AST 做檢查,然後現在有工具叫AST Explorer,非常方便,可以線上直接修改 code sample 看 AST 變化,可以用它來看你想要處理的 code 的 AST 結構,至於怎麼寫 rule 就看個人了,基本上就是監聽要注意的 node,然後檢查 AST 結構,有問題就呼叫 report 這樣。

第二點,npm module 的名稱要用eslint-plugin-開頭,官方說的規則,應該不遵守還是可以抓的到,不過就還是遵守一下免的有意外。

第三點,測試其實 ESLint 有 RuleTester 可以拿來寫測試用:

const rule = require('../rule.js')
const RuleTester = require('eslint').RuleTester

const ruleTester = new RuleTester();

ruleTester.run('no-parameter-e', rule, {
  valid: [
    'function a (event) {}',
  ],
  invalid: [
    {
      code: 'function e (e) {}',
      errors: [{ message }],
    }
  ],
});

很方便,都不用 test framework 了,並且有特別要求 valid 和 invalid 都要有 test case,不然測試就會失敗。

然後測試的時候是每個 rule 獨立跑,每個 plugin 可以有多個 rule,很多 plugin 是把不同 rule 都獨立一個檔案,每個 rule 可以丟的東西除了檢查外還有不少,像是說明文件、自動修復的動作等,詳見官方文件,我一開始是參考eslint-plugin-import的,不過現在初心者應該也可以先看我的eslint-plugin-no-parameter-e,東西更少一些。

下一個想來挑戰處理空行,看了一下感覺是比較困難啊~


Immer 原理

前陣子有個蠻有趣的 library 叫Immer,是 MobX 的開發者 Michel Weststrate 做的,這個 library 做的事情很有趣,它整合了 immutable 資料和原生資料的特性,反過來從缺點來看,immutable 資料型態的問題就是操作比較不方便,所有的修改動作都要透過 method 來執行,不能直接用 assign 的,有時候要改比較深層一點的資料就很麻煩,像 Facebook 的immutable.js就需要用getInupdateIn來處理:

getIn({ x: { y: { z: 123 }}}, ['x', 'y', 'z']) // 123

const original = { x: { y: { z: 123 }}}
setIn(original, ['x', 'y', 'z'], 456) // { x: { y: { z: 456 }}}

用陣列丟每層的屬性名稱,也有一些是用.切分的 path 來處理這個問題(像是prop1.prop2.prop3這種結構),而原生資料的缺點,在這個場景來看當然就是不 immutable 了,Immer 就提出了一個新的構想,把這兩者的優點結合在一起,讓資料可以保持 immutable 特性,又可以直接修改,當然不能直接修改 JavaScript 行為,所以還是有些地方需要等價交換,就是修改資料的時候,要包進 produce function 內:

const nextState = produce(baseState, draftState => {
    draftState.push({ todo: "Tweet about it" })
    draftState[1].done = true
})

然後得到的nextStatebaseState就會是不同物件,就像是 immutable 物件一樣行為,所以如果沒修改就還是同個物件,初看覺得有點黑魔法,不過思考過後覺得也不是不能做,有了些假想的實做方法後去研究了一下程式碼,不太意外的其實在 produce 裡面拿到的 draft 物件,是一個Proxy包裝過的物件,然後 immutable 相關的邏輯都做在 Proxy 內,produce 跑完後再把新的值 finalize 取出用 plain object 傳回給nextState,當然因為 Proxy 是比較新的東西,所以針對 ES5 也有另外的處理,我大致看一下就是比較土法煉鋼的下去比對,至於為什麼不全部都這樣做應該是效能考量吧。其實我覺得比起實做的原理,能想到這樣設計實在是很厲害,不像大部分人早就放棄了,還持續思考是不是有更好的作法可以整合兩種資料格式的優點才有機會找到這條路。

最後,Immer 這名字的由來,雖然在德文有這單字,不過我判斷應該還是從 immersive 來的吧。


PEG.js

pegjs

知道這東西也好一陣子了,最近才真的第一次用,感覺還不錯,很久沒有因為東西會動而這麼高興了,大概也是太久沒努力離開舒適圈的關係吧。

總之,最近想著要做出類似一些搜尋引擎支援的條件語法,像是 and、or、not 之類的,稍微花了點時間調查一下確定要正確的處理就是要個 parser,沒錯,就是 compiler 最前面那個 parser,身為非 CS 領域出身的人,compiler 我一直是朦懂朦懂的,parser 到產生 AST 那塊算是比較清楚一些,因為像是 Babel、還有以前幫忙過的TernJS都是先 parse 程式碼產生 AST 才開始做事,不過這次和以前不一樣的是我要從頭開始建立一個語法的 parser,然後因為是網頁前端要用的,所以就找到了PEG.js這個用 JavaScript 寫的 parser generator,相較於手工的 parser,這種工具只要有定義好的語法(grammer)給它,它就可以產生出對應的 parser,至於什麼是語法(grammer)呢,例如下面這段就是:

IdentifierName ::
    IdentifierStart
    IdentifierName IdentifierPart

IdentifierStart ::
    UnicodeIDStart
    $
    _
    \ UnicodeEscapeSequence

IdentifierPart ::
    UnicodeIDContinue
    $
    _
    \ UnicodeEscapeSequence
    <ZWNJ>
    <ZWJ>

UnicodeIDStart ::
    any Unicode code point with the Unicode property &ldquo;ID_Start&rdquo;

UnicodeIDContinue ::
    any Unicode code point with the Unicode property &ldquo;ID_Continue&rdquo;

這段是從 ECMAScript Spec 內找出來的,identifier 名稱格式的語法(grammer)定義,其實還算蠻好理解的,而 PEG.js 也有自己定的語法格式,只要使用該格式定義好語法,就可以產生出 parser 來,不過當我開始寫的時候,才發現到一個問題:我不知道 parse 後要產生什麼東西,這時我才意識到,在開始定義語法之前,我應該要先想清楚後續的產出物(例如 AST)的結構,和要如何使用這個 parser 的產出物實做出真正想要的效果。

以我的目標來說,我希望可以做出簡單的邏輯組合,包括 and、or、not 和 parenthesized expression(括號包起來的),其實我一開始的想法也沒很明確,只是覺得應該可以用樹狀結構加上遞迴來實做後面的判斷,然後參考了Kibana裡面 Kuery 的語法,也算是慢慢的把語法和 AST 的組合方式定義出來,當時做的語法我還有放在gist上,語法和 AST 定義好的時候,其實後面應用端的 script 還沒寫,不過因為結構很簡單,所以我已經確信一定可以運作了,後來隔一天果然不花什麼時間就把應用端的 script 也寫好,之後還花時間作了些手工測試,修正了一些語法上的細節問題,像是支援&|這些符號之類的,還有符號兩邊不用空格等等。

還有一點想特別說的是,其實一開始定義語法的時候,我是沒有想要去參考 Kibana 的,雖然我當時就知道 Kibana 的 Kuery 語法和我的需求很像,而且也是用 PEG.js 做的,不過我開始寫語法定義沒多久就卡關了,卡關的地方就是,一開始就是 and、or、not、parenthesized expression 都有可能出現,但是這無法用/的方式來處理,因為 PEG.js 的 parser 不會解析到一半發現不對就游標往回退(backtracking),然後我就卡關了,我可以寫出 and 加上判斷,支援以下兩種查詢:

keyword
keyword1 and keyword2

但是卻無法更進一步加上支援or,結果只好去參考 Kuery 語法,發現奇妙的寫法,以下是我後來成品的定義:

start
  = orQuery?

orQuery
  = left:andQuery Or right:orQuery
  / andQuery

andQuery
  = left:notQuery And right:andQuery
  / notQuery

notQuery
  = Not right:subQuery
  / subQuery

subQuery
  = '(' ws* query:orQuery ws* ')'
  / queryValue

如此,or查詢支援兩種內容,第一種是and查詢語句,第二種才是真的or查詢,但是他的第一個元素是and查詢,也就是說雖然是or查詢的判斷,但是卻先去看有沒有and查詢,然後and查詢也是類似的定義,實際上先去找有沒有not的語句,然後not會去看有沒有子查詢(parenthesized expression),整個讓人覺得很神奇,仔細下去推敲也確實可以理解判斷的過程,不過在邏輯上我還不太能完全通透的理解。第一次看到這種定義方式時,覺得很神妙,不過也有想說這應該是什麼常見的 grammer 寫法,後來去查了一下 ECMAScript Spec,發現也是這樣的作法,看來真的算是個 convention 了吧(看起來是left recursive),真不知道第一個寫出這種 grammer 的人腦袋裝什麼。

最後我的成果有丟一個可以讓人用的版本上 GitHub,也有用 NPM 發佈,叫simple-search-query,詳細用法可以參考 README,至於完整的語法定義就在query目錄內,還在補測試就是。


CodeceptJS + puppeteer

看起來一切似乎都很美好,直到真的下去用。

這幾天就在這組合裡面打滾,昨天還花了幾乎半天在查一個問題,總之先條列一下目前覺得幾個重點:

  • CodeceptJS文件裡面有 code sample 用 generator function 的非同步取值,現在支援用 async await 了,不過 code sample 還沒改。
  • 每種 helper 可以用的 method 不完全相同,大部分一樣,不過也沒列出基本組合,所以好像也不是很好一組 test 測所有 helper。
  • Puppeteer helper 裡面其實有很多地方是直接跟 CDP(Chrome DevTools Protocol) 溝通的,這部分也可以印 debug log:env DEBUG="puppeteer:protocol" codeceptjs run --steps --verbose
  • 開 CDP 的 log 的話資訊量會超多,訊息內容還算好理解,細節網路上也有文件,左邊 sidebar 有很多不同領域的,上面的 DEBUG 參數也可以自己修改只顯示想要的,詳見puppeteer 文件
  • puppeteer 的page.goto有個選項是 waitUntil,預設是 load 事件,不過我發現這個事件有時候會觸發不到,雖然我看開發工具的 network 圖是有線出來,不過總之我後來會這樣的案例就先都改成networkidle2了。

然後昨天花很多時間查的問題已經上去發了issue,總之就是發點擊事件點連結後,要檢查新頁面的內容會出現錯誤:

Protocol error (Runtime.callFunctionOn): Cannot find context with specified id undefined

目前探究下來狀況應該是:puppeteer 的點擊回傳的 promise,在點擊完成就 resolve 了,這時候瀏覽器去開新網頁,才要開始發出請求,新的網頁還沒準備好,所以要做檢查的時候就會沒有 context。然後我有用 Nightwatch helper 測試過,是沒這問題的,總之就是個實做問題,puppeteer 目前這樣邏輯上也不算是錯誤的設計,不知道最後會怎麼修改,當然簡單一點就是 click 觸發 browser navigate 到別的網頁時就要等新網頁回來。目前的 work around 是自己多 wait 一下。

最後就是,我終於可以順暢的把 puppeteer 這個單字打出來了QQ


分號大戰 again

今天一早起來就看到有人說 TC39 要準備建議 JavaScript 程式碼應該要加分號:

然後下面就一大串了,本來想說standardjs要哭哭了,難道semistandard要扶正了嗎?不過我仔細端詳了一下,發現這個PR還是 open 狀態,而且 Brendan Eich 甚至表態反對:

原因之一是已經有很多 standardjs 的 code 其實運作的很好,不過另外一個原因我覺得更有力,就是 TC39 的文件,做為 spec 似乎不該提出建議,當然提出這個 PR 的 Daniel Ehrenberg 其實也不是單純因為支持加分號才提的,他其實是Class field declarations的主要貢獻者,這是什麼呢?就是:

class Counter extends HTMLElement {
  x = 0;

  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }
}

這種在 class method 外面定義 class 屬性和預設值的語法(另外還有 private property),而這種很像是 expression 的語句,一定要 semicolon,不然會有他稱為 AST hazard 的情形,也就是很難評斷開發者實際上意圖的情形發生,也就無法用 ASI 自動補分號,對此 Brendan Eich 有個建議是在 class field 裡面關掉 ASI 機制,也就是這些 property 定義一定要加分號做結。

目前看起來,Brendan Eich 提的那點,TC39 不該做語法的建議實在很強而有力,應該也反駁不了,所以結果應該就是沒有收 PR,或是改成更中性的文字,不是建議的文字,不過想來想去還是沒有比較適合的,畢竟在 spec 文件裡面還提建議、警告開發者用的語法就很怪。


Web F2E 看 Python Syntax

Bruce Eckel's keynote,

雖然主業是 Web Front End,不過其實要搞好 Front End,後端也不可不知,所以我工作內容其實也寫 Python 寫了不少,最近終於可以跟Flake8相安無事,所以想來記錄一些對我來說很有趣的 Python Syntax,不全是喜歡的就是了,以下內容以 2.7 為主。

內建支援 String Formatting

第一個我覺得很棒的是,Python 內建有String Formatting Operations可以用,超方便的,所以我只要寫:

'%d: The answer to the ultimate question of life, the universe and everything' % 42

就可以把 42 填進去字串裡面了,雖然我第一次看到放最後面還以為是什麼奇妙的註解符號;傳統的 formatting 用來做翻譯字串就會發現,如果有多個變數,它們的順序在不同的語言可能有不同,傳統的 formatting 只能處理固定順序,不適合這種情景,這時候還有新的format()可以用,幫變數命名好、然後丟參數進去就可以了,例如:

'{author} wrote {name}'.format(author='JRR', name='TLOR')

雖然 Python 的 string format 很好用,不過文件寫得太高深了,所以還有人做了pyformat.info這個站,收集了不少實用的範例幫助大家理解,而且仔細看過之後發現舊的格式也可以用 dict 格式丟命名變數進去。

Multiline String

多行字串也是我蠻喜歡的,像是要弄 template 的時候就很方便,JavaScript 一直到 ES6 的 tempalte string 才算是有內建,Python 就用三個引號框起來就可以了:

template_string = """<div>
                       Wow
                     </div>"""

不過很理所當然的,那些為了縮排所填入的空白,就都是真的字串內容,所以Wow前面就是有 23 個空白字元,如果字串在 class 或是 function 定義裡面,那空白就會更多,在一些使用情境下,空白數量是影響很大的,所以就會有到底該怎樣排的問題,StackOverflow 上就可以找到相關的問題,還好我處理 HTML template 的話,影響不大,當然結果會造成一些多餘的資料傳輸啦。

Keyword Arguments

很久以前我寫過一篇options object的文章,為的就是處理參數太多造成程式碼不好解讀的問題,沒想到 Python 可以在呼叫函數的時候,傳入參數的名稱,例如以下的函數:

def func(a, b, c, d):
    return a + b + c + d

呼叫的時候可以分別說明每個參數的 key 和 value 對應關係:

func(a=1, b=2, c=3, d=4)

而且也可以混用:

func(1, 2, c=3, d=4)

覺得這語法真是領先超多,當然 ECMAScript 現在可以用 destructing assignment 的語法做到類似效果,不過我覺得還是有些差距。

而針對 Keyword Argument 其實還有特殊的 syntax 是**kwargs,其實我一開始是先看到這個語法的,想說 Python 怎麼有個很像 C++ 指標的東西,看了許久,某天終於會意到 kw 是 keyword 的意思,然後才終於理解是怎麼回事,後來查資料才知道還有*args,現在的 ECMAScript 的話可以用...spread operator 做到。

Circular Dependency

恩,可以做到循環相依,第一次看到真是覺得不可思議,不過後來慢慢瞭解限制,大概也知道怎麼實際上是如何跑的了。

Ternary Operator

三元運算,Python 的語法真的是比較特別一點,其它語言比較常看到的是用?,不過 Python 是用後置的if else

reality = True if isReal else False

其實我還蠻喜歡後置的if語句,第一次看到這種寫法是在 CoffeeScript,我很常用在一些參數特殊狀況的處理,一樣 CoffeeScript,不用後置if的時候:

filename = file.name

if file.hash
  filename = filename + '-' + file.hash
  
if file.ext
  filename = filename + '.' + file.ext

用了後置if的話可以寫成:

filename = file.name

filename = filename + '-' + file.hash if file.hash

filename = filename + '.' + file.ext if file.ext

看起來整齊許多,視覺上(?)少了一層縮排,不過 Python 的三元運算,和 CoffeeScript 的後置if語法是不一樣的東西,雖然可以用來做類似的事情,但是因為他是三元運算,所以一定要提供else區段:

filename = filename + '.' + file.ext if file.ext else filename

就比較不喜歡這樣就是了。

Tuple

Python 的 List 資料型態可以比做 JS 的 Array、Dict 可以比做 Object,兩種資料型態分別是使用中括號和大括號,不過在 Python 語言裡,還有一種用小括號的 Tuple 資料型態。

Tuple 資料型態似乎還蠻少見的,我第一次聽到這個名詞的時候是在學校學資料庫系統的時候,一筆資料稱為一個 Tuple,不知道為什麼印象很深,然後第一次看到使用 tuple 的程式碼自然是不太理解,不過還算直觀看的懂,後來不知道為什麼查到這種語法其實是一種資料型態叫 Tuple 的,意義上和資料庫系統的 Tuple 感覺還蠻像的,理解這是個資料型態之後用起來覺得順手很多,而且 Python 還蠻自由,很多地方和 List 都可以用一樣的操作,像是in運算,或是作為 function 的多個回傳值(多回傳值的函數也蠻方便的)。

in 運算

上面提到的in運算,用來判斷一個 List 或 Tuple 是否包含特定元素:

if target.stat in ('ACTIVE', 'PREMIUM')
    ok()

對於常在古早 JS 開發的人,真的是超羨慕的,可能有人說可以用indexOf做,雖然 JS String 的 indexOf 很早就有了,但是 Array 的 indexOf 卻是到 ES5.1 才正式進標準,IE9 之前的都不支援,所以要用他來判斷一個元素是否在一個陣列內,首先要確定你不支援 IE8 之前的瀏覽器,不過就算支援,其實程式碼也沒in運算來的漂亮,後來 ES2015 有個比較好一點的Array.includes可以用就是了。

Dict

Dict 可以比做 JS 的 Object 比較好理解,對於這個我不能適應的有兩個地方,一是 Dict 不是 class,所以不能直接用.取屬性,一定要用[]或是內建的get(),再來就是用[]取屬性的時候,一定要 key 存在,用到不存在的 key 就會噴錯誤,如果一定要這樣操作就要改用get(),get 還有一個特點是可以給 default 值,如果是複雜的結構,想要一口氣很深入就可以寫成:

data.get('attr1', {}).get('attr2', {}).get('attr3', None)

實在是有點難看,CoffeeScript 是有 Existential Operator 可以做這種多階層的取值:

data.attr1?.attr2?.attr3?

在 TC39 的草案也有類似的Optional Chainging,這兩樣都是上一篇文章有提到的東西。

Unix Timestamp

內建的 datetime 似乎沒有支援直接輸出 Unix Timestamp,是說目前有需要都用Pendulum,還蠻好用的,API 介面也蠻直接,也有完整的時區、Period、Duration 等觀念。

PEP8, Flake8

文章一開始提到的 Flake8 把好幾個 code checker 包進去,包括了官方的PEP8、PyFlakes、pycodestyle 等,我用 Vim 的Syntastic都有支援,只要有安裝就會偵測到執行檔,然後就可以用來檢查了,一開始裝起來的時候就和第一次用 JSLint 一樣傷感情,不過兩個月過後到是還蠻適應的,其中比較和以往習慣不一樣的就是 function 參數的值,不論是定義時的 default value 還是呼叫時的 keyword argument,=的左右兩邊都是不加空白的,例如:

def hello(name='John'):
  return 'Hello ' + name

hello(name='Hancock')

另外就是特殊情況需要循環相依,或是 import 但是不使用時,會需要關閉一些檢查,可以在該行末端加上註解關閉特定項目:

import pages  # noqa: F401

錯誤的編號可以參考 Flake8 的文件

Python Enhancement Proposals (PEPs)

之前在研究 Joda Time 的時候,發現 Java 有個JSR(Java Specification Requests),在找 Python 的 coding style 的時候則是發現了PEPs(Python Enhancement Proposals),不過 ECMAScript 目前是沒有類似的、完整的收集並編目各個 Proposal 文件的地方,甚至連語言本身的官網都沒有啊...XDrz


問號出頭天

Mario

剛剛掃了一下 TC39 新的草案,發現和?相關的還不少,稍微來介紹一下這幾個很初期的草案吧:首先第一個是已經見過一陣子的 Optional Chaining,第一次看到這種語法是CoffeeScript,在 CoffeeScript 現在是叫 Existential Operator,不知道是不是以前就這個名字,對付多階層的物件特別好用:

let zip = lottery.drawWinner?.()?.address?.zipcode

可以像這樣用,中間任何一層回傳 falsy value 就會直接把值給 zip 變數,而不會繼續往下找,不會造成 Script 執行錯誤,不過目前看起來對於 function 的處理比 CoffeeScript 麻煩一點,要寫成?.(),而不是?(),其實我覺得也比較醜一些。

第二個是Nullish Coalescing,這是正港的用來設定變數 default 值用的,以前通常的作法是用||

function (option = {}) {
  let quick = option.quick || true;
}

在上面的範例中,quick預期是 boolean 值,可能是trueflase,預期的預設值是true,不過這樣寫其實,如果傳false進來會誤判,結果會變成用預設值的true,新的 Nullish Coalescing 就是要來解決這個問題,把||換成??

function (option = {}) {
  let quick = option.quick ?? true;
}

這樣傳false時就不會被當成沒傳值,只有nullundefined才會用預設值,其實判斷的方式和第一個 Optional Chaining 一樣。

第三個是Partial Application,其實就是字面上的意思,不過是從 syntax 上就支援 partial 執行 function:

function add(x, y) { return x + y; }

const addOne = add(1, ?); // apply from the left
addOne(2); // 3

我覺得用?這招還蠻漂亮的,雖然我還不太有機會用到 就是。


Temporal - 下個世代的 Date

這篇文章寫到快寫完的時候,決定到Modern Web 2017分享,所以就比較晚發佈 ,其實 Modern Web 演講內容比較多,文末有放相關參考資料。

JSConf EU 2017 前陣子放出演講影片,蠻多場次都不錯,這篇要主要是從其中的一場演講而來,演講是「 The Past, Present, and Future of JavaScript Date and Time APIs」,講者是 Matt Johnson,Moment.js的作者,下面是這場演講的影片:

長度不長,推薦可以看一下,主要是在談 JavaScript 的 Datetime,提出這老東西的問題,我覺得可以稱為 WAT JavaScript 的 Datetime 篇,像是 0 起始的月份、不支援 Time Zone、難以運算、是 mutable 物件等等,接著介紹了目前檯面上比較多人用的幾個 library 和他們的特色,都是品質不錯的 library,有需求的可以從中選用,包括了:

最後則是提到他們目前在進行的,改進 JavaScript Datetime 的計畫,也就是新的 ECMAScript Datetime 的 proposal,叫temporal,除了 Matt Johnson 之外,還有一位 Microsoft 的 Maggie Pint 也是目前草案的主力推手,他的 blog 上就有兩篇相關的,裡面有列出目前Date的主要問題:

  1. 不支援 timezone,只有 UTC 和 local
  2. Parser (轉譯日期字串轉成日期物件)的行為不可靠且難以使用
  3. Date object 是 mutable 物件
  4. 日光節約時間的行為無法預期
  5. 日期計算 API 很難用
  6. 不支援Gregorian以外的日曆(例如農曆)

事實上,目前的 Date 物件,當初 Brendan Eich 因為時間緊迫,所以 Datetime 的 API 是直接參考 Java 的,當時是 1995,參考的應該是 Beta 版 Java 的java.util.Date,後來 1996 年 1 月 Java 1.0 發佈,但是到了 1997 年 2 月的 Java 1.1 發佈時,java.util.Date大部分的設計都被捨棄了,然後 1997 年 6 月,ECMAScript 標準 1.0 發佈,結果這個在 Java 語言只活了 1 年多的設計,就活在 JavaScript 世界活了 20 年之久,相信有用過的人都能多少都知道使用起來的痛苦。

不過要改善 JavaScript 從來就不是一件容易的事情,最大的困難點就是你不能隨便改動任何已有的東西,像是已經存在 20 年的Date,即使它設計不好,隨便改動都可能造成大量的網站壞掉,不像是 Java 1.0 升級到 Java 1.1 那樣,各自用各自的,在改善 JavaScript 時基本上就是要當成有人從不升級,不能有 broken change,最簡單的方法就是增加新的東西,而不要去修改舊的,這也是目前 temporal 的方向(其實 ES 5.1 後,舊有的東西該修的東西大概都修完,之後就是一直加新的而已),在 draft 文件已經有一點基礎和預期的 code sample 了:

var ldt = new CivilDateTime(2017, 12, 31, 23, 59);

var addHours = new CivilDateTime(2017, 12, 31, 23, 00)
    .add(2, 'hours');

var zdt = ldt.withZone('America/New_York');

可以看到有方便的加減時間的 API、immutable 特性、還有時區支援等等。事實上這份草案還非常初步而已,還缺非常多細節,預期會有的物件目前已經有八種了,不過這八種物件的 API 也都還沒定義完,不過也正因為如此,想參與的人反而這時候比較有機會提供想法,推薦有興趣的人可以關注關注,給點意見。

最後列一些參考文件:


此類別所有文章