Skip to main content

· 13 min read

前陣子 Facebook 推出一套名為 FLUX 的前端程式架構,期望能幫過去沒有條理,程式多了結構就亂得像一團麵條的 Javascript 程式寫法找到一個理想的組織方法。

FLUX 簡介

視圖(View)-> 操作(Action)-> 分配器(Dispatcher)-> 資料處理器(Store)-> 繪圖者(Renderer)-> 視圖(View) FLUX 的基本原理有別於常用的 MVC(Model/View/Controller)或 MVVM(Model/View/ViewModel)是在 M,V,C(VM)三者之間互相傳遞或修改資料,

 

MVC (image from fluxxer)

FLUX 重新定義整個組織架構為單向的視圖(View)-> 操作(Action)-> 分配器(Dispatcher)-> 資料處理器(Store)-> 繪圖者(Renderer)->  視圖(View)的運作流程。

FLUX (image from fluxxer)

就我的理解,FLUX 的架構可以拆分為三個重點流程:

  • 跟視圖(View)有關的操作(Action),都透過事件註冊到分配器(Dispatcher)去。
  • 分配器 (Dispatcher)負責將操作(Action)傳遞給需要的資料處理器(Store)。
  • 資料處理器(Store)負責跟資料直接相關的操作。若資料處理器(Store)修改的結果需要反映到視圖(View)上,可以透過發送訊息通知給繪圖者(Renderer)處理。這邊講到了原本 FLUX 概念圖中沒有提到的繪圖者(Renderer)這個角色,在 Facebook 中他們是用 ReactJS 處理。

瞭解其基本架構之後,我發現其實就算不用他們提供的函式庫,用 Javascript 內建的 addEventListener, handleEvent, customEvent 等方法,也可以利用前面所提的三個重點,漸進寫出符合 FLUX 精神的程式。

目前的 JS 組織方式

一個常見的 JS 檔案,一般的架構是

var App = {    init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', function() {         // do something       });       this.view2.addEventListener('keyup', function() {         // do something       });     } }; 若我們想要將視圖(View)的操作從 init 分離開來,大部分的人會這樣做

 var App = {     init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', this.clickView1);       this.view2.addEventListener('keyup', this.keyupView2);     },

    clickView1: function app_clickView1() {        // do something     },

    keyupView2: function app_keyupView2() {        // do something     }

}; 如果在 clickView1 或 keyupView2 中要呼叫到 App 裡的參數或方法,那麼我們需要在 addEventListener 時使用 bind (this)

 var App = {     init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', this.clickView1.bind(this));       this.view2.addEventListener('keyup', this.keyupView2.bind(this));     },

    clickView1: function app_clickView1() {        // do something     },

    keyupView2: function app_keyupView2() {        // do something        this.clickView1();     }

}; 大多數書籍的範例大概就停在這裡,沒有再進一步探討程式的組織架構了。即使是龐大的 Javascript 專案如 Gaia,不少部分的程式碼組織方式也是如此。在這種組織方式裡,若有很多的視圖(View)需要操作或修改,我們的程式碼就會開始亂起來。

下面來試著將以上程式漸進改為 FLUX 架構。

改進建議一:將 handleEvent 當作 Dispatcher 來使用

跟視圖(View)有關的操作(Action),都透過事件註冊到分配器(Dispatcher)去。 (溫馨提示:IE 9 以上版本才有支援 handleEvent 方法,在之前版本上使用要加 polyfill)

我們先來想想看視圖(View)跟操作(Action)在前端 Javascript 程式中分別代表著什麼。 視圖(View)很明顯,就是透過 getElementById 等方法,從 HTML 中取得代表對應節點(Node)的元素(element)。

若想要套用 FLUX 架構,我們可以將附加在各個元素(element)上的事件行為分離,將事件註冊到一個統一的地方(分配器),在這個地方對不同的事件進行操作。

 Javascript 內建的分配器叫做 handleEvent,它可以拿來處理任何事件 Event,寫法如下。

 var App = {     init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', this);       this.view2.addEventListener('keyup', this);     },

    handleEvent: app_handleEvent(evt) {        switch (evt.type) {          case 'click':            switch (evt.target) {               case this.view1:                 this.clickView1();                 break;            }            break;          case 'keyup':             switch (evt.target) {               case this.view2:                 this.keyupView2();                 break;             }        }     },

    clickView1: function app_clickView1() {        // do something     },

    keyupView2: function app_keyupView2() {        // do something        this.clickView1();     }

}; 這麼做帶來的明顯好處是所有的呼叫都統一在 handleEvent 中,可以更容易地追查到。

這麼寫也可以在 addEventListener/removeEventListener 時不用使用 bind (this),而 bind (this) 經常有些 side effect 需要特別留意。

例如假使我們想要反註冊 view1 上的 click 方法,使用以下寫法

 this.view1.removeEventListener ('click', this.clickView1.bind (this)); 其實並沒有將第一個 event 移除。因為使用了 .bind(this) 後,回傳的其實是一個新的 instance...。 正確的寫法是 this.bindClickView1 = this.clickView1.bind (this) this.view1.addEventListener ('click', this.bindClickView1); ... this.view1.removeEventListener ('click', this.bindClickView1); 用 handleEvent 可以省點事,要反註冊時也傳入 this 即可。 this.view1.removeEventListener ('click', this);

改進建議二:將資料處理的部分分離,使用自訂事件來改變 Store 狀態

分配器 (Dispatcher)負責將操作(Action)傳遞給需要的資料處理器(Store) 資料處理器(Store)負責跟資料直接相關的操作。在稍大的 Web App 中,我們可以另外定一個 Object 來處理資料相關的事宜。一般我們的寫法會是

// Store.js function Store() {   this._data: 0; };

Store.prototype = {   getSomething: function s_getSomething() {     return this._data;   },

  doSomething: function s_doSomething() {     this._data += 1;   },

  setSomething: function s_setSomething(val) {     this._data = val;   } };

// App.js var App = {   this.store = new Store();   this.store.init();   this.store.getSomething();   this.store.doSomething();   this.store.setSomething (2); }; 若想要套用 FLUX 架構,首先我們要避免從資料處理器(Store)外部直接改變資料處理器(Store)。我們可以透過在呼叫端使用 window.DispatchEvent 發送自訂事件(CustomEvent),並在資料處理器(Store)中接收自訂事件來做到。

如此一來,資料處理器(Store)將只留下 get 方法來讓外部取得 Store 想提供的資料。

另外如果程式碼改善進入到下一個重點,在操作(Action)時應該不需要再呼叫 Store.getSomething 了,我們將資料處理器(Store)的 getSomething 方法留著給繪圖者(Renderer)使用 。

// Store.js function Store() {   this._data: 0; };

Store.prototype = {   init: function s_init() {      window.addEventListener('store_do', this);      window.addEventListener('store_set', this);   },

  handleEvent: s_handleEvent(evt) {     switch(evt.type) {       case 'store_do':         this.doSomething();         break;       case 'store_set':         this.setSomething(evt.detail.val);         break;     }   },

  getSomething: function s_getSomething() {     return this._data;   },

  _doSomething: function s_doSomething() {     this._data += 1;   },

  _setSomething: function s_setSomething(val) {     this._data = val;   } };

// App.js var App = {   this.store = new Store();   this.store.init();   //this.store.getSomething();   window..dispatchEvent(new CustomEvent('store_do'));   **window..dispatchEvent(new CustomEvent('store_set',**     {'detail':{'val':2}}   )); }; 這麼做帶來的明顯好處是測試時可以簡單地將 Store 與 App 分開來測試,這對大型 App 是很重要的。

改進建議三:讓 Renderer 來處理視圖

若資料處理器(Store)修改的結果需要反映到視圖(View)上,可以透過發送訊息通知給繪圖者(Renderer)處理 > //  Renderer.js var ClickRenderer = {   init: function s_init(element, Store) {      this.element = element;      this.store = Store;      window.addEventListener('render_view1', this);   },

  handleEvent: s_handleEvent(evt) {     switch(evt.type) {       case 'render_view1':         this.element.textConent = this.store.getSomething();         break;     }   }};

// Store.js function Store() {   this._data: 0; };

Store.prototype = {   init: function s_init() {      window.addEventListener('store_do', this);      window.addEventListener('store_set', this);   },

  handleEvent: s_handleEvent(evt) {     switch(evt.type) {       case 'store_do':         this.doSomething();         break;       case 'store_set':         this.setSomething(evt.detail.val);         break;     }   },

  getSomething: function s_getSomething() {     return this._data;   },

  _doSomething: function s_doSomething() {     this._data += 1;     window..dispatchEvent(new CustomEvent('render_view1'));   },

  _setSomething: function s_setSomething(val) {     this._data = val;   } };

// App.js var App = {   init: function a_init() {     // get view     this.view1 = document.getElementById('xxx1');     this.view2 = document.getElementById('xxx2');     this.store = new Store();     this.store.init();     ClickRenderer.init(this.view1, **this.store);**   },

    handleEvent: a_handleEvent(evt) {     window..dispatchEvent(new CustomEvent('store_do'));     //Store.setSomething(2)     window..dispatchEvent(new CustomEvent('store_set',       {'detail':{'val':2}}     )); }; 上段程式在 App 中註冊了 ClickRenderer,並傳入 Store 與 所需的 View 元件。所有的介面更新全交由 ClickRenderer 處理。

(另一個方法是讓繪圖者(Renderer)監看資料處理器(Store)的狀態,然後去改變視圖(View))  http://fluxxor.com/documentation/store-watch-mixin.html

總結

整理完後,一般 javascript 套用 flux 架構的運作流程如下:

簡而言之,上面的各種建議是鼓勵大家多使用 Javascript 內建的 addEventListener, handleEvent, customEvent 等方法。透過大量使用 event,我們可以改善 Javascript 程式邏輯,資料,與介面元件之間的關聯程度。

將 FLUX 架構拆分為三個重點流程來理解或實踐的好處,是我們能漸進地遵循其中一些方法來改善我們現有的程式架構。

以上是我關於如何使用 FLUX 架構在一般 Javascript 組織方式的第三個版本,可能有些錯謬之處,還迎大家討論或給予建議。

· 2 min read

FoxBox is the project that intent to provide a battery included Firefox OS build environment.

The goal of foxbox is to try any approach that make new user can do as less as possible to start the FirefoxOS development

Our first take is use vagrant with virtualbox to make major platform users can try FirefoxOS dev in VM.

It will be great to setup the current version of foxbox in your desktop environment

http://github.com/gasolin/foxbox

And record obstacles you encountered here https://github.com/gasolin/foxbox/issues?state=open. There are some issues (but not the limit) that might be worth to do in the future version of foxbox.

Note that you require a desktop with INTEL VT-x/AMD-V hardware virtualization support(Windows8 or Mac already enabled it), at least 4GB RAM and about 10~40GB disk space(for gaia or full B2G development).

FoxBox has been approved by the Google Summer of Code administrator http://wiki.mozilla.org/Community:SummerOfCode14 , so its perfect time to step up, try FoxBox, fix issues that every others will encounter, save everybody's time and start make your own Firefox OS phone.

If you'd like to contribute FoxBox for SummerOfCode14. We expect you could find out the interesting topic you want to contribute or any other way that can better achieve FoxBox's goal.  

· One min read

Android Wear 不是作業系統,基本上是拿來開發 Android 周邊所使用的協定。它是把原本需要拿出手機查訊息,變成透過 Notification API 傳給手錶。透過 Google Now 手錶可以收到推播來的相關訊息或透過語音辨識來控制手機應用做事情。

因為 Android Wear + Google Now 帶來的便利性,Google 可以再進一步加強圍繞著 Play Service/Google Now 的生態圈。

· 14 min read

這篇文章的原文為 Life is a game. This is your strategy guide,作者為 Oliver Emberton,譯者為 gasolin。已獲得作者的翻譯許可。

(按下開始鍵)

真實的人生就像是遊戲,每個人都身在其中。 但遊戲過程可能會遇到困難,這篇文章就是你的人生攻略指南。

基礎

你可能還沒意識到,但真實的生活其實是場策略遊戲。雖然遊戲裡面還包含了許多有趣的小遊戲 -- 像是跳舞、開車、跑步、與愛愛 -- 但是遊戲勝利的關鍵,其實只在於如何善用你的資源。 最重要的是,成功的玩家將他們的時間用在正確的事情上。即使在遊戲的中後階段加入了「金錢」這個要素,但你應該優先考慮的,依然是關於如何運籌帷幄你的時間。

童年

人生遊戲開始於你被指派了一個隨機的角色與隨機的環境

(選擇你的角色) 遊戲的前 15 年左右只是新手導引任務。這部分的導覽實在做得不怎麼樣,而且還不提供跳過選項。

青年階段

作為一個年輕的玩家,你擁有許多時間與精力,但幾乎沒有經驗。你會發現大部份的東西 -- 像是最好的工作、最好的財產、最好的伙伴 -- 直到你取得之前都仍然未解鎖。 這個階段是可以快速升級你的技能的時機。在之後的階段裡,你再也不會那麼多時間與精力了。 為了好好地玩這場遊戲,你的首要任務是將你的時間作最好的分配。每件你做的事情都會影響你的狀態與技能:

(喝酒 - 健康 - 精力 - 金錢)  這聽起來很容易,但問題是你不總是知道該選擇做什麼事,而且你的身體不見得總是服從你的命令。這就來一一說明吧:

如何讓身體服從你的命令

許多玩家發現,當他們選擇做某個任務時 -- 例如「去健身房健身吧」-- 他們的身體完全忽略了這個命令。這並不是一個錯誤(bug)。其實,每個人身上都有一個隱藏的狀態欄,只是你無法直接看到它。這個狀態欄很可能長得像這樣:

(健康 精力 意志力)  如果你的某個狀態指數太低,你的身體將抗拒執行你的指令,直到你的需求已被滿足。試著在你又累又餓的時候讀書吧,你會發現你的注意力一直跑到臉書上。

在這些狀態中,你的意志力指數狀態是最重要的。每天起床後,意志力就會隨著時間降低,在吃些東西後會回復少許,只有透過睡一晚好覺才會完全回復。當你的意志力指數很低的時候, 你只能做那些你身體想要做的任務。 生活中作出每個決定時,都會消耗意志力指數。對於那些你必須要作出不做比較吸引你的任務,而去做較不吸引你的任務的決定(例如不看電視而去健身)需要消耗大量的意志力。

這邊有許多技巧可以幫助你保持良好狀態:

1. 保持良好的狀態. 如果你覺得飢餓、疲憊、或感覺全無樂趣,你的意志力會崩潰。請確保你有持續地好好對待你自己。  2. 不要一天內消耗太多意志力. 將你最困難的項目分作多天完成,並搭配一些沒那麼難的項目一起做。   ** ** 3. 每天先做最重要的事. 這會讓其他的事情更困難一些,但這讓你最重要的項目更可能完成。

4. 透過減少選項來減少消耗意志力. 如果你試著在可以連上臉書的電腦前工作,你需要更多的意志力來工作,因為你必須持續地選擇做困難的項目而不是選簡單的。消除這些分心的事吧。

玩這場遊戲的重要部分是平衡你的眾多任務的順序與你的身體狀態。別讓你自己進入隨波逐流的 自動導航模式,否則你什麼事都完不成。

選擇正確的任務

在正確的時間選擇正確的任務就是這遊戲的精髓。有些任務會影響你的狀態,例如

(吃東西 + 精力 - 飢餓) 另外一些任務會影響你的技能:

(練搖滾 + 音樂性 + 臉部彩繪) 你需要花時間在那些保證你健康狀態的事情上 - 例如進食與睡眠 - 好讓你的意志力狀態維持高檔。然後你需要發展你尚未俱備的技能。一些技能比其他的技能更有價值。好的技能可以開啓整條升級道路,像是科技樹一樣:

(電腦技能 -> 電影制作者,程式設計師,網頁設計師 -> 臉書創辦人 ->千萬富豪)

有些技能則是沒有發展性的死路:

(用膝蓋頂球) 結合多個技能是最有效的。要把一個技能練滿是非常困難的 -- 事實上,通常是不可能的。但把一堆相關的技能練到似模似樣就簡單多了。例如:

(商業 + 自信 + 心理學 = 企業家)

(烹飪 + 跳舞 + 心理學 = 舞男) 看到心理學如何讓你變得又富有又有魅力了吧?你應該學學這個(笑)。

你在哪裡生活

你所生活的環境會持續地影響你的狀態、技能,與你升級的機會。你可以在任何地方把遊戲玩地很好,但在一些地方,要把遊戲玩好卻更簡單。例如如果你是身在某些國家的女性玩家,有許多成就你都不能解鎖。任何玩家生在最佳環境的機會都基本上是零。所以搜索看看你的選擇吧,仔細思考是否要早點移動。環境是你的技能與狀態的一個乘數。在適合的環境中,你的表現會成倍增長。

尋找伴侶

「魅力」本身就是個複雜的小遊戲。但大部份伴生於你已經在玩的遊戲。如果你已處在很好的狀態並擁有很好的技能,那麼你已經具備不小的魅力了。一個疲憊、易怒、身無所長的玩家一點都不吸引人,而且可能不該嘗試尋找另一半。

(成就解鎖 快樂 +1 頭痛 x2)

在這個小遊戲的初期,不管是拒絕其他玩家,或是被其他玩家拒絕都很常見。這是正常的。但不幸地是大部份玩家不太會處理拒絕,這都會降低你的狀態。你需要消耗一些意志力以恢復遊戲,而意志力需要透過睡眠來回復,所以給點時間吧。

80% 要找到另一半的因素,通常歸結於如何讓自己變成最有魅力的自己 -- 就像人生遊戲的其他方面一樣 -- 意味著將你的時間放在正確的地方。如果你運動、社交、補充營養、發展自己的事業,你的魅力也會自動增加。剩下的 20% 則是讓自己出現在會遇到對的人的地方。

錢、錢、錢

 遊戲的稍後階段中你需要管理一種叫做「金錢」的新資源。大多數玩家會發現遊戲的早期階段金錢通常會逐漸增加,但通常這只會造成更多問題,而不是更少。

關於處理金錢最重要的規則是「絕不借錢,除非借了錢能讓你賺更多回來」 例如,教育或房貸可能有價值(但得看狀況,並不一定)。借錢買鞋則不是。

根據你的財務目標,這邊有幾種策略可以參考:

  1. 不為錢煩惱. 低壓力策略:只要量入為出,並未雨綢繆即可。在你可以存錢的階段時就盡量存錢吧,不然你會後悔的。** **
  2. 小康. 審慎選擇一個職業與環境,準備經常轉職或升職。你需要大量投資在相關的技能上,這會花掉你許多時間。而且注意別因此操壞你的身體,不然你會提早掛掉的。
  3. Mega rich. 開始自己的生意. 為別人工作幾乎不可能變得富有。自己工作不會變得富有,擁有東西 – 資產 – 才會變得富有。資產會回報比投資時所花費的更多,而且你的公司是你可以從頭建立起的一個有力的資產。 結合你的回報變成更多的資產,最終它們讓你完全不需工作.

晚年階段

在遊戲進行的過程中,你能選擇的也跟著改變。婚姻和孩子將減少你的時間和精力,而且在遊戲中引入了更多隨機元素(緊急換尿布任務!)這讓你更難以快速地發展你自己。  較年長的角色通常擁有更多的技能、資源,與經驗,能解鎖那些之前無法參與的大任務。例如「擁有一棟房子」,或「寫一本(好)小說」。

(聽力 -1 寬鬆的褲子 +10)  所有的玩家在大概 29000 天,或 80 年後死去。如果你的狀態和技能良好,你可能可以延長一點時間。但是並沒有作弊碼可以大幅延長遊戲時間。

在遊戲開始時,你沒有辦法控制你是誰或你在哪裡。在遊戲結束時,你依然無法在這時改變結局。你過去所下的那些決定,會絕大地形塑你最終結局的好壞。如果在你最後的人生中開心、健康、圓滿 -- 或不然 -- 你都無法再改變什麼。

這就是為什麼攻略很重要。 因為隨著時間我們大部份人都會瞭解人生遊戲到底怎麼玩,但這時我們已經浪費掉太多精華部分了。

現在你最好趕緊去玩人生遊戲吧。

· 2 min read

I'm pleased to release first FoxBox version that may save a lot of time for people who wants to give FirefoxOS development a try.

For web developer who wants to contribute to gaia might meet a serious problem that they might have limit knowledge of *nix system. But to build gaia, the command line and make script is essential.

For developer who wants to contribute to B2G project, a bunch of per-requisite settings must be configured well before really digging into the code.

FoxBox could help (currently I mainly focus on) B2G/gaia developer quickly setup a working environment in VM.

Generally FoxBox 0.4 provide a Vagrantfile which automatically configure a VM with bunch of tools you need for building FirefoxOS and gaia development.

FoxBox have put a comprehend list of USB vendors, so any Android/FirefoxOS smartphone plugged can be identified.

With vagrant's NFS shared folder, developer can use their favorite editor to code.

FoxBox also bundled with a minimum GUI environment that enable you to test FirefoxOS in VM. Firefox Nightly will be pre-installed for you as well!

Read README for more detail.

Happy Hacking!

Disclaim: Though I work for mozilla, FoxBox is not an official project of Mozilla. Currently its just my side project create around Chinese New Year's holiday.