jQuery 中的編程範式(上)

瀏覽器前端編程的面貌自2005年以來已經發生了深刻的變化,這並不簡單的意味着出現了大量功能豐富的基礎庫,使得我們可以更加方便的編寫業務代碼,更重要的是我們看待前端技術的觀念發生了重大轉變,明確意識到了如何以前端特有的方式釋放程序員的生產力。本文將結合jQuery源碼的實現原理,對javascript中涌現出的編程範式和常用技巧作一簡單介紹。

       1. AJAX: 狀態駐留,異步更新

       首先來看一點歷史。

       A. 1995年Netscape公司的Brendan Eich開發了javacript語言,這是一種動態(dynamic)、弱類型(weakly typed)、基於原型(prototype-based)的腳本語言。

       B. 1999年微軟IE5發佈,其中包含了XMLHTTP ActiveX控件。

       C. 2001年微軟IE6發佈,部分支持DOM level 1和CSS 2標準。

       D. 2002年Douglas Crockford發明JSON格式。

       至此,可以說Web2.0所依賴的技術元素已經基本成形,但是並沒有立刻在整個業界產生重大的影響。儘管一些“頁面異步局部刷新”的技巧在程序員中間祕密的流傳,甚至催生了bindows這樣龐大臃腫的類庫,但總的來說,前端被看作是貧瘠而又骯髒的沼澤地,只有後臺技術纔是王道。到底還缺少些什麼呢?

       當我們站在今天的角度去回顧2005年之前的js代碼,包括那些當時的牛人所寫的代碼,可以明顯的感受到它們在程序控制力上的孱弱。並不是說2005年之前的js技術本身存在問題,只是它們在概念層面上是一盤散沙,缺乏統一的觀念,或者說缺少自己獨特的風格, 自己的靈魂。當時大多數的人,大多數的技術都試圖在模擬傳統的面嚮對象語言,利用傳統的面向對象技術,去實現傳統的GUI模型的仿製品。

       Ajax這一前端特有的概念迅速將衆多分散的實踐統一在同一口號之下,引發了Web編程範式的轉換。所謂名不正則言不順,這下無名羣衆可找到組織了。在未有Ajax之前,人們早已認識到了B/S架構的本質特徵在於瀏覽器和服務器的狀態空間是分離的,但是一般的解決方案都是隱藏這一區分,將前臺狀態同步到後臺,由後臺統一進行邏輯處理,例如ASP.NET。因爲缺乏成熟的設計模式支持前臺狀態駐留,在換頁的時候,已經裝載的js對象將被迫被丟棄,這樣誰還能指望它去完成什麼複雜的工作嗎?

       Ajax明確提出界面是局部刷新的,前臺駐留了狀態,這就促成了一種需要:需要js對象在前臺存在更長的時間。這也就意味着需要將這些對象和功能有效的管理起來,意味着更復雜的代碼組織技術,意味着對模塊化,對公共代碼基的渴求。

       jQuery現有的代碼中真正與Ajax相關(使用XMLHTTP控件異步訪問後臺返回數據)的部分其實很少,但是如果沒有Ajax, jQuery作爲公共代碼基也就缺乏存在的理由。

       2. 模塊化:管理名字空間

       當大量的代碼產生出來以後,我們所需要的最基礎的概念就是模塊化,也就是對工作進行分解和複用。工作得以分解的關鍵在於各人獨立工作的成果可以集成在一起。這意味着各個模塊必須基於一致的底層概念,可以實現交互,也就是說應該基於一套公共代碼基,屏蔽底層瀏覽器的不一致性,並實現統一的抽象層,例如統一的事件管理機制等。比統一代碼基更重要的是,各個模塊之間必須沒有名字衝突。否則,即使兩個模塊之間沒有任何交互,也無法共同工作。

       jQuery目前鼓吹的主要賣點之一就是對名字空間的良好控制。這甚至比提供更多更完善的功能點都重要的多。良好的模塊化允許我們複用任何來源的代碼,所有人的工作得以積累疊加。而功能實現僅僅是一時的工作量的問題。jQuery使用module pattern的一個變種來減少對全局名字空間的影響,僅僅在window對象上增加了一個jQuery對象(也就是$函數)。

       所謂的module pattern代碼如下,它的關鍵是利用匿名函數限制臨時變量的作用域。

  1. var feature =(function() {
複製代碼

       js本身缺乏包結構,不過經過多年的嘗試之後業內已經逐漸統一了對包加載的認識,形成了RequireJs庫這樣得到一定共識的解決方案。jQuery可以與RequireJS庫良好的集成在一起, 實現更完善的模塊依賴管理。http://requirejs.org/docs/jquery.html

  1. require(["jquery", "jquery.my"], function() {
  2.     //當jquery.js和jquery.my.js都成功裝載之後執行
  3.     $(function(){
  4.       $(‘#my’).myFunc();
  5.     });
  6.   });
複製代碼


       通過以下函數調用來定義模塊my/shirt, 它依賴於my/cart和my/inventory模塊,

  1. require.def(“my/shirt”,
複製代碼

       3. 神奇的$:對象提升

       當你第一眼看到$函數的時候,你想到了什麼?傳統的編程理論總是告訴我們函數命名應該準確,應該清晰無誤的表達作者的意圖,甚至聲稱長名字要優於短名字,因爲減少了出現歧義的可能性。但是,$是什麼?亂碼?它所傳遞的信息實在是太隱晦,太曖昧了。$是由prototype.js庫發明的,它真的是一個神奇的函數,因爲它可以將一個原始的DOM節點提升(enhance)爲一個具有複雜行爲的對象。在prototype.js最初的實現中,$函數的定義爲

  1. var $ = function (id) {
  2.   return ”string” == typeof id ? document.getElementById(id) : id;
  3. };
複製代碼


       這基本對應於如下公式

  1. e = $(id)
複製代碼


       這絕不僅僅是提供了一個聰明的函數名稱縮寫,更重要的是在概念層面上建立了文本id與DOM element之間的一一對應。在未有$之前,id與對應的element之間的距離十分遙遠,一般要將element緩存到變量中,例如

  1. var ea = docuement.getElementById(‘a’);
  2. var eb = docuement.getElementById(‘b’);
  3. ea.style….
複製代碼


       但是使用$之後,卻隨處可見如下的寫法

  1. $(‘header_’+id).style…
  2. $(‘body_’+id)….
複製代碼


       id與element之間的距離似乎被消除了,可以非常緊密的交織在一起。

       prototype.js後來擴展了$的含義,

  1. function $() {
  2.   var elements = new Array();

  3.   for (var i = 0; i < arguments.length; i++) {
  4.       var element = arguments[i];
  5.       if (typeof element == ’string’)
  6.         element = document.getElementById(element);

  7.       if (arguments.length == 1)
  8.         return element;

  9.       elements.push(element);
  10.   }

  11.   return elements;
  12. }
複製代碼


       這對應於公式

  1. [e,e] = $(id,id)
複製代碼


       很遺憾,這一步prototype.js走偏了,這一做法很少有實用的價值。

       真正將$發揚光大的是jQuery, 它的$對應於公式

  1. [o] = $(selector)
複製代碼


       這裏有三個增強

       A. selector不再是單一的節點定位符,而是複雜的集合選擇符

       B. 返回的元素不是原始的DOM節點,而是經過jQuery進一步增強的具有豐富行爲的對象,可以啓動複雜的函數調用鏈。

       C. $返回的包裝對象被造型爲數組形式,將集合操作自然的整合到調用鏈中。

       當然,以上僅僅是對神奇的$的一個過分簡化的描述,它的實際功能要複雜得多. 特別是有一個非常常用的直接構造功能

  1. $(“<table><tbody><tr><td>…</td></tr></tbody></table>”)….
複製代碼


       jQuery將根據傳入的html文本直接構造出一系列的DOM節點,並將其包裝爲jQuery對象. 這在某種程度上可以看作是對selector的擴展: html內容描述本身就是一種唯一指定。

       $(function{})這一功能就實在是讓人有些無語了, 它表示當document.ready的時候調用此回調函數。真的,$是一個神奇的函數, 有任何問題,請$一下。

       總結起來, $是從普通的DOM和文本描述世界到具有豐富對象行爲的jQuery世界的躍遷通道。跨過了這道門,就來到了理想國。

       4. 無定形的參數:專注表達而不是約束

       弱類型語言既然頭上頂着個”弱”字, 總難免讓人有些先天不足的感覺. 在程序中缺乏類型約束, 是否真的是一種重大的缺憾? 在傳統的強類型語言中, 函數參數的類型,個數等都是由編譯器負責檢查的約束條件, 但這些約束仍然是遠遠不夠的。一般應用程序中爲了加強約束, 總會增加大量防禦性代碼, 例如在C++中我們常用ASSERT, 而在java中也經常需要判斷參數值的範圍:

  1. if (index < 0 || index >= size)
  2.     throw new IndexOutOfBoundsException(
  3.         ”Index: ”+index+”, Size: ”+size);
複製代碼


       很顯然, 這些代碼將導致程序中存在大量無功能的執行路徑, 即我們做了大量判斷, 代碼執行到某個點, 系統拋出異常, 大喊此路不通. 如果我們換一個思路, 既然已經做了某種判斷,能否利用這些判斷的結果來做些什麼呢? javascript是一種弱類型的語言,它是無法自動約束參數類型的, 那如果順勢而行,進一步弱化參數的形態, 將”弱”推進到一種極致, 在弱無可弱的時候, weak會不會成爲標誌性的特點?

       看一下jQuery中的事件綁定函數bind,

       A. 一次綁定一個事件 $(“#my”).bind(“mouseover”, function(){});

       B. 一次綁定多個事件 $(“#my”).bind(“mouseover mouseout”,function(){})

       C. 換一個形式, 同樣綁定多個事件 $(“#my”).bind({mouseover:function(){}, mouseout:function(){});

       D. 想給事件監聽器傳點參數 $(‘#my’).bind(‘click’, {foo: “xxxx”}, function(event) { event.data.foo..})

       E. 想給事件監聽器分個組 $(“#my”).bind(“click.myGroup″, function(){});

       F. 這個函數爲什麼還沒有瘋掉???


       就算是類型不確定, 在固定位置上的參數的意義總要是確定的吧? 退一萬步來說, 就算是參數位置不重要了,函數本身的意義應該是確定的吧? 但這是什麼

  1. //取值
  2. value = o.val(),
  3. //設置值
  4. o.val(3)
複製代碼

       一個函數怎麼可以這樣過分, 怎麼能根據傳入參數的類型和個數不同而行爲不同呢? 看不順眼是不是? 可這就是俺們的價值觀. 既然不能防止, 那就故意允許. 雖然形式多變, 卻無一句廢話. 缺少約束, 不妨礙表達(我不是出來嚇人的).

       未完待續。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章