元編程之javascript


最近拜讀了下ruby元編程,對元編程編程觸動很深。本人一直從事前端開發工作,后來反思了一下javascript在元編程方面的能力。

相信大家對元編程多少有些了解,元編程簡單說就是“編寫代碼的代碼”,換個高雅解釋即是元編程是編寫在運行時操縱語言構件的代碼”。

反射用元編程解釋就是,一門語言擁有對自身的元編程能力就表現在反射。

Demo短小,所以沒多加過多的業務注釋,畢竟代碼很短,不是想告訴解決某一特殊的模式問題。只是想傳達一種編程范式,不必深究其中的業務邏輯。

下面的demo問題模型如下:

    “一個部門有很多例如電腦之類的硬件資源,這些硬件需要IT部門管理,

     每台電腦又由很多零件組成(顯卡,聲卡,CPU,鼠標,鍵盤......),

     這個電腦財產的管理軟件維護工作在你的手上,不過這個第一版不是你開發的,

     IT部門對你維護的此款管理硬件管理系統提出了一個新的需求,

      要求電腦組件價格超過100刀樂的要在輸出前加上‘*’  ”

下面這個DS類是核心的底層接口類。你也可以叫它model層。封裝了底層關於電腦的數據庫。總之這個是別人給你提供的。你要做的就是在這個封裝了數據信息的接口基礎上做上層開發。

function DS( computorId ){

this
.computorId = computorId; //不同的電腦有不同的配置。 this.data = {
mouseInfo :
"鼠標",
mousePrice :
"999",
keyboardinfo :
"鍵盤",
keyboardprice :
"888", lcdInfo : "驅動", lcdPrice : "888" /* *略去其他音響,聲卡,先卡等屬性 */ } } //DS 您可以當做數據庫系統,或者前端開發過程中你也可以當做后台返回的json數據。 DS.prototype = {
constructor : DS,
get_mouse_info :
function() { return this.data.mouseInfo; }, //取回鼠標信息
get_mouse_price : function() { return this.data.mousePrice; }, //取回鼠標價格
get_keyboard_info : function() { return this.data.keyboardinfo; },//取回鍵盤信息

get_keyboard_price : function() { return this.data.keyboardprice; }//取回鍵盤信息 /* *還有其他一些關於顯示器,音響,聲卡等等信息和價格 */ }

 

下面讓我們看看最直觀的方式

function Computer( id, data_source ){
    this.id = id;
    this.data_source = data_source;
}

//沒有重構之前的源代碼,這種寫法中規中矩,初級程序員都會直觀想到這種方式。作為大牛的你一定不會這么平庸的寫代碼
Computer.prototype = {
    mouse : function() {
        var info = this.data_source.get_mouse_info( this.id ),
            price = this.data_source.get_mouse_price( this.id ),
            result = "mouse:" + info + price;
        price >= 100? return "*" + result : return result;
    }
    keyboard : function() {
        var info = this.data_source.get_keyboard_info( this.id ),
            price = this.data_source.get_keyboard_price( this.id ),
            result = "mouse:" + info + price;
        price >= 100? return "*" + result : return result;
    }

    /*

   此處略去其他顯示器,音響之類的信息。

   */
}
var zs = new
Computer('2834750234', new DS(2342341244));
 
        

從上面的代碼我們能直觀分析出來,這種寫法重復性很強,很多重復工作。典型硬編碼。電腦有多少設備就需要手動的定義多少種取回設備信息價格的方法。可擴展性和維護性極差。

 

 

//第一次改進-動態派發。

function Computer( id, data_source ) {
    this.id = id; //電腦的Id信息
    this.data_source = data_source; //電腦的組件信息
}
Computer.prototype = {
    mouse : function() {
        return this.component( 'mouse' );
    },
    keyboard : function() {
        return this.component( 'keyboard' ); //動態的調用方法,只需傳遞方法的字符串參數。
    },

    //電腦其他顯示器,音響之類的省略,如果問題模型的組件越多,此處的重復也很多,不過較之第一種,已經優化了許多。
    component : function( name ) {
        var methodName = 'get_' + name,
            info = this.data_source[ methodName + '_info' ]( this.id ), //我們首先要取出關於電腦的一些組件info
            price = this.data_source[ methodName + '_price' ]( this.id ),//其次我們要取出關於電腦的一些price
            result = "mouse:" + info + price;
            reuslt = price >= 100? "*" + result : result; //判斷價格,高於100塊的加上'*'
        return reuslt;
    }
}

var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log(comObj.keyboard());

 

我們看到較之第一種已經優化了很多,重復性工作也變少了,動態派發的小技巧全在javascript對象方法的動態調用。雖說抽象出通用層,但是還是避免不了硬編碼,可維護性也較差。

 

 

 

進一步改進-動態創建方法,對於第二種方法,我們還是無法滿足,畢竟重復工作還是占了很大一部分。作為一個大牛的你一定不會寫出這種中級程序員的代碼,於是你像高級程序員做法發起挑戰

var Computer = function() {
var
AimClass = function( id, data_source ) { this.id = id; this.data_source = data_source; } //第三種寫法在於動態的創建方法,動態創建方法?你沒聽錯,就是代碼執行中創建方法,這要謝謝我們偉大的new Function(),以前對new Function着實無法理解,誰會這么定義一個function。 AimClass.define_component = function( name ) { var name = name, fnBody = 'var methodName = "get_' + name + '",' + 'info = this.data_source[ methodName + "_info" ]( this.id ),' + 'price = this.data_source[ methodName + "_price" ]( this.id ),' + 'result = "mouse:" + info + price;' + 'reuslt = price >= 100? "*" + result : result;' + 'return reuslt;' this.prototype[ name ] = new Function( 'name', fnBody );//此處是重點,動態創建方法(包括get_*_info,get_*_price);你再也不用手動的去定義那些討厭的方法了。
return this; } AimClass.define_component( 'mouse' ) //不過在此你還是要調用下你的類方法 .define_component( 'keyboard' );//只需你把方法參數寫進去就可以 //這里還有很多關於顯示器,音響之類的。從這里可以看出雖然第三種照第一,第二種優化了很多重復工作。可還是覺得還是需要調用很多這個創建實例方法的類方法 return AimClass; }() var obj = new DS( '15' ); var comObj = new Computer( 12, obj ); console.log(comObj.keyboard());

動態定義方法 相比 動態派發 有了進一步的優化,但是這種優化不是顛覆性的。這里你還需要手動的過程即“AimClass.define_component()”。

 

 

改進之最后一步(內省方式進一步優化代碼),終於我們來到終極改造,也是你作為大牛應該一展身手之處

var Computer = function( allMethod ){
    var aimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
    }
    AimClass.define_component = function( name ) {
        var name = name,
            fnBody = 'var methodName = "get_' + name + '",' +
                     'info = this.data_source[ methodName + "_info" ]( this.id ),' +
                     'price = this.data_source[ methodName + "_price" ]( this.id ),' +
                     'result = "mouse:" + info + price;' + 
                     'reuslt = price >= 100? "*" + result : result;' +
                     'return reuslt;'
        this.prototype[ name ] = new Function( 'name', fnBody );
    }
    for( var i in allMethod ) { //javascript對象內省機制。這種機制,解放了你的雙手。不需要重復工作。不需要重復調用動態創建實例方法的類方法
        var reg = /^get_(.+)_info$/, str = '';
        if ( allMethod.hasOwnProperty( i ) && ( ( typeof allMethod[ i ] ) == 'function' ) && ( i != 'constructor' ) ) {
            str = i.replace( reg, '$1' );
            AimClass.define_component( str );
        }
    }
    return AimClass;
}( DS.prototype ) //把DS的所有方法穿入當參數。
var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log( comObj );
console.log( comObj.keyboard() );

最后的代碼我們看到運用點正則的技巧還有對js對象(DS.prototype)內省機制。

 

我們看之間耍的小把戲已經讓一個冗余的代碼變得可維護性很強。

下面我們來看另外一種奇淫巧計。

由於javascript沒有methodmissing這樣迷人的內核方法。我自己模擬一個,當然這種模擬是有缺陷的。缺陷就是方法的調用是間接調用。而且模擬的方法不是內核方法。

var AimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
}
AimClass.prototype = {
    constructor: AimClass,

    methodmissing: function( name, args ) {
        var methodName = 'get_' + name;
        if ( !this.data_source[ methodName + '_info' ] ) {
            return '找不到此設備信息'
        }
        var info = this.data_source[ methodName + '_info' ]( this.id ),
price = this.data_source[ methodName + '_price' ]( this.id ),
result = "mouse:" + info + price; reuslt = price >= 100? "*" + result : result; console.log(result);
return this; }, methods: function() { var args = Array.prototype.slice.call( arguments ), methodName = args.shift() || undefined, methodArgs = args.length > 1? args : []; if ( typeof methodName == 'undefined' ) { return; } if( this[ methodName ] ) { return this[ methodName ].apply( this, methodArgs ); } else { return this[ 'methodmissing' ]( methodName, methodArgs ); } } } var b = new AimClass( 12, new DS( '15' ) ); b.methods('keyboard').methods('www');

 對象調用方法,例如obj.fn1();說白了過程不過就是向obj對象發送一條‘fn1’的消息,這里我們用b.methods來模擬發消息的過程。

 以上代碼大部分要做的事情是操作語言構件,而並非直接要處理業務邏輯。讓代碼去管理代碼,好比你直接去管理各代碼‘士兵’,不如設立一個代碼‘將軍’。

 

 

鄙人長期處於一線開發工作當中,對於文筆還略有欠缺,程序員交流更多的靠代碼。如果覺得贊,請不要惜墨。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM