JavaScript Primitives Objects

接續上一篇文章,JavaScript 的資料型別可以分為兩類:基本型別(primitive value)和物件(object)。基本型別包含字串、數值、布林值等,物件除了物件外,還有像是函數、Regular Expression 等。JavaScript 還有一組很特別的 constructor,對應到每種基本型別,所以你可以new String('blah');來產生基本型別的物件:

new String('blah');
new Number(42);
ner Boolean(true);

如果看最近的書本和教學都會說不要使用這種方法來建立這些基本型別,缺點有效能問題、API 設計不好,會有混淆等等。不過如果是物件的話,不就可以很方便的做一些操作了嗎?

這兩年很受歡迎的 Ruby 有一個特性就是所有的基本型別都是物件,所以就可以很簡單的用一些串接的方式直接對這些基本型別進行操作:

str = 'hello'
str.capitalize        #  Hello
str.capitalize!
str                   #  Hello
str.count('l')        #  2

在 JavaScript 中的基本型別其實不需要以物件的形式,也可以做類似的操作:

var str = 'hello';
str.length;           // 2
str.contains('l');    // true
str.indexOf('l');     // 2

但是如果想要更進一步的在上面加上屬性或是 method 就會發現行不通:

var str = 'hello';
str.target = 'world';
str.target;            // undefined

這中間到底是有什麼差異呢?這個問題我想了很久,直到看了 The Secret Life of JavaScript Primitives 這篇文章才瞭解,最後面這個例子,其實等同於:

var str = 'hello';
(new String(str)).target = 'world';
(new String(str)).target;

當使用物件形式的.運算子對基本型別資料操作的時候,會產生一個新的物件,然後用完就丟掉,所以在指派 target 屬性那行操作到的物件,和要取回 target 屬性那行的物件,是完全不一樣的兩個物件,這也就是為什麼這些資料可以用類似物件的方法操作,但是卻不能真的像物件一樣使用。


JavaScript is Untyped

untyped

上週去聽 Gias Kay LeeAnimation in AngularJS,說到 JavaScript 其實是 untyped language,因為我沒受過 Computer Science 的正統教育,這名詞對我來說是新的,所以回來花了些時間看網路上的文件。

Wikipedia 上的文章不少,不過看起來比較吃力,Stack Overflow 上也有幾則不錯的問答,對我來說幫助很大,這個問題其實也沒很複雜,在 Computer Science 上來說,是不使用 strong type 和 weak type 來形容程式語言的,因為沒有明確的定義,取而代之的,是數個有明確定義的相關特性:type safety、memory safety、static/dynamic type checking、type conversions。

所以我之前 文章 說到 JavaScript 是 weak type 那段其實應該要說是 untyped 會比較好。其實講到 type 的問題,就會想到 JavaScript 的效能問題,Brendan Eich 說過 JavaScript 引擎很難跑得快的原因就是因為它是 untyped 的。在 Performance Tips for JavaScript in V8 這篇文章有提到,V8 引擎其中有一個提升速度的設計是給變數 hidden class,這個 hidden class 其實就是隱藏的變數型別,如果你的變數從頭到尾都維持同樣的型別,例如整數永遠是整數、字串永遠是字串,而不會有中間換型別的情形,那 V8 引擎就可以利用 hidden class 的資訊來提升運算速度,我覺得這個 tip 很不錯,不只是 V8 可以跑比較快,即使其他引擎沒有受益,作為寫程式時的 good practice 也很受用,減少了程式內的不可預期性。

另外一個我覺得很相似的例子是這兩年新的 asm.js,asm.js 是一個 JavaScript 的子集,設計上可以直接做一些比較低階的操作,像是直接 allocate 記憶體還有 Typed Array 等,而其中還定義了一組相容於普通 JavaScript 的寫法來宣告變數的型別:

function f(x, y) {
    x = x|0;      // int parameter
    y = +y;       // double parameter

    return;
}

只是 asm.js 會直接手寫的應該很少,像 asm.js 有名的例子: Unreal Engine 就是用工具 compile 過去的。

PS: 我還找不到上面那張圖片的投影片出處,有人知道的話麻煩告訴我,感謝。


autocomplpop + snipmate

autocomplpop + snipmate

我用 vim-snipmate 和 vim-autocomplpop 已經蠻久了,不過運氣不好的是兩個 plugin 的原作者都已經不維護了,過了許久,有幾個人接手了 snipmate,不過大改一陣子之後,原本兩者間可以合作的hack 就失效了,考慮一陣子之後我開始在 github 上 fork 了一版 autocomplpop 來維護,一來是我用 vundle 安裝方便,二來是希望有一天能重新把 autocomplpop 支援 snipmate 的功能再弄起來。

其實幾個月前我有研究過一次,不過那時候幾乎看不懂新版 snipmate 的程式碼,沒想到今天又看一次竟然看懂了,不知道是不是之前那版本來就很難讀就是,總之研究了一下可行性,發現似乎可以不用像以前那樣用 hack 的方法就可以讓功能復活,花了大約兩三個小時下去弄,真的成功了,感動。

這次不用改檔案,設定有設對

let g:acp_behaviorSnipmateLength = 1

輸入的時候輸入大寫就應該要看到 snippets 的候選項目了。

最後要補充一下,我 fork 的 autocomplpop 是原作者 ns9tks 拆出一些 component 的版本,所以會需要安裝 L9,新的 snipmate 也有相依的 plugin:vim-addon-mw-utilstlib,而且它也不含 snippets,東西在另外一個 vim-snippets,其實有點多,蠻混亂的,所以整理一下總共要裝哪些 plugin:


Vim Variable Variables

上一篇文章介紹 Syntastic 的最後面,其實本來想順便介紹一個 Vim Plugin 設定的 pattern,後來想一想還是獨立一篇文章好了,剛好今年 COSCUP 要介紹一些 Vim 的東西,這篇文章也可以作些 Vim 的介紹,就先從上一篇文章最後面的那行 Vim 設定開始看起:

let g:syntastic_coffee_coffeelint_args = "--csv -f ~/coffeelint-config.json"

在這行設定中,作的事情其實只是宣告並給一個變數值,而我想說的重點是這個變數的變數名稱:g:syntastic_coffee_coffeelint_args,這個變數其實是根據 Syntastic 的文件 5.2 節設定的,其實這個變數名稱裡面有 coffeecoffeelintargs 三個變數,在文件中是這樣描述這個變數的組成的:

syntastic_[filetype]_[subchecker]_[option-name]

表示的是 coffee 這種檔案格式下面,用 coffeelint 時的 args 參數,這種階層式的設定,在其他程式語言通常會用 dictionary 這種資料型態比較多,可是在 Vim 的圈子卻不是這樣,而是比較多用上面這種 Variable Variables 的形式,我想可能原因是: Vim Script 要建立多階層的空 dictionary 不是很方便,要一層一層的建立,而這個問題不管是開發者自己不方便,使用者在 vimrc 裡面作設定也會變得很麻煩。

那在 Vim Script 裡面,要怎樣實作這樣 variable variables 形式的程式呢?很簡單,和 PHP 很像,都是用大括號,所以要讀取最上面那個使用者設定的值很簡單:

g:syntastic_{filetype}_{subchecker}_{optionName}

這樣就好了,真的那麼簡單就好了...

Vim Script 和現在主流的 Scripting 語言差異蠻大的,有很多不太方便的設計,像是設定 list, dictionary 時如果要在不同行寫不同項目的內容,每行間都要加一個反斜線\,資料的型別是強型別,字串和數字都要自己手動作轉換,宣告和改變數的值都要用let等等,而這邊會遇到的限制是,無法存取任何一個不存在的變數,要是有存取到的話都會出現錯誤,即使只有讀取值也是,並不會直接給你 false value 或是像 JavaScript 那樣有undefined,所以要是使用者沒設定過這個變數g:syntastic_coffee_coffeelint_args,我在 Plugin 裡面就不能直接讀取他,不然只會看到紅色的錯誤訊息。

要處理這個問題,要用的是 exists 這個內建的 function,這個 function 是專門用來檢查變數是否存在用的,如果變數存在才做事情,寫起來就像是:

if exists('g:syntastic_' . filetype . '_' . subchecker . '_' . optionName)
    let args = g:syntastic_{filetype}_{subchecker}_{optionName}

實在是不太好看,不過寫 Vim Script 基本上就是常常要寫這樣難看的程式碼,做的都是一些土法煉鋼的事情,其實還蠻辛苦的,而且你現在應該可以想像,如果要用多階層的 dictionary 在 vimrc 裡面作設定會長什麼樣子了。


使用 Syntastic 執行 CoffeeLint 檢驗 CoffeeScript

我之前有 fork jslint.vim 出來做了一個 coffee-check.vim,不過前陣子開始改用 Syntastic,作者 Martin Grenfell 也是個蠻有名的 Vim plugin 作者,最有名的應該 nerdtree 了吧,Syntastic 則是一個通用的 syntax checker plugin,使用的是外部的 syntax checker,設計成可以任意擴充支援不同檔案類型,而且支援一個檔案類型多種 checker,並且沒有限制 syntax 檢查,只要外部程式會跟你說哪裡有問題就可以。

Syntastic 預設就已經有 CoffeeScript 用的設定,支援 CoffeeScript Compiler 和 CoffeeLint,前者可以用來作 syntax checker,後者則是 lint,實際上安裝起來蠻簡單的,系統的 coffee 和 coffeelint 指令都可以透過 npm 安裝:

npm install -g coffee-script
npm install -g coffeelint

Synatastic 用 Vundle 或 pathogen 安裝,然後你編輯完 CoffeeScript 檔案存檔時,就會出現檢查的結果了。

不過雖然安裝簡單,但是我還是遇到一個問題,就是 CoffeeLint 的其中一項檢查項目是每行的程式碼不要太長,預設是 80 個字元,不過這個數值對我來說太小了,所以我就必須要想辦法修改這個限制,CoffeeLint 有提供設定這些規定的選項,有點複雜,首先你必須要先有一個設定檔,而這個設定檔要 CoffeeLint 0.5.5 以後才有辦法用指令產生,運氣很不好的,npm 現在的 stable 版卻是 0.5.4,還不支援產生設定檔,我又不想要自己 compile 一份,所以只好上網搜尋,結果還真的給我找到範例,趕緊丟 gist 備份

設定檔內容就是個 JSON:

{
    "max_line_length": {
      "value": 80,
      "level": "ignore",
      "message": "Line exceeds maximum allowed length"
    }
}

還蠻好理解的(可是沒範例我怎麼知道格式是怎樣啊!怒!),改好我要的設定後,執行 coffeelint 指令時還要加上參數來指定設定檔:

coffeelint -f ~/coffeelint-configuration.json route.coffee

之前做到這步後我就卡住了,因為不知道怎樣設定成讓 Syntastic 執行時也接一樣的參數,直到今天早上跟 joseph 推薦 Syntastic 之後,才又花時間研究一次,終於發現其實可以在 vimrc 裡面設定特定檔案、特定 checker、特定設定的值:

let g:syntastic_coffee_coffeelint_args = "--csv -f ~/coffeelint-config.json"

前面的--csv是一定要的,這樣設好,我終於可以不用一直看到 CoffeeLint 跟我說我的程式碼太長了。


Options Object

Options Object

上一篇文章 DOM Event Module 有講到 function API 設計收 option 物件比不同設定的值分開接收還好,這篇就要介紹一下這個 pattern,其實這個 pattern 我看過好幾篇文章講了,想不到等我現在要寫文章時卻找不太到,因為關鍵字有點太通用,不過剛好最近看完的 Effective JavaScript 也有說到這個 pattern,所以就拿他當參考文獻了~~

這種設計的優點第一個當然就是前一篇有說到的,使用時的程式碼,用看的就知道不同的參數是什麼意義,直接偷 Effective JavaScript 的範例來,先看看不是 options object 的:

var alert = new Alert(100, 75, 300, 200, 
                      "Error", messaage,
                      "blue", "white", "black",
                      "error", true);

這個例子比我前一篇的例子還要複雜許多,相信更能夠感覺到這些參數分別的意義很難判斷吧,所以改成用 options object 的:

var alert = new Alert({
    x: 100, y: 75,
    width: 300, height: 200,
    title: "Error", message: message,
    titleColor: "blue", bgColor: "white", textColor: "black",
    icon: "error", modal: "true"
});

雖然其實我覺得全部的參數都塞到 options object 也不好,不過這問題不是這篇文章的重點,接下來要說的是另外一個優點,就是 API 介面可以和參數的數量脫勾(decouple),在未來更新改版增加新選項時也不用改參數的設計,這個優點要舉的例子是 jQuery.ajax,他的第二個參數就是 options object,可以看文件有詳細的介紹所有的可以用選項,然後有一些選項,像是 headers、converts 就是後來才新增的,而 jQuery.ajax 本身收的參數則是從 1.0 之後就沒有變化了。

收了參數之後,通常第一步要做的是填預設值,很多的 JavaScript Library 都有提供 extend 可以利用,以 jQuery.ajax 為例,它用的是特製的 ajaxExtend,當然 jQuery 也有提供 jQuery.extend 給一般的 case 使用,undersocre 也有 extend,不過它還有一個填預設值專用的 defaults,兩者的差別在當初 propose 的 issue 上介紹的蠻清楚,基本上就是用 defaults 你可以這樣寫:

_.defaults(options, defaultOptions);

但是如果用 options 要達成一樣的效果,你要寫成:

options = _.extend({}, defaultOptions, options);

因為 extend 會讓後者的屬性覆蓋掉前者的,所以 defaultOptions 要放在 options 前面,但是 extend 回傳的是前面那個物件,所以要取代原本的 options 還要多一個 assign,至於 defaults 則是會改原來的 options 物件,所以不需要多那個 assign,也不需要先丟一個空物件給它,如果要讓 options 是可省略的參數,用 CoffeeScript 還可以在定義 function 時給它預設值,整個程式碼就漂亮很多:

mylib.ajax = (url, options = {}) ->
    _.default(options, defaultOptions)

Native DOM Event Module

我是在看 tap.js 的程式碼時,才真的注意到 DOM 的 event module (事件模組)其實我們已經可以拿來用了,關鍵在於比較少人注意到的 dispatchEvent 這個 method,這個 method 可以讓你對任意的 DOM Node 觸發任意事件,不管是 click 事件、mousemove 事件,還是 keydown 事件都可以,所以理論上你可以用 JS 模擬所有使用者做的操作,要做 integration 測試也沒問題,不過像是滑鼠拖拉要模擬就有點辛苦了。而除了原生有的事件外,其實 dispatchEvent 還支援自訂事件,對於自訂事件用途還不清楚的可以先看看以前的文章

dispatchEvent 的用法和一般 JavaScript Library 的 trigger event 不太一樣,要先建立一個 event 物件給他:

var event = document.createEvent('Event');
event.initEvent(type, true, true);

domnode.dispatchEvent(event);

其中的 type 才是你想要觸發的事件名稱,而第一個 createEvent 收的參數,除了 "Event" 這個最通用的之外,還有很多種原生事件,如果是要自訂事件的話,可以用最通用的 "Event"。不過其實,還有一個比較新的 API:

var event = new CustomEvent(type, {bubbles: true, cancelable: true});

這是 DOM Level 4 裡面提出來的 Custom Event Constructor,在這之前的 Level 3 就可以用第一個範例的方法產生 custom event,要先 create 再 init。這邊岔題一下,應該有人會注意到,createEvent 後 initEvent 那邊的 API 設計和 CustomEvent 不一樣,initEvent 收三個值,後面兩個都傳了 true,可是這樣的 API 設計近年來都被認為是不好的,因為你看程式碼,你完全不知道這兩個 true 是什麼意思,所以都是建議用第二種 API 的設計,收一個 options 物件,其實,initEvent 那兩個 true 分別就是 CustomEvent 後面的 bubbles 和 cancelable,但是後者的設計就可以讓程式碼看一眼就知道那兩個 true 是什麼用途的,看到這種不止開發者的程式在進化,連標準也跟著一起演進的現象還蠻有趣的~

Custom Event Constructor 目前 IE 的支援還比較差,所以還不能直接使用,還是要做一下 feature detection。至於哪些物件可以使用這些 method,除了 DOM Node,外,其實一般瀏覽器都還有一些物件可以聽事件,像是 window、document、 XMLHttpRequest instance 等,很可惜不是任意物件都可以,事實上,這些可以使用 DOM Event Module 的物件都是實作了 EventTarget 這個介面,不過又要再說一次很可惜的,目前沒辦法把瀏覽器內的這些實作偷出來給其它物件使用,幸好 Andrea Giammarchi 有做了 一個 放在 Github 上,可以給一般物件繼承去用。

我曾經寫過一篇文章介紹 addEventListener 的第三個參數,這篇文章的最後我要來介紹第二個參數,第二個參數一般都是丟 function 進去,不過他其實也可以丟 EventListener 進去,基本上就是一個物件有 handleEvent 來當事件的 callback 而已,不過像 tap.js 就有很有趣的用法,它不管是什麼事件,在加上 addEventListener 時丟進去的都是 this 物件,實際上則是統一在 handleEvent 裡面在根據事件的不同去作不同的事情

Tap.prototype.handleEvent = function (e) {
    switch (e.type) {
        case 'touchstart': this.start(e); break;
        case 'touchmove': this.move(e); break;
        case 'touchend': this.end(e); break;
        case 'touchcancel': this.cancel(e); break;
        case 'mousedown': this.start(e); break;
        case 'mousemove': this.move(e); break;
        case 'mouseup': this.end(e); break;
    }
};

Dependency Injection of AngularJS

在公開 javascript-libraries-syntax.vim 之後,我收到的第一個 Issue 就是支援 AngularJS,因為我一直都沒寫過,所以就花了些時間下去研究,然後就看到一個很有趣的名詞:Dependency Injection

AngularJS 有一些很特別的參數,像是$scope$http之類的,只要你的 function 是在 AngularJS 的 framework 內,然後接收的參數用到這些名字,例如:

function($http, $q) {
    // do something with $http and $q   
}

這個 function 執行的時候,你不用管怎麼把正確的 $http 和 $q 傳給他,AngularJS 自己會幫你把他處理好,而這個特色就被稱為 Dependency Injection,當然除了內建的那些模組外,也可以自己註冊新的名稱對應到你開發的模組。

其實一開始看到這個特性的時候覺得有點神奇,很好奇怎麼做的,因為我想的到的就只有一個方法可以辦到,就是 Function.prototype.toString,這個 method 會把整個 function 的定義從第一行開始輸出成字串,然後分析字串把參數抓出來,這樣就可以在執行前知道需要哪些模組,本來一直期待是不是有什麼神奇技巧,而不是用 toString 辦到的,結果去看了看原始碼,發現真的就是這樣做的,一下子神秘感就消失了~~

Merrick 有一篇 JavaScript Dependency Injection,有比較清楚的介紹到 AngularJS 這塊是怎麼做的。不過其實我對於 AngularJS 的這個特性是不是能稱為 Dependency Injection 一直存疑。

在看介紹 Dependency Injection 的文章時,幾乎舉的例子都是在有 Interface 的語言下,程式內的的相依性都相依在 Interface 上,而不相依在實做的 instance 上,等到實際上跑的時候才根據情況丟不同的實做(implement),而 JavaScript 並沒有這層 interface 的設計,AngularJS 自然也沒有,為此我想了很久,也一直在找網路上的資源,直到昨天終於想通了。

Dependency Injection 的重點不在於用程式語言的 Interface 特性來把相依性抽離,而是利用這樣的設計,讓你的程式不要直接相依於實際的實體(instance),由於 JavaScript 是弱型別的語言,所以其實在定義 function 時,並不需要指定輸入參數的型別,利用這個特性,其實就已經可以達成 Depedency Injection 了,只要實際執行程式時,根據狀況丟入應該丟入的 instance 即可。

AngularJS 則是在這特性之上,利用參數名稱來當成相依的 Interface,實際上程式執行時會是那個模組的實體傳入,就要看是哪一個模組註冊到對應的名稱了。

在我終於想通之後,發現到其實 RequireJS 也是很相似的架構,所以就搜尋了一下,果然也有人利用 RequireJS 來做 Dependency Injection,Eric Feminella 寫了 Basic Dependency Injection with RequireJS 這篇文章,裡面的例子很不錯,假設開發一個 Web Applicaiotion,同時需要支援 Desktop 和 Mobile,兩種環境要不同的介面,但是核心的程式運作是一樣的,和 UI 相關的東西則要有 Mobile 用和 Desktop 用,這時候就可以用 Dependency Injection 來根據不同環境,決定要丟 Mobile 版的 UI 模組還是 Desktop 版的,核心的模組則不需要考慮到不同環境的差異,只要確保不同的 UI 模組的介面是一樣的就可以了。

最後還要講的是 RequireJS 的一個語法:

define(function (require) {
    var core = require('core'),
        ui = require('ui');

    // do something with core and ui
});

我第一次看到這種寫法時就覺得:「不對吧,這樣沒辦法確保這個模組執行時,裡面需要的 core 和 ui 已經有了啊。」因為除了去解析程式碼外,這樣子寫從外面根本不知道 function 裡面 require 了什麼東西,而且現在也不可能讓程式跑到某行暫時停下等其他模組準備好,所以看了 RequireJS 原始碼的結論是,它就是去分析程式碼裡面 require 了什麼東西,然後自動加到這個模組的相依性清單裡面這樣,一樣是用 Function.prototype.toString 辦到的。


更之前的文章