神乎奇技的jQuery Hacking

十八歲的jQuery天才少年James Padolsey這回又讓我佩服到起了雞皮疙瘩...

一直以來,jQuery的語法有個小缺陷,就是在API中少了直接引用自己的捷徑。舉個例子來說,如果我想讓某個<div id='”dvX”>的寬度增加20px,常見的解法有兩種:

  1. $("#dvX”).width($(“#dvX”).width() + 20); <--會引用兩次$(...),效能不佳
  2. var $d = $(“#dvX”); $d.width($d.width()); <-- 效能較佳,但還是囉嗦

James在他的Blog文章中,發明了一種新寫法:

$("#dvX”).width($._this.width() + 20);

James的做法是巧妙地攔截jQuery建構式,偷偷地把最後一次呼叫的jQuery建構式內容用jQuery._this共用變數保存下來: (有一點以前寫組合語言攔截INT中斷的fu~~~)

(function(_jQueryInit){
     jQuery.fn.init = function(selector, context) {
        return (jQuery._this = new _jQueryInit(selector, context));
    };
})(jQuery.fn.init);

Javascript的超大彈性與天才少年的慧黠,一樣都教人嘆為觀止,目瞪口呆!

【茶包射手專欄】IFrame中的TextArea、TextBox無法輸入文字

被一個問題搞到暴怒!

我寫的一個網頁採用BlockUI的方式顯示一個嵌在IFrame裡的子網頁,以進行一些額外的資料輸入操作,操作完成後則以$.unblockUI()收掉子網頁,將控制權交回原網頁。

起初有一個使用者跟我抱怨,IFrame子網頁裡的Textarea,在重覆開啟關閉後,子網頁的Textarea、Textbox就都無法輸入文字(點選後不會顯示文字輸入游標)。試著在自己公司的電腦上進行過同樣的操作,並沒有發生無法輸入的狀況,加上其他使用者都無人回報類似錯誤,因此被我判定為少數Client環境問題怪異的個案,未再深究。

不料,今天在家裡寫網頁時,家裡的Vista x64+IE8也冒出同樣狀況: 重覆開啟子網頁後,Textarea、Textbox便無法再取得焦點輸入文字。使用FF、Chrome開啟該網頁操作,並不會有任何問題... orz

媽呀,這個設計模式已被我應用在好幾個專案裡,要是IE用起來有問題,對我來說將是一埸災難。更何況,網頁其他操作正常,就只有TextArea、TextBox在搞自閉,這是什麼鳥狀況,虧IE8可以搞出來。

讀到這裡,想必眾多Web Developer已然義憤填膺,對IE的新仇舊恨湧上心頭,蘊釀好情緒,準備要和我一起高聲吶喊 "我操你X的IE" 了吧?

且慢,李組長眉頭一皺,覺得案情並不單純...

深入做了幾個實驗,發現無法輸入的Textarea,click事件是活著的,disabled/readonly屬性也未被設定;而同網頁上的Button、Checkbox、Radio Button盡皆正常,這實在太詭異了。

於是我將可以重製問題的網頁盡可能簡化以利研究,變成以下的樣子:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="../js/jquery.js" type="text/javascript"></script>
    <script src="../js/jquery.blockUI.js" type="text/javascript"></script>
    <script type="text/javascript">
        function ShowIFrame(url) {
            var frmHtml = 
            "<iframe style='width: 100%; height:100%;' src='" + url + "'></iframe>";
            $.blockUI({
                css: { width: '200px', height: '200px' },
                message: frmHtml
            });
        }
        $(function() {
            $("#btnShow").click(function() {
                ShowIFrame(location.href + "?x=2");
            });
            $("#btnClose").click(function() {
                parent.eval("$.unblockUI();");
            });
            if (parent.window != window)
                $("#btnShow").hide();
            else {
                $("#btnClose").hide();
                $("#ta").hide();
            }
 
        });
    </script>
</head>
<body>
<input type=button value='Open SubWin' id="btnShow"/>
<input type=button value='Close SubWin' id="btnClose"/>
<textarea id="ta"></textarea>
</body>
</html>

一開始我寫成ShowIFrame(location.href),結果遇上了IE的IFrame遞迴防呆而失效。說來好笑,這個意外讓我因禍得福,在研究為什麼IFrame裡一直是一片空白的過程中,我用IE8 Dev Tools在空白頁的HTML Source中看到一個可疑的註記!

<META content=WIMRCAXYME name=SKYPE_FRAMEID>

Skype? 我的網頁被Add-on加料了! 會不會TextArea的問題也與Add-on有關? 要驗證這個問題最快的方法就是開啟Accessories/System Tools/Internet Explorer (No Add-ons),在沒有Add-on的狀態下開啟IE,再試了一次問題網頁,欄位無法輸入的症狀不藥而瘉。如此,便可確定問題出在Add-on身上。

很快地找到Skype Add-on,予以停用,苦惱數小時的TextArea失效問題迎刃而解。

換句話說,在本次事件中,IE無罪,問題出在Add-on上;若沒有李組長明察秋毫(遠目),IE恐怕會承受這不白之冤。不過我想,IE此刻的心情應該像被罵慣了的何大人,山西布政司放的屁算他頭上也懶得辯解吧... (話雖如此,IE常跟其他瀏覽器行為不同,還是讓我很想揍它)

原本提供擴充應用空間的Add-on設計美意,隨著各家廠商開發出五花八門的外掛,安裝在各式各樣的環境組合中,常演變出難以預期的結果。依微軟自己的統計,有很大的比例,IE的Crash主因常出在外掛上,而非IE本體,這也是為什麼程式集中會特別提供一個IE無外掛執行模式的原因。

因此,大家未來在遇到IE有光怪陸離行為時,記得先用無外掛模式執行一次,先排除Add-on是元凶的可能性,再對IE爆粗口繼續追查,應可事半功倍。

Posted 29 June 2009 09:11 AMJeffrey | 4 comment(s)
Filed under:
瀏覽器的IFrame無窮迴圈防呆

在射茶包的過程中,發現了一個有趣的瀏覽器行為。

程式碼如下,按下Button會在IFrame元素中開啟目前所在網頁。會寫成這種架構是打算讓一個網頁同時扮種兩種角色,不必為了一個小測試搞出兩個HTML檔案來。實際的測試中,我用if (parent != window)判別網頁是否在IFrame中被開啟,執行不同的功能。這裡只為了突顯瀏覽器的行為特性,我把程式碼簡化到最少。

<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<iframe id="f" src=""></iframe><br />
<input type="button" value="showFrame" 
onclick="document.getElementById('f').src=location.href;" />
</body>
</html>

我本來預期按下Button後,IFrame中會出現同一個網頁,結果不然!

使用IE8進行測試,按下Button後,啥事也沒發生,IFrame中空白一片,由網頁屬性發現它仍維持about:blank,也就是src並未被改變。如果把src設定改為http://www.google.com http://www.bing.com(身為MVP,偶爾也要從事一下政令宣導莒光教學咩)則行為正常。看來,問題出在location.href。接著我把location.href改成httq://localhost/lab/recursive.htm,依然無效,但再改成recursive.htm?x=1,則可以順利開啟。

經過推敲,我覺得這是一種安全機制,防止網頁內嵌自己,內嵌那一分又再內嵌了自己,形成無窮迴圈會吃光資源。打個比喻,如果將DV的即時影像輸出到電視上,再用DV去拍電視,畫面就會變成一層包一層,層層相包無窮無盡的電視框。還想像不出來的人,我們請基努李維親自示範一下類似的效果。

就像程式的遞迴呼叫Recursive有相對的防呆,瀏覽器也要設法防止這種內嵌無窮迴圈發生! 好奇心起,用各家瀏覽器做了一下測試,發現有三種結果:

IE、Opera: 完全不允許內嵌自己,iframe.src = location.href 無反應。

Chrome、Safari: 只能呼叫一層,內嵌的那一層便不再允許內嵌自己。

Firefox: 會出現錯誤訊息--uncaught exception: [Exception... "Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIDOMHTMLIFrameElement.src]" nsresult: "0x8000ffff (NS_ERROR_UNEXPECTED)" location: "JS frame :: httq://localhost/Lab/Recursive.htm :: onclick :: line 1" data: no]

以上是我的心得報告。

Posted 29 June 2009 01:30 AMJeffrey | 3 comment(s)
Filed under: ,
Mail Server for Vista

太驚訝了... Vista居然把SMTP Service拿掉了!!

要跑簡單的測試,在Windows安裝選項中找了半天,遍尋不著SMTP Service的項目。Goggle了一下,證實: Vista把STMP Service拿掉了 拿掉了 拿掉了 拿掉了 拿掉了~~~

網路上有人推免費的Free SMTP Server,一個簡單的前景程式,很講義氣地就只提供SMTP服務,完全不囉嗦。但發現用起來還是有點問題,因為其實我還需要模擬信件傳送,Free SMTP Server在傳送信件到真實信箱時,很容易因為PC Client DNS反向註冊等問題被當成可疑垃圾信件源被擋信。而先前在Windows 2000/2003 Server上,可以模擬出本機信件地址,透過檔案或POP3找到剛才寄出的信件,這是Free SMTP Server做不到的。

依照我的需求,我還是需要一個可寄可收的模擬小郵局,搜索了一下,找到以前做專案時曾用過的MDaemon,牌子老、信譽好,最重要的是,有MDaemon 免費五人版可用。

約50MB,快速下載安裝,馬上完成了發信收信測試,功能正常,很好很強大。

【心得】以後要開發專案,還是乖乖裝Server級OS比較省事。

就是那個光...

今早噗浪時看到這個...

不知不覺間,Karma已經超過81,抵達涅槃之境,獲得太極勳章一枚。(怪了,佛教的涅槃配上道教的太極,這是萬流歸宗嗎?)

依我個人的觀點,噗浪/Twitter是Internet從BBS, Homepage, Blog, Forum, MSN(Instant Messenger)之後,另一項全新的應用突破。它帶有幾分MSN的即時性,在社群涵蓋度上直追BBS,尤其噗浪的討論串概念更營造出另一塊"鄉民"得以大展身手的空間。(我認為現在的記者除了上PTT、訂部落格之外,少不了要加幾個名人噗浪當好友才不會Lag)

歷經幾個月的使用經驗,我發現噗浪有些有趣的特性是其他平台、溝通工具無法取代的:

  • 雞犬相聞好親切:
    台中現在在下大雨、高雄今天太陽很大、早上在XX路口"又"看到車禍(我一直懷疑某噗友的體質特殊,車禍老是如影隨行)... 每天都可以即時收到來自台灣各個角落的最新消息,讓人有莫名的親切感。鄉親吶,這就是愛台灣啦~~~
  • 聚沙成塔蒐遺珠:
    說實在說,過有許多技術上的小Tip,不到自成一篇Blog文章的規模,但又有幾分記錄下來的價值,十分雞肋。噗浪算是很不錯的部落格替代平台,寫個幾行貼上去,分享兼備忘(中年人很需要),而我也在噗浪上檢到不少部落客沒貼成文章的技術遺珠。
  • 產業經驗大家談:
    我結識的噗友多在資訊產業,沒機會實際認識那麼多人,噗浪倒是給了機會跟一大群同產業的朋友討論專案執行、軟體工程、客戶習性、甚至公司文化議題的機會,是很棒的經驗交流管道。
  • 即時救援人情濃:
    遇到緊急難解問題,噗一聲很快就有回音,噗浪的輕便與即時性,讓參與社群的人更不吝於發聲。畢竟,噗一下比在討論區/部落格上留言更方便更自然。另外,我一定要推一下它的即時性! 有一次我發現MSDN下載有問題,但不確認是否為我所在網路的個別問題,在噗浪上問一聲,不到幾分鐘就得到許多噗友的熱心回應,證實是我的網路被MSDN網站排擠了(鳴~~)。在日益冷漠的現代社會裡,還能感受到濃濃的人情味,是很難得的事。
  • 喊苦渲洩兼取暖:
    噗浪不若部落格正式,閒聊性質更強,回應時效也高,無形中也是大家釋放負面能量的場所。吐吐苦水、喊喊累、罵奧客,都能獲得噗友的關心。我在想,這應該比下鄉取暖來得簡便省事多了,可惜看守所不給上網。
  • 流行八卦一把抓:
    只要加到幾個消息靈通又愛分享的噗友,就能確保你在第一時間掌握最新消息。最近已有數次經驗,同事傳來有趣的影片文章,都已在噗浪河道上出現過。

你今天噗浪了嗎?

CODE-enum, string, int間的轉換

專案剛好用到列舉型別(enum)的處理,之前雖已寫過一篇相關文章,但這次又多用到了找上下筆及列出清單的特性,索性再整理一篇更完整的。

以下示範列出所有列舉項目及對應數值、字串、整數與列舉間的雙向轉換寫法,還有尋找上一個、下一個列舉值的簡陋做法(很不嚴謹,僅為示意,想深入研究的人這裡有個LINQ範例可參考)。注意: 字串轉列舉無對應時會有Exception,數字轉列舉無對應值時不會出錯而是出現未列舉宣告值的數字。(列舉居然可以出現非預先設定範圍內的值,不就打破了其存在的意義? 這點始終讓我很狐疑)

using System;
 
public class CSharpLab
{
    public enum State
    {
        Init, Open, Proc, Close, Done = 9
    }
    public static void Test()
    {
        State st = State.Init;
        //列出所有名稱
        foreach (string en in Enum.GetNames(typeof(State)))
            Console.WriteLine("Enum Name: " + en);
        //列舉背後有值, 預設由零開始排, 但也可自訂
        //TIPS: typeof(State)與st.GetType()都可取得列舉型別
        foreach (int v in Enum.GetValues(st.GetType()))
            Console.WriteLine("Enum Value: " + v.ToString());
        //將列舉轉為字串
        Console.WriteLine("Enum To String: " + st.ToString());
        //將字串轉為列舉
        st = (State)Enum.Parse(typeof(State), "proc", true);
        Console.WriteLine("String To Enum: " + st);
        try {
            Console.WriteLine("String To Enum(not found): " + 
                                    Enum.Parse(typeof(State), "WTF"));
        } catch (Exception ex) {
            Console.WriteLine("Error when Enum.Parse: " + ex.Message);
        }
        //將列舉轉為數字
        Console.WriteLine("Enum to Int: " + st + " " + (int)st);
        //將數字轉回列舉
        st = (State)9;
        Console.WriteLine("Int to Enum: " + st);
        //小心數字轉換對不上時不會有錯誤,但會出現非列舉值
        st = (State)100;
        Console.WriteLine("Int(100) to Enum: " + st);
        //如果數字有連續,可以用+1, -1找上一個下一個
        //注意: 以下範例並未檢查邊界及數值不連續的狀況, 
        //並非嚴謹寫法,僅為簡單示意,勿用於正式用途
        st = State.Proc;
        State next = (State)((int)st)+1;
        State prev = (State)((int)st)-1;
        Console.WriteLine("Now = " + st + " Next = " + next + " Prev = " + prev);
    }
}
 

執行結果如下(以上程式可用Mini C# Lab直接執行),橘色為字串轉換無法對映的狀況,綠色為數字轉換無法對映的狀況。

Enum Name: Init
Enum Name: Open
Enum Name: Proc
Enum Name: Close
Enum Name: Done
Enum Value: 0
Enum Value: 1
Enum Value: 2
Enum Value: 3
Enum Value: 9
Enum To String: Init
String To Enum: Proc
Error when Enum.Parse: Requested value 'WTF' was not found.
Enum to Int: Proc 2
Int to Enum: Done
Int(100) to Enum: 100
Now = Proc Next = Close Prev = Open

最後補充一個用法,enum可以加上[Flags]宣告,再將各值設成0x01, 0x2, 0x4, 0x8...,就可以當成旗標用myFlags = MyEnum.Flag1 | MyEnum.Flag2的方式操作,myFlags.ToString()時會傳回Flag1, Flag2。MSDN上有完整的範例,這裡不再贅言。

Posted 24 June 2009 08:23 AMJeffrey | 2 comment(s)
Filed under:
在Firefox中透過Javascript存取剪貼簿

在上一篇強化程式範例複製功能裡,其實迴避了一個問題: 使用者真正想要的操作是點一下就搞定呀! "點一下->Ctrl-A->Ctrl-C"的操作步驟肯定會被嫌棄。不過,據我先前的了解,存取剪貼簿的Javascript存在跨瀏覽器的問題,除了IE內建支援外,其他瀏覽器等靠另嵌Flash物件達成,以前找到的解決方案在Flash Player 10改變剪貼篿存取政策後就壞了,我也一直發懶沒去找替代方案。無法解決IE以外瀏覽器的複製功能,獨厚IE必會引發民怨,為了公平起見,索性就不加自動複製功能了。(謎之聲: 真的是因為這樣嗎? 懶鬼!)

琛哥說,出來混,遲早是要還的。

果不其然,貼文不到幾小時,自以為天衣無縫的裝死行徑立刻被眼尖網友識破,只好乖乖出來誠實面對。

爬了一下文,很幸運地找到了一個Frefox的解決方案,不需引用額外的Flash,只要調整Firefox的signed.applets.codebase_principal_support安全設定即可支援。
(警告: 啟用該選項會增加資安風險,但就我的理解,它所開放的範圍限於經電子簽章簽署過的元件,應無重大漏洞。但各位仍應依自己的資安原則,斟酌是否使用。)

我已在網站加上複製文字的功能。在IE裡可以直接服用,Firefox則需要透過以下的步驟調整設定,至於其他瀏覽器,我則悍然決定繼續裝死。(如果有人知道解決方案,歡迎提供)

在Firefox網址列輸入”about:config”,會先出現警告訊息。(題外話,這個警告訊息的用字深得我心呀!)

在篩選條件輸入signed就可以馬上找到該選項,將值由false改成true。

如此,在Firefox也可以享受直接打包帶走的服務囉!

以下是我偷學到的剪貼簿複製函數,順手貼出來供有興趣研究的熱血青年參考:

//Copy to clipboard: ref http://forum.moztw.org/viewtopic.php?p=131407
function copyToClipboard(txt) {
    var copied = false;
     if(window.clipboardData) {
        window.clipboardData.clearData();
        window.clipboardData.setData("Text", txt);
        copied = true;
     } else if (window.netscape) {
        try {
           netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
        } catch (e) {
           alert("被瀏覽器拒絕!\n請在瀏覽器網址列輸入'about:config'\n,將'signed.applets.codebase_principal_support'設為'true'");
        }
        var clip = Components.classes['@mozilla.org/widget/clipboard;1']
        .createInstance(Components.interfaces.nsIClipboard);
        if (!clip)
           return;
        var trans = Components.classes['@mozilla.org/widget/transferable;1']
        .createInstance(Components.interfaces.nsITransferable);
        if (!trans)
           return;
        trans.addDataFlavor('text/unicode');
        var str = new Object();
        var len = new Object();
        var str = Components.classes["@mozilla.org/supports-string;1"]
        .createInstance(Components.interfaces.nsISupportsString);
        var copytext = txt;
        str.data = copytext;
        trans.setTransferData("text/unicode",str,copytext.length*2);
        var clipid = Components.interfaces.nsIClipboard;
        if (!clip)
           return false;
        clip.setData(trans,null,clipid.kGlobalClipboard);
        copied = true;
     }
     if (copied) alert('文字內容已複製到剪貼簿中!');
     else alert("使用的瀏覽器不支援文字複製功能!");
}    
令人垢病的程式範例複製功能,終於改善了

過去不少網友反映過我網站上的程式碼複製不易。

其實,我自己也身受其害... 有時要從以前的文章抄範例來用,用Firefox複製得到的結果會買一送一,每列都加附一列空白;用IE則是程式碼斷行通通消失。我後來試出一種克難解法是先Copy到Word,再由Word Copy出來,就能萃取出原汁原味。

一個成天在搞網站開發的老傢伙,竟在自家網站留下如此敗筆,實在是人神共憤,天地不容吶~~~ 但一皮天下無難事,想偷懶總能找到一籮筐的理由,於是,就這麼一年撐過一年,網友礙於【黑暗】二字的淫威,敢怒不敢言,也只能隱忍在心,沒事再紮紮小草人渲洩一番。

今天大概是良心發現,想說自己老在鼔吹jQuery有多好用多好用,這不就是最好的驗證機會? 所以,我用jQuery寫了以下的Code,做出了上圖的效果: 每個程式碼區上方會多出兩個切換連結,點下"純文字"就會以Textarea方式顯示程式內容,大家就可以快快樂樂輕輕鬆鬆把程式帶回家囉!

有什麼使用上的問題,再反應給我吧!

$(function() {
    $("div.BlogCodeBlock").each(function() {
        $codeBlock = $(this).children("div:first");
        var code = []
        $codeBlock.children("pre").each(function() {
             code.push($(this).text());
        });
        var $ta = $("<textarea class='cCodeBlockTextarea'></textarea>");
        $ta.height($codeBlock.height()).width($codeBlock.width())
        .val(code.join("\n")).css("display", "none").insertAfter($codeBlock);
        $codeBlock.before(
        "<div class='cCBBar' style='background-color: #888888; padding: 3px'>" + 
        "<span class='cCBShowHL'>排版顯示</span>" +
        "<span class='cCBShowPT'>純文字</span></div>");
    });
    var cLEDColor = "yellow";
    $(".cCBShowHL,.cCBShowPT")
    .css({ 
        "text-decoration":"underline",
        "cursor":"pointer",
        "color":"black",
        "margin-left":"5px"
    }).click(function() {
        if ($(this).css("color") == cLEDColor)
            return;
        $(this)
        .css({ color:cLEDColor })
        .siblings()
        .css({ "color":"#444444" })
        .end().parent()
        .next().toggle().next().toggle();
    }).filter(".cCBShowHL").css("color", cLEDColor);
});
Posted 21 June 2009 04:08 AMJeffrey | 3 comment(s)
Filed under:
TIPS-設定WCF使用Windows認證

原本測試OK的WCF,在取消IIS匿名存取,改用整合式驗證後出現以下錯誤訊息:

Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service.

爬了一下文,大致的心得是要在web.config改變安全設定。原本的設定是VS2008建立AJAX enabled WCF時自動產生的:

<system.serviceModel>
    <behaviors>
        <endpointBehaviors>
            <behavior name="WebApiAspNetAjaxBehavior">
                <enableWebScript/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <services>
        <service name="WebApi">
            <endpoint address="" behaviorConfiguration="WebApiAspNetAjaxBehavior" 
             binding="webHttpBinding" contract="WebApi"/>
        </service>
    </services>
</system.serviceModel>

要修改為:

<system.serviceModel>
    <behaviors>
        <endpointBehaviors>
            <behavior name="WebApiAspNetAjaxBehavior">
                <enableWebScript/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <services>
        <service name="WebApi">
            <endpoint address="" behaviorConfiguration="WebApiAspNetAjaxBehavior" 
              binding="webHttpBinding" contract="WebApi"
              bindingConfiguration="NTLMBinding"
              />
        </service>
    </services>
<bindings>
  <webHttpBinding>
    <binding name="NTLMBinding">
      <security mode="TransportCredentialOnly">
        <transport clientCredentialType="Windows"/>
      </security>
    </binding>
  </webHttpBinding>
</bindings>
</system.serviceModel>

醬就可以柳~~~ (羞昂風)

Posted 20 June 2009 03:23 AMJeffrey | 1 comment(s)
Filed under:
【閒聊】大家選擇的是C#或VB.NET呢?

一時興起想到的題目。

開發.NET程式,C#與VB.NET幾乎具有完全相同的能力,微軟的官方想法本來也就是蘿蔔青菜各有所好,把使用語言的決定權交給開發人員決定。

我在學.NET前寫過好幾年的VB及ASP,因此一開始入門是從VB.NET開始的,但只玩了一兩個月後就改走C#(雖然微軟在推出C#/VB.NET時的說法,C#適合有C/Java背景的人,我兩個都沒有),一直到現在。

我個人偏好C#的理由是: (純個人意見,非關語言優劣)

  1. 用字比較精簡,{ } vs Begin End、繼承、實作介面C#只用一個:就打死,而VB.NET則是用保留字。程式一長,當場省下好多文字。
  2. 語法區隔性比較強。像C#陣列用[],函數用(),等於比輸用==;而VB.NET陣列與函數都用(),指定與比較都用=。
  3. 排列自由,可以任意空白、換行,把程式碼調成自己想要呈現的方式,易於閱讀。
    例如:
    if (
         a == b || //Blah blah
         c == d || //Blu Blu
         e == f     //Kero Kero
        )
    { … }
    VB.NET也可以,但要多加_,比較繁瑣。
  4. C#裡有@"..."可以用,程式要內嵌SQL或Javascript時很方便:
    string sql = @"
    SELECT *
    FROM WTFTable
    WHERE Col1 = ‘kerokerokero’”;
  5. 與Javascript的語法較相近,不必同時熟悉兩套不同的語言(年紀大了腦容量變小),寫Web時可以減少大腦Context Switch的功夫(呵)。不過,還是常常不小心把二者搞混,在Javascript中打成foreach (var p in q)或for (int i = 0; …)。
  6. 網路找到的進階範例程式,C#的比例比VB.NET高。以CodeProject做個不嚴謹的例子,比一下文章的發表頻率便知: C#VB.NET(看下方Last 10 Updates的發表日期)。
  7. 當初還有個天真的想法,C#跟Java比較接近,"萬一"(呃... 只是萬一而已)必須轉到Java時,可以比較快接軌。(不過現在看來,這個萬一發生的機率跟中樂透頭彩差不多)

但C#還是有吃癟的場合:

  1. VB.NET在.NET 3.0裡對XML表示有額外的支援: 如直接內嵌XML(XML Literals)、Default Namespace
  2. VB.NET支援Named Parameter及預設參數值,例如: word.Documents.Open( FileName:=file_title, ConfirmConversions:=False, ReadOnly:=False ),若用C#寫,十幾個參數得全部列出來。
  3. 有些應用程式只支援VB.NET,例如: Reporting Service的Embedded Code。

大家都慣用哪一種語言呢?

Posted 19 June 2009 10:01 AMJeffrey | 31 comment(s)
Filed under:
80萬人次紀念

80萬人次囉~~~ 照例還是要貼文一篇留念,謝謝大家支持!

【成長歷程】

CODE-Javascript測速用碼錶

前陣子我分享了關於JS效能調校的經驗,IE8 Dev Tool是個很方便的工具。不過,不是每種瀏覽器上都有Profiler可用,若想在不同瀏覽器上都能精確地量測某段操作的時間長短,寫一個Javascript版的Stopwatch計時碼錶應是最直接有效的方法。所以我寫了一個JS計時碼錶---darkStopWatch:

//Declare a stopWatch "class"
function darkStopWatch(timerName) {
    this.timerName = timerName;
    this.timerSt = new Date();
    this.resultQueue = [];
    this.tag = "Test";
}
darkStopWatch.prototype = {
    start: function(tag) {
        this.tag = tag;
        this.timerSt = new Date();
    },
    stop: function() {
        var ts = new Date() - this.timerSt;
        this.resultQueue.push(this.tag + " -> " + ts + "ms");
    },
    getRecord: function(clear) {
        var s = this.resultQueue.join("\n");
        if (clear) this.clear();
        return "StopWatch [" + this.timerName + 
               "]\n====================\n" + s;
    },
    reset: function() {
        this.resultQueue = [];
    }
}

使用方法還蠻簡單的,利用new darkStopWatch("碼錶標籤")宣告物件,用start(“計時標籤")開始計時、stop()停止計時,darkStopWatch會將start()到stop()的時間記錄為該"計時標籤"的時間長度,並可反覆多次start()/stop(),最後則是用getRecord()取回所有計時結果。

以下的範例有三段,第一段是計算你按下alert確認鈕的時間長度、第二段是連續五次計時,每次長度為1秒,第三段則是示範一次可以使用多個碼錶。Check it out!

//Usage
var sw = new darkStopWatch("Alert Delay");
sw.start();
alert("請等待幾秒鐘再按下確定");
sw.stop();
alert(sw.getRecord());
 
//Use setTimeout go test the measure unit
function measure() {
    var sw = new darkStopWatch("歸零射擊");
    sw.start("Test 1");
    var count = 1;
    var hnd = setInterval(
        function() {
            sw.stop();
            if (count < 5) {
                count++;
                sw.start("Test " + count);
            }
            else {
                clearInterval(hnd);
                alert(sw.getRecord());
            }
        },
    1000);
    //Demo 2, multiple stopwatches
    window.swAry = [];
    for (var i = 1; i <= 5; i++) {
        swAry[i] = new darkStopWatch("Swatch " + i);
        swAry[i].start("Wait for " + i + " sec");
        setTimeout("swAry[" + i + "].stop()", i * 1000);
    }
    setTimeout(function() {
        for (var i = 1; i <= 5; i++)
            alert(swAry[i].getRecord());
    }, 6000);
}
measure();
        
jQuery Textarea - 該用val()還是text()

網頁在FF下不正常,搞了半天才發現問題出在我使用val()指定Textarea的內容,畫面顯示看來一切OK,但經過clone()後,內容值卻消失了。

我整理出以下的範例做測試: (可用Mini jQuery Lab直接執行)

$("<div id='x1'><textarea id='t1'></textarea>" + 
"<textarea id='t2'></textarea></div>").appendTo("body");
$("#t1").text("AAA");
$("#t2").val("BBB");
alert("t1=" + $("#t1").text() + "/" + $("#t1").val());
alert("t2=" + $("#t2").text() + "/" + $("#t2").val());
alert($("#x1").html());

在IE下,不管用val()或text()設定,後續的讀取都正常;但在Firefox下,使用val()指定的值,畫面上會出現,但是用text()或是透過html()檢視時卻是字串。因此在Firefox中,如果你希望設定給textarea值出現在html()中或可以被clone(),請用text(...)設定。

且慢!! 事情如果這麼單純,那麼連小學生也會跨瀏覽器了。使用text()設定時得注意換行問題,若你在IE中下text("A\nA"),在顯示時只會呈現空一格而不會換行。依我測試的結果,在IE下text("A\r\A")得到顯示結果比較接近預期,但是text("A\rA")在Firefox中顯示時會換列,用text()取出時卻是連在一起的... 這... 這... 這...

最後,我採取了懦夫策略,在呼叫.clone()前做了這件事迴避問題:

$theDiv.find("textarea").each(function() { $(this).text($(this).val()); });

很醜,但看來是有效的! 如果有人有其他好點子,再分享一下吧!

【心得】沒有劈成一字馬的本事,不要跟別人說你會"跨"瀏覽器!

CODE-分贓程式的寫法

把一筆錢依特定的比例分給幾個人是我工作上常要處理的需求。由於金額必須四捨五入到元或分,因此常需面對除不盡的錢要設法攤掉的問題。例如100元平分給三個人,每人33元後,最後的1元要發給三人之一的幸運兒,變成一人34, 兩人33的分配結果。

以前年紀小不懂事,很直覺的想法是先用100*1/3四捨五入得到33把錢分一分,之後再跑一個迴圈(沒辦法,總不能打電話請這三個人過來猜拳吧?)把分剩的錢(總金額大、人數多時餘下數十上百元也是有可能滴)每次一元地發下去,直到發光為止。

說實在說,當初並不覺得這個寫法有什麼不對,直到有前輩指點了另一種更精巧的演算法,一口氣就能把錢攤到一毛不剩,省去分完一輪後處理餘數的麻煩。相形之下,原本的寫法挺笨拙的...

using System;
using System.IO;
using System.Threading;
 
public class CSharpLab
{
    public static void Test()
    {
        //100萬元要依比例分給3個人(四拾五入計算到元)
        int totalAmt = 1000000;
        int[] amt = new int[3];
        //三個人勢均力敵,100萬除以3會有餘1元的問題
        //且看以下的邏輯演法如何避免事後分攤的困擾
        decimal[] fact = new decimal[] { 1.2M, 1.2M, 1.2M };
        //先加總分配權重
        decimal factSum = 0;
        for (int i = 0; i < fact.Length; i++)
            factSum += fact[i];
        //使用以下邏輯分配, 會自然吸收掉四捨五入差額
        for (int i = 0; i < fact.Length; i++)
        {
            amt[i] = Convert.ToInt32(
                    Math.Round(totalAmt * fact[i] / factSum, MidpointRounding.AwayFromZero)
                );
            Console.WriteLine("{0} * {1} / {2} = {3}", totalAmt, fact[i], factSum, amt[i]);
            totalAmt -= amt[i];
            factSum -= fact[i];
        }
    }
}

執行結果是: (以上程式可以用Mini C# Lab直接測試)

1000000 * 1.2 / 3.6 = 333333
666667 * 1.2 / 2.4 = 333334
333333 * 1.2 / 1.2 = 333333

很棒吧! 它可以很自然順暢地在分攤過程中吸收掉四捨五入可能產生的差額,一步到位!

最近手上的案子在寫AJAX網頁,開始把這樣的概念搬到Javascript上實作。(以下程式可以使用Mini jQuery Lab測試)

var factor = [ 1.2, 1.2, 1.2 ], factorSum = 0;
var totalAmount = 1000000, amount = [];
for (var i = 0; i < factor.length; i++) factorSum += factor[i];
var r = "";
for (var i = 0; i < factor.length; i++)
{
    amount[i] = parseInt(Math.round(totalAmount * factor[i] / factorSum))
    r += totalAmount + " * " + factor[i] + " / " + factorSum + " = " + amount[i] + "\n";
    totalAmount -= amount[i];
    factorSum -= factor[i];
}
alert(r);

執行結果是

1000000 * 1.2 / 3.5999999999999996 = 333333
666667 * 1.2 / 2.3999999999999994 = 333334
333333 * 1.2 / 1.1999999999999995 = 333333

數字是對的,但明眼人已可看到其中暗藏殺機... 3.5999999999999996? 明明應該是3.6,因為Javascript裡只有浮點數,沒有像.NET decimal一樣分亳不差的精準數字型別,所以數字都是用逼近的。

俗話說得好: "算錢用浮點,遲早被人扁"! 雖然浮點運算的微小誤差多半要遇到天文數字計算或複雜的累乘累除時才會爆炸,但實事求事總是比較好,跟錢有關的東西,一丁點不對都會吵翻天。"插擠摳"(差一元)是每一個帳務會計程式開發人員的惡夢,為了避免未來某一天為了找一塊錢找到吐血,這裡還是花點心思防範未然。

var factor = [1.2, 1.2, 1.2], factorSum = 0;
var totalAmount = 1000000, amount = [];
function r2(n) { return parseFloat(n.toFixed(2)); }
for (var i = 0; i < factor.length; i++) 
    factorSum = r2(factorSum + factor[i]);
var r = "";
for (var i = 0; i < factor.length; i++)
{
    amount[i] = parseInt(Math.round(totalAmount * factor[i] / factorSum))
    r += totalAmount + " * " + factor[i] + " / " + factorSum + " = " + amount[i] + "\n";
    totalAmount -= amount[i];
    factorSum = r2(factorSum - factor[i]);
}
alert(r);

我假設factor的精準度到兩位,利用to.Fixed(2)的方法四捨五入加總及刪減過的結果。經過這番修正,結果好看多了!

1000000 * 1.2 / 3.6 = 333333
666667 * 1.2 / 2.4 = 333334
333333 * 1.2 / 1.2 = 333333

【心得】Javascript在數字處理上挺弱的,沒有decimal,型別不嚴謹,計算速度無法跟.NET等編譯式語言匹敵,連四捨五入到小數第幾位都得繞一圈。不過看在它可以輕易跨平台跨瀏覽器的分上,只好摸摸鼻子認了。Silverlight 3.0正式版快要誕生了,過陣子再來Survey它與AJAX網頁應用上的互補性。

好用到掉渣的IE8 Developer Tools--JS效能調校經驗

對我來說,IE8最讓人興奮的新功能非"IE8 Developer Tools"莫屬!! (沒騙你們,我有文章為憑: 1 2 3

上市後,IE8立即取代Firefox成為我開發網頁時的主力測試工具,也開始體驗它的強大威力。

像是可任意下指令的Console視窗我就超愛,把整個網頁玩弄於股掌之間的感覺真好~~

就拿新聞網站為例,先用上次提過的技巧載入jquery-1.3.2.js,用HTML Tab的工具觀察DOM結構,然後可以在Script Tab的Conosle區一行一行下指令,便可邊試邊改,恣意地上下其手(邪笑),享受惡搞的樂趣,哇哈哈哈~~~ (謎之聲: 應該沒人像你這麼無聊吧?)

CSS編輯器可以任意新增Rule、啟用停用Rule的功能也很讚,這點先前已介紹過

如果你跟IE8 Developer Toolbar還不熟,點部落上好幾篇精彩的介紹文可以為你們搭起友誼的橋樑:

IE8 Dev Tools的介紹已經很多了,今天則來談談我這兩天利用它調校Javascript效能的實戰經驗。

故事是這樣的,手上的案子需要寫一個類似Excel試算表的網頁介面,總共有近1500格(120列*12欄)的大表格,每列有兩個欄位可以輸入數字,而任一格修改數字(如紅框所示)後,會影響整個分配結構,全表有大半的欄位都要重算過(如綠框所示)。

開發初期,我用最直覺的寫法,$("..input..").change()時利用$(this).parent().next()之類的技巧去找前後格子讀取數字,重新計算加總,再將結果回寫其中。一開始只有十來列時速度還OK,等到測試極端案例(120列)時,問題就大了。每更改一格的內容平均要等上3-5秒才會完成更新,有時甚至IE8會跳出Slow Script警告,詢問是否要停止執行。

觀察到這種現象,心中已有不祥預感。顫抖著雙手,用未來使用者的標準配備---老爺PC+IE6組合而成的魔王機(由來)做一次測試,果不其然,IE6以從頭到尾CPU 100%的姿態當住不動,連正常關閉程式都沒辦法。

好了,是該調校效能的時候了。永遠記得效能調校的第一步驟-->要能找出效能衡量的明確指標!

IE8 Dev Tools的Profiler可以分析並記錄各個Javascript函數被呼叫的次數以及執行所耗時間,無疑是絕佳的觀察工具。操作的方法是,先按下"Start Profiling"(橘框所在),接著在網頁上進行操作,再按下"Stop Profiling"。每次按下"Stop Profiling",就會產生一份Report,介面上有個下拉選單(藍框處)可以切換多次測試的結果進行比較。

報告結果可以依函數做統計,但我偏好的是"Call Tree"方式(紫框),可像Call Stack一樣,列出函數間彼此呼叫的來龍去脈。一般來說,我們首先關心的是累計執行時間,值愈大代表效能愈差。因此,整個調校的過程就是設法讓這個數字愈來愈小!

Call Tree檢視的好處在於,你可以先鎖定最慢的函數,展開它所呼叫的函數,再找出這些子呼叫中最慢的再展開,一路找下去,很快就能查到效能不彰的根源並加以改善,立竿見影。找到有問題的函數,右方會有函數所在的js檔案URL以及列數,點兩下,IE8 Dev Tools馬上就會在Script Tab開啟該js,並捲到該列程式所在位置,貼心到破錶吧! 當然,要改JS程式內容得回VS2008,修改後重新整理網頁,再做一次同樣動作並用Profiler監測,比較看看修改後累積耗用時間是否明顯下降。

重覆以上的步驟,我終於每次改值重算的時間由4,5秒降到1秒左右,雖不滿意,但可接受了。

應該有人很好奇Javascript程式要怎麼修改才會變快,我想這部份Case by Case,並無通用的簡便法則,有時甚至要違背一般的程式設計準則才能達到目的。(就像設計資料庫Schema時,透過"反正規化"改善查詢效率一樣)

但我還是整理一下本次修改的心得:

  • 原本的設計將數值直接存在td裡,讀寫時都要涉及UI元素的存取,是效能不彰的最大元凶。我後來利用Javascript Object在背後建立出對應表格的完整資料陣列,每次重算時先在物件陣列裡算好,再將結果寫回前端。這樣做較不直覺,且程式複雜許多,但存取UI元素比操作Javascript元件慢上數倍,為了達到預期的效能,似乎是唯一解。
  • 加總最直覺的算法是用for迴圈累加全部的數字,當其中一個值改變時,更快的寫法是將加總值扣掉原值,加上新值,避免重新跑迴圈重算。一樣的,這種設計較不直覺淺顯,但CPU耗用及速度都改善許多。
  • children()比find()快
  • 少用Regular Expression
  • 為每個td取專屬名字, 利用$("#...")找元素
  • 直接存取elem.propName取代$(elem).data("propName")
    非良好慣例,但直的變快很多
  • $(node).text("...")改寫成node.innerHTML = "..."
    因指定的文字內容很單純(純數字),所以可用這種不周全的寫法瓜代。直接寫Javascript不用jQuery,速度變快蠻多,但犠牲了相容性。
  • 不需要串接且會被大量呼叫的Plugin,就把return this.each拿掉

注意: 上述橘色斜體字部分表示非正規建議做法,純粹因加速而走的偏鋒,大家參考時要特別注意,切忌誤用!! (有個比喻,就像賽車都不裝方向燈跟喇叭一樣,是針對特殊情境才有的設計,可別任意仿效改裝你的愛車)

更多文章 下一頁 »

搜尋

Go

<July 2009>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
 
RSS
【工商服務】


BlogLook Score and Rank

Syndication