jQuery中的編程範式(下)

10. extend: 繼承不是必須的

        js是基於原型的語言, 並沒有內置的繼承機制, 這一直讓很多深受傳統面向對象教育的同學們耿耿於懷。但繼承一定是必須的嗎? 它到底能夠給我們帶來什麼? 最純樸的回答是: 代碼重用。那麼, 我們首先來分析一下繼承作爲代碼重用手段的潛力。

        曾經有個概念叫做”多重繼承”, 它是繼承概念的超級賽亞人版, 很遺憾後來被診斷爲存在着先天缺陷, 以致於出現了一種對於繼承概念的解讀: 繼承就是”is a”關係, 一個派生對象”is a”很多基類, 必然會出現精神分裂, 所以多重繼承是不好的。

  1. class A{ public: void f(){ f in A } }
  2. class B{ public: void f(){ f in B } }
  3. class D: public A, B{}
複製代碼


        如果D類從A,B兩個基類繼承, 而A和B類中都實現了同一個函數f, 那麼D類中的f到底是A中的f還是B中的f, 抑或是A中的f+B中的f呢? 這一困境的出現實際上源於D的基類A和B是並列關係, 它們滿足交換律和結合律, 畢竟,在概念層面上我們可能難以認可兩個任意概念之間會出現從屬關係。但如果我們放鬆一些概念層面的要求, 更多的從操作層面考慮一下代碼重用問題, 可以簡單的認爲B在A的基礎上進行操作, 那麼就可以得到一個線性化的結果。也就是說, 放棄A和B之間的交換律只保留結合律, extends A, B 與 extends B,A 會是兩個不同的結果, 不再存在詮釋上的二義性。scala語言中的所謂trait(特性)機制實際上採用的就是這一策略。

        面向對象技術發明很久之後, 出現了所謂的面向方面編程(AOP), 它與OOP不同, 是代碼結構空間中的定位與修改技術。AOP的眼中只有類與方法, 不知道什麼叫做意義。AOP也提供了一種類似多重繼承的代碼重用手段, 那就是mixin。對象被看作是可以被打開,然後任意修改的Map, 一組成員變量與方法就被直接注射到對象體內, 直接改變了它的行爲。

        prototype.js庫引入了extend函數,

  1. Object.extend = function(destination, source) {
  2.   for (var property in source) {
  3.     destination[property] = source[property];
  4.   }
  5.   return destination;
  6. }
複製代碼


        就是Map之間的一個覆蓋運算, 但很管用, 在jQuery庫中也得到了延用. 這個操作類似於mixin, 在jQuery中是代碼重用的主要技術手段—沒有繼承也沒什麼大不了的。

        11. 名稱映射: 一切都是數據 

        代碼好不好, 循環判斷必須少。循環和判斷語句是程序的基本組成部分, 但是優良的代碼庫中卻往往找不到它們的蹤影, 因爲這些語句的交織會模糊系統的邏輯主線, 使我們的思想迷失在疲於奔命的代碼追蹤中。jQuery本身通過each, extend等函數已經極大減少了對循環語句的需求, 對於判斷語句, 則主要是通過映射表來處理. 例如, jQuery的val()函數需要針對不同標籤進行不同的處理, 因此定義一個以tagName爲key的函數映射表。

  1. valHooks: { option: {get:function(){}}}
複製代碼


        這樣在程序中就不需要到處寫

  1. if(elm.tagName == ’OPTION’){
  2.   return …;
  3. }else if(elm.tagName == ’TEXTAREA’){
  4.   return …;
  5. }
複製代碼


        可以統一處理

  1. (valHooks[elm.tagName.toLowerCase()] || defaultHandler).get(elm);
複製代碼


        映射表將函數作爲普通數據來管理, 在動態語言中有着廣泛的應用。特別是, 對象本身就是函數和變量的容器, 可以被看作是映射表。jQuery中大量使用的一個技巧就是利用名稱映射來動態生成代碼, 形成一種類似模板的機制。例如爲了實現myWidth和myHeight兩個非常類似的函數, 我們不需要。

  1. jQuery.fn.myWidth = function(){
  2.     return parseInt(this.style.width,10) + 10;
  3.   }

  4.   jQuery.fn.myHeight = function(){
  5.     return parseInt(this.style.height,10) + 10;
  6.   }
複製代碼


        而可以選擇動態生成

  1. jQuery.each(['Width','Height'],function(name){
  2.   jQuery.fn['my'+name] = function(){
  3.     return parseInt(this.style[name.toLowerCase()],10) + 10;
  4.   }
  5. });
複製代碼


        12. 插件機制:其實我很簡單   

        jQuery所謂的插件其實就是$.fn上增加的函數, 那這個fn是什麼東西?

  1. (function(window,undefined){
  2.   // 內部又有一個包裝
  3.   var jQuery = (function() {
  4.     var jQuery = function( selector, context ) {
  5.           return new jQuery.fn.init( selector, context, rootjQuery );
  6.       }
  7.      ….
  8.     // fn實際就是prototype的簡寫
  9.     jQuery.fn = jQuery.prototype = {
  10.         constructor: jQuery,
  11.         init: function( selector, context, rootjQuery ) {…  }
  12.     }

  13.     // 調用jQuery()就是相當於new init(), 而init的prototype就是jQuery的prototype
  14.     jQuery.fn.init.prototype = jQuery.fn;

  15.     // 這裏返回的jQuery對象只具備最基本的功能, 下面就是一系列的extend
  16.     return jQuery;
  17.   })();
  18.   …
  19.    // 將jQuery暴露爲全局對象
  20.   window.jQuery = window.$ = jQuery;
  21. })(window);
複製代碼


        顯然, $.fn其實就是jQuery.prototype的簡寫。

        無狀態的插件僅僅就是一個函數, 非常簡單。

  1. // 定義插件
  2. (function($){
  3.     $.fn.hoverClass = function(c) {
  4.         return this.hover(
  5.             function() { $(this).toggleClass(c); }
  6.         );
  7.     };
  8. })(jQuery);

  9. // 使用插件
  10. $(‘li’).hoverClass(‘hover’);
複製代碼


        對於比較複雜的插件開發, jQuery UI提供了一個widget工廠機制,

  1. $.widget(“ui.dialog”, {
  2.   options: {
  3.        autoOpen: true,…
  4.     },
  5.     _create: function(){ … },
  6.     _init: function() {
  7.        if ( this.options.autoOpen ) {
  8.            this.open();
  9.        }
  10.     },
  11.     _setOption: function(key, value){ … }
  12.     destroy: function(){ … }
  13. });
複製代碼


        調用 $(‘#dlg’).dialog(options)時, 實際執行的代碼基本如下所示:

  1. this.each(function() {
  2.       var instance = $.data( this, ”dialog” );
  3.       if ( instance ) {
  4.           instance.option( options || {} )._init();
  5.       } else {
  6.           $.data( this, ”dialog”, new $.ui.dialog( options, this ) );
  7.       }
  8.   }
複製代碼


        可以看出, 第一次調用$(‘#dlg’).dialog()函數時會創建窗口對象實例,並保存在data中, 此時會調用_create()和_init()函數, 而如果不是第一次調用, 則是在已經存在的對象實例上調用_init()方法. 多次調用$(‘#dlg’).dialog()並不會創建多個實例。

        13. browser sniffer vs. feature detection

        瀏覽器嗅探(browser sniffer)曾經是很流行的技術, 比如早期的jQuery中

  1. jQuery.browser = {
  2.       version:(userAgent.match(/.+(?:rv|it|ra|ie)[/: ]([d.]+)/) || [0,'0'])[1],
  3.       safari:/webkit/.test(userAgent),
  4.       opera:/opera/.test(userAgent),
  5.       msie:/msie/.test(userAgent) && !/opera/.test(userAgent),
  6.       mozilla:/mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
  7. };
複製代碼


        在具體代碼中可以針對不同的瀏覽器作出不同的處理

  1. if($.browser.msie) {
  2.     // do something
  3. } else if($.browser.opera) {
  4.     // …
  5. }
複製代碼


        但是隨着瀏覽器市場的競爭升級, 競爭對手之間的互相模仿和僞裝導致userAgent一片混亂, 加上Chrome的誕生, Safari的崛起, IE也開始加速向標準靠攏, sniffer已經起不到積極的作用。特性檢測(feature detection)作爲更細粒度, 更具體的檢測手段, 逐漸成爲處理瀏覽器兼容性的主流方式。

  1. jQuery.support = {
  2.       // IE strips leading whitespace when .innerHTML is used
  3.       leadingWhitespace: ( div.firstChild.nodeType === 3 ),
  4.       …
  5.   }
複製代碼


        只基於實際看見的,而不是曾經知道的, 這樣更容易做到兼容未來.

        14. Prototype vs. jQuery

        prototype.js是一個立意高遠的庫, 它的目標是提供一種新的使用體驗,參照Ruby從語言級別對javascript進行改造,並最終真的極大改變了js的面貌。$, extends, each, bind…這些耳熟能詳的概念都是prototype.js引入到js領域的。它肆無忌憚的在window全局名字空間中增加各種概念, 大有誰先佔坑誰有理, 捨我其誰的氣勢。而jQuery則扣扣索索, 抱着比較實用化的理念, 目標僅僅是write less, do more而已。

        不過等待激進的理想主義者的命運往往都是壯志未酬身先死。當prototype.js標誌性的bind函數等被吸收到ECMAScript標準中時, 便註定了它的沒落。到處修改原生對象的prototype, 這是prototype.js的獨門祕技, 也是它的死穴。特別是當它試圖模仿jQuery, 通過Element.extend(element)返回增強對象的時候, 算是徹底被jQuery給帶到溝裏去了。prototype.js與jQuery不同, 它總是直接修改原生對象的prototype, 而瀏覽器卻是充滿bug, 謊言, 歷史包袱並夾雜着商業陰謀的領域, 在原生對象層面解決問題註定是一場悲劇。性能問題, 名字衝突, 兼容性問題等等都是一個幫助庫的能力所無法解決的。Prototype.js的2.0版本據說要做大的變革, 不知是要與歷史決裂, 放棄兼容性, 還是繼續掙扎, 在夾縫中求生。

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