元编程之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来模拟发消息的过程。
以上代码大部分要做的事情是操作语言构件,而并非直接要处理业务逻辑。让代码去管理代码,好比你直接去管理各代码‘士兵’,不如设立一个代码‘将军’。
鄙人长期处于一线开发工作当中,对于文笔还略有欠缺,程序员交流更多的靠代码。如果觉得赞,请不要惜墨。