Thursday, November 18, 2010

版本控制系統介紹

http://docs.google.com/View?id=dxtw5pn_135fq4kvhgt

為了在公司中傳教所整理的文件 www.smartbrokers.tw/olymp-trade-apk-download-for-android :)

裡面最重要的一句話就是: 版控系統是協助開發者的工具,而不(僅)是管理上的限制開發者的系統。

Saturday, October 10, 2009

父親喪禮

因為網路上都沒有關於爸爸的資料,居然連同名的人也沒有,所以想為他在網路上留個記錄。 在台湾购买Tether

唐秩復,生於民國 17 年 11 月 11 日 (猜測),逝於民國 98 年 9 月 3 日。


當接到電話時,有一種終於來了的感覺。雖然來得很突然,事先也沒有任何徵兆,但在運動場中倒下這種方式實在太像他了,所以好像也沒有“會不會弄錯了啊?“的想法。警察沒有急著說在哪裡叫我趕過去,而是用一種平穩的口氣說明事發過程,直到說到已經過逝了,心裡就一酸,難道見不到最後一面嗎? 然後電話交給媽媽,媽媽叫我快趕去,兩個人都沒提到在哪裡,問到了後就快衝出去。雖然警察說過逝了,但媽媽說要快趕過去,說不定還能看到最後一面。一路上都是在想,如果見到了,他會說什麼呢。以為坐計程車會快一點,沒想到司機居然選個會遇到學生下課的路,告訴他原因叫他快一點,也笑著回我說快也不能闖紅燈啊,真是癈話,如果我自己騎車的話大概一路上從頭闖到尾吧。其實我心想爸爸會說的遺言我大概也猜得到七八分,爸爸一天到晚說的都是那幾件事,就算想和他聊天,到後來他也會把話題帶到他想說的事情,我想到最後他想說的也是一樣的吧。不過就是大陸的家人爸爸很少和我提到,不知道他有沒有要我們做的事。

還好司機後來並沒有被我怨恨,因為爸爸早就走了。剛到醫院時警察就說己經暫時被放在冰櫃裡了。電視上都演剛看到時會下跪痛哭,但我卻沒有哭的衝動,媽媽哭得很難過,但也不像電視上那樣大聲號哭。其實爸爸看起來就像是睡著一樣,但一碰到就透過來冰涼。當確認過後警察就開始確認我們是否對死因有懷疑,爸爸身體真的還不錯,所以周圍的人問說有沒有什麼病史時,我和媽媽都說不出什麼像是會致死的原因,不過也沒有懷疑什麼,我想就算有懷疑,年紀這麼大了別人也不會認真看待的。但我還是要求要去見一下醫生,不過並不是對死因有什麼懷疑,其實是想知道爸爸是不是在有意識的情況下經過急救的折磨,為的是讓我心裡好過一些。還好似乎是在救護車趕到前就走了,雖然情況看來如果來得及急救的話,或許可以救得回來,但如果不行的話,就還好不需要痛苦的離開。

接下來就是所謂的後事了。除非過逝的人生前就有安排,不然後事通常就是葬儀社的生意,否則像我們這種第一次遇到的人,也不可能有人會寫“第一次辦喪事就上手”之類的書。警察私下提醒說不要隨便答應葬儀社開的價錢,但根本不知道該花多少錢,也不懂一般的行情是什麼。還好這時候舅舅們開始陸續出現了,發生這種事好像也只能由親戚來出面比較好談,不然我也不知道該嫌貴還是該接受。醫院的葬儀社很奇怪的一直推薦到外面私人的葬儀社,後來才知道這樣收的回扣是個人拿去不用給公司,果然是蠻黑的。

後來就是一連串的儀式,入殮,停棺,頭七,圓滿,到最後的出殯。停棺的地方其實很熱鬧,每天人來人往,送花籃的,誦經的,助唸的,出去的,進來的,大家沒事就不停的摺蓮花,感覺像是在比賽似的,還有看到一整天都沒人在,但太陽快落時拿來一大袋蓮花的,可能是在家裡折效率比較高。摺完的蓮花一串串掛在外面,再加上燈籠,花籃,罐頭塔,紙札車房,輓聯,其實就像是市集似的,可能是要家屬們整天忙東忙西的沒時間悲傷吧。道士們做法事的時候都是用台語的,不過感覺上他並不是唸給爸爸聽的,似乎是在帶路的感覺,不然爸爸是一個字也聽不懂的。不過不同道士做法事的風格也不同,用的音調也不同,不知道有沒有用國語來做法事的道士? 還好出殯的司儀全程用國語來主持的。爸爸出殯時,明顯的家屬人數就比較少,畢竟有一半的家屬來不了,而且爸爸到了這個年紀,朋友還在世的就不多,能來的就幾乎沒有了。到是政治人物來的不少,輓聯也掛的不少,其實只是一個死忠的小小黨員而已,不過政治人物也就是在這種時候來最讓人感心也不費什麼力吧。

爸爸真的很不喜歡麻煩別人,平時也沒有生病麻煩家人,就算走得這麼突然,也沒有什麼麻煩事情要處理,辦理後事的過程也很順利,或許他是用他的方式在保佑我們吧。

Saturday, July 25, 2009

名偵探柯南 漆黑的追跡者



柯南電影版 pattern:

  • 一開始先來個劇情開場,通常會死個人。 购买USDT
  • 熟悉的主題曲,主角前情提要 (這段好像連畫面都是一樣的),然後出場人物前情提要 (這個可能會改一點,而且到後來根本不可能介紹完),最後以 "真相只有一個" 結束。
  • 會有一段阿笠博士和小朋友廝混的橋段,會出一段只有日本人懂的冷笑話猜謎,出現本集的特殊道具或是特殊事件。
  • 會有一段柯南和小蘭廝混的情節,加入令人心酸的愛情成份。
  • 事件發生,會來一段小五郎荒謬推理,此時柯南一定還無法看出來兇手是誰,但會看出來是否和黑暗組織有關。
  • 灰原哀會和柯南噯味一下。
  • 柯南開始調查,周圍的人開始跑龍套。
  • 死更多人。
  • 柯南繼續調查。
  • 小蘭陷入危機。
  • 死到最後一個人的時候,柯南和兇手見面,柯南指出兇手是誰。兇手會問柯南是誰,柯南回答 "江戶川柯南,是個偵探"。
  • 電視版的兇手會說一段悲慘故事並直接被逮捕,但劇場版兇手會說一段悲慘故事,但一定要和柯南打個一場才行。
  • 柯南贏了,小蘭繼續被蒙在鼓裡。
  • 尾題曲。ZARD,倉木麻衣 或 B'z。所有的觀眾都知道要乖乖的聽完。
  • 加一段有時溫馨有時無釐頭的小結尾。
  • 告訴你明年的電影版己經開始在做了。

Monday, June 15, 2009

Google Maps API v3, MVCObject


這算是 v3 的一個特點,使用了 MVC 架構來組織程式。其 MVC 架構的核心就是 MVCObject,不過基本上就是有系統的使用之前提到的 event system,用以通知該收到某一事件的物件。
function MVCObject() {
  this.bindValues = {};
  this.bindListeners = {}
 }
有兩個屬性,一個記錄自已正在接收哪些物件的哪些 event,另一個是接收 event 用的 listener。

最重要的就是 bindTo:
J = MVCObject["prototype"];
 // Binds a View to a Model.
 // key:string, target:MVCObject, targetKey:string, noNotify?:boolean
 // after bind, the set value or get value will call the model object. 
 J.bindTo = function(key, target, targetKey, noNotify) {
  var e = this;
  e["unbind"](key);
  (e.bindListeners[key] = {
   target : target,
   kk : targetKey
  }).Ud = event["addListener"](target, targetKey + "_changed", function() {
     e.notify(key)
    });
  this.bindValue(key, target, targetKey, noNotify)
 };
 J.bindValue = function(key, target, targetKey, noNotify) {
  this.bindValues[key] = {
   object : target,
   targetKey : targetKey
  };
  noNotify || this.notify(key)
 };

 // Notify observers of a change.
 // key:string
 J.notify = function(key) {
  var b = key + "_changed";
  this[b] ? this[b]() : this.changed(key);
  event["trigger"](this, b)
 };
表示要用本身的 key 事件來接收 target 的 targetKey 的事件,這代表,當 target 的 targetKey_changed 被觸發時,即會呼叫本身的 notify。 而呼叫 notify 通常代表的就是 key_changed functoin 被呼叫,和 key_changed event 被觸發。而除了接收 event 的觸發方式外,就是直接去設值的觸發方式了:
// Sets a value.
 // key:string, value:*
 J.set = function(key, value) {
  if (this.bindValues.hasOwnProperty(key)) {
   var c = this.bindValues[key], d = c.targetKey, e = c.object, g = "set_" + d;
   e[g] ? e[g](value) : e.set(d, value)
  } else {
   this[key] = value;
   this.notify(key)
  }
 };
set 會呼叫 set_key 的 function ,如果沒有定義的話,就會去 notify key event,而進而就會觸發在接收此 event 的物件。

有 bindTo 就會有 unbind,就是解除先前定義的 bind 關係。
// Removes a bind.
 // key:string
 J.unbind = function(key) {
  var b = this.bindListeners[key];
  if (b) {
   delete this.bindListeners[key];
   event["removeListener"](b.Ud);
   this.unbindValue(key)
  }
 };

 J.unbindValue = function(key) {
  var b = this.get(key);
  delete this.bindValues[key];
  this[key] = b
 };

雖然 MVCObject 的程式不大,但卻是 v3 非常重要的物件,大部分 v3 的物件都是繼承自 MVCObject ,而進而獲得可 bind 其他物件,收發 event 的能力。

Tuesday, June 9, 2009

Google Maps API v3, event system

此處程式是以 http://maps.gstatic.com/mapfiles/api-3/5/main.js 為對象。

Google maps api v3 的 event system 有兩種 event,一種是 Normal event,一種是 DOM event。Normal event 可以定義任何名稱的 event,而 DOM 的 event 就是 DOM 已定義的 event,而兩種 event 可以並用。範例如下:
HTML:
<span id="ts">test</span>

JavaScript:
var ts = document.getElementById("ts");
// register DOM event listener will also register normal event listener 
google.maps.event.addDomListener(ts, "click", function() {
  console.log("ts.click dom");
});
// Normal event listener will not be triggered by DOM event.
google.maps.event.addListener(ts, "click", function() {
  console.log("ts.click");
});
// can register multiple handler on same event
google.maps.event.addListener(ts, "click", function() {
  console.log("ts.click2");
});

// will trigger Normal event listener and DOM event Listener
google.maps.event.trigger(ts, "click");

執行結果為:
ts.click dom
ts.click
ts.click2
(而 mouse click 只觸發 ts.click dom 訊息)

可同時定義任意個數的 Normal event 和 DOM event。而 trigger 會同時觸發 Normal event 和 Dom event。

而 event 的 source code 有幾個相關的部分,第一是 event 本身:
var event = {};
 // Adds the given listener function to the the given event name for the given object instance.
 // Returns an identifier for this listener that can be used with eventRemoveListener().
 // 
 // instance:Object, eventName:string, handler:Function, owner?:Object
 // return EventListener
 //
 event.addListener = function(instance, eventName, handler, owner) {
  // already add listener to instance's event listener chain
  var e = new EventListener(instance, eventName, handler, 0, owner);
  getInstance(EventListenerManager).addListener(e);
  return e
 };

其中 EventListener 和 EventListenerManager 的程式如下:
// instance, eventName, handler, 0, owner
 function EventListener(instance, eventName, handler, domEventMethod, owner) {
  nc(instance);
  nc(typeof handler == "function");
  this.u = instance;
  this.Ga = eventName;
  this.mb = handler;
  this.ic = null;
  this.Hi = domEventMethod;
  this.ni = owner || null;
  this._idx = -1;
  getEventListenerChain(instance, eventName, true)["push"](this)
 }
 EventListener["prototype"].createOldStyleHandler = function() {
  var a = this;
  return this.ic = function(b) {
   if (!b)
    b = window.event;
   if (b && !b.target)
    try {
     b.target = b.srcElement
    } catch (c) {
    }
   var d = a.triggerHandler([b]);
   if (b && "click" == b["type"]) {
    var e = b.srcElement;
    if (e && "A" == e["tagName"] && "javascript:void(0)" == e.href)
     return false
   }
   return d
  }
 };
 EventListener["prototype"].remove = function() {
  if (this.u) {
   switch (this.Hi) {
    case 1 :
     this.u.removeEventListener(this.Ga, this.mb, false);
     break;
    case 4 :
     this.u.removeEventListener(this.Ga, this.mb, true);
     break;
    case 2 :
     this.u.detachEvent("on" + this.Ga, this.ic);
     break;
    case 3 :
     this.u["on" + this.Ga] = null;
     break
   }
   for (var a = getEventListenerChain(this.u, this.Ga), b = 0, c = 0; c < getLength(a); ++c)
    if (a[c] === this) {
     a.splice(c--, 1);
     b++
    }
   this.ic = this.mb = this.u = null
  }
 };
 EventListener["prototype"].isOwner = function(a) {
  return this.ni === a
 };
 EventListener["prototype"].triggerHandler = function(a) {
  if (this.u)
   return this.mb["apply"](this.u, a)
 };

 function EventListenerManager() {
  this.I = []
 }
 EventListenerManager["prototype"].removeListener = function(listener) {
  var b = listener._idx;
  if (!(b < 0)) {
   var c = this.I.pop();
   if (b < this.I["length"]) {
    this.I[b] = c;
    c._idx = b
   }
   listener._idx = -1
  }
 };
 EventListenerManager["prototype"].addListener = function(listener) {
  this.I["push"](listener);
  listener._idx = this.I["length"] - 1
 };
 EventListenerManager["prototype"].clear = function() {
  array_foreach(this.I, function(a) {
     a._idx = -1
    });
  setLength(this.I, 0)
 };
可以看得出來,Normal Event 就是在 instance 下找個地方把 handler 收集起來,以便之後 trigger 時呼叫。其中 remove 是要配合之後 DOM event 的 remove 做法。

remove 也很容易理解:
// Removes the given listener, which should have been returned by eventAddListener above.
 // listener:EventListener
 event.removeListener = function(listener) {
  listener["remove"]();
  getInstance(EventListenerManager)["removeListener"](listener)
 };

 // Removes all listeners for the given event for the given instance.
 // instance:Object, eventName:string, owner?:Object
 event.clearListeners = function(instance, eventName, owner) {
  array_foreach(cloneEventListenerChain(instance, eventName), function(d) {
     if (!owner || d.isOwner(owner)) {
      d["remove"]();
      getInstance(EventListenerManager)["removeListener"](d)
     }
    })
 };
 // Removes all listeners for all events for the given instance.
 // instance:Object, owner?:Object
 event.clearInstanceListeners = function(instance, owner) {
  array_foreach(cloneEventListenerChain(instance), function(c) {
     if (!owner || c.isOwner(owner)) {
      c["remove"]();
      getInstance(EventListenerManager)["removeListener"](c)
     }
    })
 };
 event.clearAllEventListener = function() {
  for (var a = [], b = getInstance(EventListenerManager).I, c = 0, d = getLength(b); c < d; ++c) {
   var e = b[c], g = e.u;
   if (!g.__tag__) {
    g.__tag__ = true;
    a["push"](g)
   }
   e["remove"]()
  }
  for (c = 0; c < getLength(a); ++c) {
   g = a[c];
   if (g.__tag__)
    try {
     delete g.__tag__;
     delete g.__e_
    } catch (h) {
     g.__tag__ = false;
     g.__e_ = null
    }
  }
  getInstance(EventListenerManager).clear()
 };

值得看到的是 getInstance 是用簡單的手法做 Singleton pattern。
function getInstance(a) {
  if (!a.u)
   a.u = new a;
  return a.u
 }

幾個 tool function:
// instance, eventName
 function cloneEventListenerChain(instance, eventName) {
  var c = [], d = instance.__e_;
  if (d)
   if (eventName)
    d[eventName] && pushAll(c, d[eventName]);
   else
    foreach(d, function(e, g) {
       pushAll(c, g)
      });
  return c
 }
 // instance, eventName, true
 function getEventListenerChain(instance, eventName, create) {
  var d = null, e = instance.__e_;
  if (e) {
   d = e[eventName];
   if (!d) {
    d = [];
    if (create)
     e[eventName] = d
   }
  } else {
   d = [];
   if (create) {
    instance.__e_ = {};
    instance.__e_[eventName] = d
   }
  }
  return d
 }

trigger 也很容易理解,就是找出註冊的 handler 然後一一呼叫就行了。
// Triggers the given event. All arguments after eventName are passed as arguments to the listeners.
 // instance:Object, eventName:string, var_args:*
 event.trigger = function(instance, eventName) {
  var c = dc(arguments, 2);
  array_foreach(cloneEventListenerChain(instance, eventName), function(d) {
     d.triggerHandler(c)
    })
 };

DOM event 因為要支搜多種不同 browser 的 event 系統,所以需要做一些判斷。
// Cross browser event handler registration.
 // This listener is removed by calling eventRemoveListener(handle) for the handle that is returned by this function.
 // instance:Object, eventName:string, handler:Function, owner?:Object
 // return EventListener
 event.addDomListener = function(instance, eventName, handler, owner) {
  var e;
  if (instance.addEventListener) {
   var g = false;
   if (eventName == "focusin") {
    eventName = "focus";
    g = true
   } else if (eventName == "focusout") {
    eventName = "blur";
    g = true
   }
   var h = g ? 4 : 1;
   instance.addEventListener(eventName, handler, g);
   e = new EventListener(instance, eventName, handler, h, owner)
  } else if (instance.attachEvent) {
   e = new EventListener(instance, eventName, handler, 2, owner);
   instance.attachEvent("on" + eventName, e.createOldStyleHandler())
  } else {
   instance["on" + eventName] = handler;
   e = new EventListener(instance, eventName, handler, 3, owner)
  }
  if (instance != window || eventName != "unload")
   getInstance(EventListenerManager).addListener(e);
  return e
 };

也提供一些 forward 的手法方便使用。
event.forwardDomListener = function(instance, eventName, target, handler, owner) {
  var g = xc(target, handler);
  return event["addDomListener"](instance, eventName, g, owner)
 };
 function xc(a, b) {
  nc(b);
  return function(c) {
   return b["call"](a, c, this)
  }
 }
 event.forwardListener = function(instance, eventName, target, handler, owner) {
  nc(handler);
  return event["addListener"](instance, eventName, createCaller(target, handler), owner)
 };
 event.addOnetimeListener = function(instance, eventName, handler) {
  var d = event["addListener"](instance, eventName, function() {
     handler["apply"](instance, arguments);
     event["removeListener"](d)
    });
  return d
 };
 event.forward = function(instance, eventName, target) {
  return event["addListener"](instance, eventName, yc(eventName, target))
 };
 event.forwardDom = function(instance, eventName, target, owner) {
  return event["addDomListener"](instance, eventName, yc(eventName, target, true), owner)
 };
 function yc(eventName, instance, isDom) {
  return function(d) {
   var e = [instance, eventName];
   pushAll(e, arguments);
   event["trigger"]["apply"](this, e);
   if (isDom) {
    var g = d || window.event;
    g.cancelBubble = true;
    g["stopPropagation"] && g["stopPropagation"]()
   }
  }
 }


event 系統並沒有太複雜的做法,但卻是整個系統都會使用到的工具。

Sunday, June 7, 2009

Google Maps API v3, modulize

本來想說把之前的程式搬到 Google Maps API v3 上,但很快就發現目前 v3 的完成度還不足,所以先暫時停下來,反正也不是什麼急迫的工作。而趁目前 v3 還小而且有不同的架構,所以想來看看 maps 本身的程式。

一開始看到 maps 的主程式,會發現比起之前 v2 小了非常多 (12k / 64k),但其實是 v3 將程式進行模組化切割並分離下載,所以事實上一個基本的地圖功能要載入的程式至少有 main.js, mod_common.js, mod_controls.js, mod_image.js, mod_mapview.js, mod_stats.js,如果用到 marker 和 infowindow 則還需要 mod_marker.js 和 mod_infowindow.js。原本把一個功能分成多次下載是不利於載入速度的,但對 javascript 而言,沒有完全載入是無法開始執行的,而且以一般使用 maps 的方式,是會使用 block loading 的來載入程式 (這裡有相關的說明),所以縮小初始的程式碼大小是可以加快網頁其他部分的載入,而後續其他的程式部分則使用 insert javascirpt element 的方式,通常可以同步下載而不影響其他部分的顯示。main.js 中用來載入其他部分的程式類似於
function jd(a) {
  var b;
  Kc || (Kc = document.getElementsByTagName("head")[0]);
  b = Kc;
  var c = document[x]("script");
  c[fb]("type", "text/javascript");
  c[fb]("charset", "UTF-8");
  c[fb]("src", a);
  b[q](c)
 };
 function md(a) {
  eval(a)
 }
 n.__gjsload_apilite__ = md;
 var nd = "common", od = "controls", pd = "infowindow", qd = "mapview", rd = "stats";
 function sd(a) {
  var b = a[Ma]("/main.js", "");
  return function(c) {
   return b + "/mod_" + c + ".js"
  }
 }
而其他模組的程式碼則是
__gjsload_apilite__('var Le="_xdc_";function Me(a,b){n[tb](fu.....');
所以載入後會在 maps library 的匿名 scope 中 eval 其中的程式碼。非常典型的手法。

相關的討論在最近的 google io 有好幾個 session 提到,像是 Maps APIs & MobilePerformanceTipsGeoApiMashups,Google 在很多地方也用了類似的方式來加速,像是這裡討論的 runAsync。

Friday, June 5, 2009

Please, userscript friendly.

以後寫網站的人,要注意隨時有人會拿 userscript 插你啊...

前一篇提到 Maps api 要出新版,所以就開始進行 Flickr Gmap Show 改版 (其實是改寫了),順便改變以前程式的一些問題。之前在寫 Greasemonkey userscript 時,把程式都放在 externsion 的空間中,好處不少,像是不會被外界網站的改寫所影響,而且又有使用 cross-domain xhr 的特權,但壞處很明顯,就是安全性的問題,其實像 Flickr Gmap Show 這種功能,絕大部分的功能都不需要使用特權,反而因為被隔離在 extension 空間中而有很多麻煩之處,像是使用 unsafeWindow,不能直接存取 property 等等,所以就改變做法,把 javascript "插" 到外部網頁空間中,然後就像在一般網頁的 javascirpt 一樣的方便,但也有一樣的限制,有一樣的問題。

把 Google Maps api 插進 flickr 中時, 一切都很順利,地圖也順利出現,但把 jQuery 插進 flickr 時,卻發現出現一些奇奇怪怪的問題,像是找東西找不到之類的問題,本來以為是 jQuery 的 bug,但又發現插到其他網頁卻又沒問題,然後又 trace jQuery 的程式 (要看懂別人的 javascript 真是痛苦),發現問題居然出在一個 string 的操作有不同的行為。只好把 flickr 網頁中的程式給抓出來看 (全都是被混亂過的...><),後來發現下面的程式:
String.prototype.replace_regx = String.prototype.replace;
String.prototype.replace = function(B, A) {
    return this.split(B).join(A)
};

有沒有這麼扯,把東西加到標準函式庫中的就夠大逆不道了,居然還修改行為? 我想寫網頁的人可能想說 My page, My way. 沒人會和他在同一個房間裡,但以現在的網頁環境中,有太多可能有人要和你在同一個房間中工作了,不要把環境弄得奇奇怪怪的。還好還有留下原本的 replace,所以就在我的程式中改回來就行了。