Javascript面向?qū)ο髷U(kuò)展庫最近一直在用js做項目,遇到了許多需要應(yīng)用面向?qū)ο髞碓O(shè)計的功能,由于js對OOP的原生支持還不是很完善,所以就寫了一個面向?qū)ο蟮臄U(kuò)展庫用做底層支持,現(xiàn)在把它單獨(dú)整理出來,完善了一些功能,在這里分享一下。 lang.js庫提供了包和類的定義、類的繼承與混合(mixin)、函數(shù)重載等功能,基本可滿足大多數(shù)面向?qū)ο笤O(shè)計的需求。同時支持基于鏈?zhǔn)降亩x方式,讓庫在使用時更加規(guī)范和便捷。下面首先通過簡單的例子演示了lang.js的基本功能,之后給出了lang.js的源碼及注釋。
一.功能介紹 “l(fā)ang”作為框架的全局定義,其中包括了四個方法: lang.Package(string name) //用于定義包(默認(rèn)會暴露到全局) lang.Class(string name[, object config], object classBody) //用于定義類 lang.Object(string name | object body) //用于定義支持重載函數(shù)的普通對象 lang.Function(string name | object body) //用于定義重載函數(shù)
//1.基本的包和類的定義: //首先通過全局函數(shù)Package定義一個包,下面可通過鏈?zhǔn)秸{(diào)用向包中定義類、對象或函數(shù)。 Package('com.site'). Class('Base',{ Base: function(tagName){ this.element = document.createElement(tagName); }, add: function(child){ this.element.appendChild(child); }, show: function(){ alert('I am Base!'); } }). Class('Control', { Control: function(){ //do soming } }); alert(com.site); alert(com.site.Base); alert(com.site.Control); //2.普通對象定義 //一個不需初始化的類,可直接定義成普通對象 Package('com.site.util'). Object({ ModelA: { say: function(){ alert('ModelA: Hello!'); } }, ModelB: { print: function(){ alert('ModelB: ' + this.Class.Type.name); } } }); com.site.util.ModelA.say(); //3.類的繼承 //像很多面向?qū)ο笳Z言一樣,這里約定一個類只能擁有一個父類。但是可以通過mixin混合多個普通對象。 //在子類的構(gòu)造函數(shù)中可以通過this.base訪問父類的構(gòu)造函數(shù)。 //如子類定義了與父類相同函數(shù),父類函數(shù)將被重寫,這里同樣可以通過this.base調(diào)用父類被重寫的函數(shù)。 Package('com.site'). Class('Panel', {extend: com.site.Base, mixin:[com.site.util.ModelA, com.site.util.ModelB]}, { Panel: function(tagName){ //這里使用this.base調(diào)用父類構(gòu)造函數(shù) this.base(tagName); }, show: function(){ alert('I am Panel!'); this.base(); } }); var panel = new com.site.Panel("div"); panel.say(); panel.print(); alert(panel.element.outerHTML); //4.靜態(tài)成員的定義 /* 程序中可以將類的主體以函數(shù)的形式定義,將私有成員定義在函數(shù)內(nèi)部,將公共成員以返回值的形式暴露出來。這種設(shè)計模式在很多流行框架中都可以看到,雖然隱藏的成員只能是靜態(tài)的,不過這也足以滿足大部分的需求了。 公有的靜態(tài)成員可以通過Static標(biāo)簽定義,Static中可包含一個靜態(tài)構(gòu)造函數(shù),該函數(shù)只會在類第一次創(chuàng)建的時候執(zhí)行一次。 在運(yùn)行期間類的內(nèi)部可以通過this.Class.xxx的形式訪問自身的靜態(tài)成員,(this.Class就是所屬類的引用) */ Package('com.site'). Class('Button', {extend: com.site.Panel}, function(){ //在這里定義私有靜態(tài)成員 var tagName = 'BUTTON'; var onclick = function(){ alert('I am a Button!'); } return { //這里定義共有靜態(tài)成員 Static: { count: 1, //靜態(tài)構(gòu)造函數(shù) Button: function(){ this.createTime = new Date(); } }, Button: function(text){ this.base(tagName); this.element.innerHTML = text; this.element.onclick = onclick; //調(diào)用靜態(tài)變量 this.Class.count++; } }; }); var button = new com.site.Button("Button1"); document.body.appendChild(button.element); alert(com.site.Button.count); //5.重載函數(shù)的定義 /* lang.js中對函數(shù)重載提供了較完善的支持,在定義函數(shù)時可在函數(shù)名后面指定參數(shù)簽名,調(diào)用函數(shù)時程序根據(jù)傳入的參數(shù)自動匹配轉(zhuǎn)發(fā)。 其中支持的類型包括:var,string,number,boolean,array,date,object,function,element和自定義的類(var表示任意類型,element表示DOM元素。) 為了保證靈活性,重載支持通配符功能,遵循正則表達(dá)式的規(guī)則,可以通過標(biāo)記“+ * ? {?,?}”來處理不確定參數(shù)個數(shù)的情況。 重載函數(shù)在類的繼承過程中是有效的,簽名不同的函數(shù)不會被重寫,被重寫的父類重載函數(shù)同樣支持通過this.base調(diào)用。 以下代碼為重載在類中應(yīng)用的例子: */ Package('com.site'). Class('Student',{ Static: { //靜態(tài)函數(shù)重載 'create()': function(){ //這里的this就是類本身,所以可以直接new return new this('靜態(tài)', 1); }, 'create(string, number)': function(name, age){ return new this(name, age); }, 'search()': function(){ alert('search(*)'); }, 'search(string)': function(name){ alert('search('+name+')'); } }, //構(gòu)造函數(shù)重載 'Student()': function(){ //利用this.self調(diào)用自身構(gòu)造函數(shù)"Student(string, number)" this.self('未知', 0); }, 'Student(string, number)': function(name, age){ this.name = name; this.age = age; }, //普通成員函數(shù)重載 'print()': function(){ this.print(this.name ,this.age); }, 'print(string, number)': function(name, age){ alert('name:' + name + ',age:' + age); }, //支持自定義類的類型 'print(lib.ui.Panel)': function(panel){ this.print(panel, true); }, 'print(lib.ui.Panel, boolean)': function(panel, append){ if(append){ panel.element.innerHTML += this.name; }else{ panel.element.innerHTML = this.name; } } }). //繼承中對重載的支持 Class('MaleStudent',{extend: com.site.Student},{ Static: { //重寫父類靜態(tài)函數(shù) 'search(string)': function(name){ alert('MaleStudent: search('+name+')'); }, 'search(string, number)': function(name, age){ alert('MaleStudent: search('+name+','+age+')'); } }, 'MaleStudent': function(){ //調(diào)用父類的構(gòu)造函數(shù)"Student(string, number)" this.base('未知男同學(xué)', 0); }, 'MaleStudent(string, number)': function(name, age){ //調(diào)用父類的構(gòu)造函數(shù)"Student(string, number)" this.base(name, age); }, //重寫了父類的重載函數(shù) 'print()': function(){ alert('MaleStudent: name:' + this.name + ',age:' + this.age); }, 'print(string, number)': function(name, age){ //通過this.base調(diào)用父類函數(shù)"print(string, number)" this.base('<'+name+'>', '<' + age + '>'); }, //模糊匹配,這個函數(shù)可接受一個或多個字符串和零個或多個數(shù)字 'print(string+, number*)': function(){ var msg = []; for(var i=0; i<arguments.length; i++){ msg.push(arguments[i]); } alert('print(string+, number*):' + msg.join('+')); } }); //創(chuàng)建對象時會自動判斷調(diào)用的構(gòu)造函數(shù) var studentA = new com.site.Student(); studentA.print(); var studentB = new com.site.MaleStudent('王小虎', 19); studentB.print(); studentB.print('小虎', 19); //這里調(diào)用了模糊匹配的重載函數(shù) studentB.print('a','b','c'); studentB.print('x','y','z', 1, 2 ,3); var studentC = new com.site.MaleStudent(); studentC.print(); //調(diào)用靜態(tài)重載函數(shù) var student = com.site.Student.create(); student.print(); com.site.Student.search(); com.site.Student.search('某同學(xué)'); //共有的靜態(tài)函數(shù)會繼承到子類,所以MaleStudent也可以create var maleStudent = com.site.MaleStudent.create('小紅', 18); maleStudent.print(); com.site.MaleStudent.search('小明'); com.site.MaleStudent.search('小明',20); //6.匿名定義 //有些時候,我們不希望顯示的指定一個類或函數(shù)的名字,只希望把它保存到變量里面,那么可以通過匿名的方式進(jìn)行定義。 //1)使用lang.Class創(chuàng)建匿名類 var clazz = lang.Class({ //匿名類的名字統(tǒng)一為Anonymous,所以構(gòu)造函數(shù)的名字也要是Anonymous Anonymous: function(name){ this.name = name; }, show: function(){ alert(this.name); } }); alert(clazz); new clazz('匿名類').show(); //2)使用lang.Function創(chuàng)建匿名重載函數(shù) var fn = lang.Function({ '(default)': function(){ var a = [].slice.call(arguments, 0); alert(a); }, '(string)': function(a){ alert('(string): ' + a); }, '(string, number)': function(a, b){ alert('(string, number): ' + a + ':' + b); }, '(number)': function(a){ alert('(number): ' + a); } }); alert(fn); fn('message'); fn('message', 1); fn(1); fn(1,2,3,4,5); //3)使用lang.Object創(chuàng)建支持重載函數(shù)的普通對象 var obj = lang.Object({ name: 'jim', 'write()': function(){ this.write(this.name); }, 'write(string)': function(name){ alert(name); } }); obj.write(); obj.write('joe'); //7.綜合例子 Package("com.site"). Class('ClassA', { print: function(){ alert(this.toString()); } }). Class('ClassB', { print: function(){ alert(this.toString()); } }). Object({ fn1: function(){ alert(1); }, fn2: function(){ alert(2); }, 'debug(string)': function(msg){ alert(msg); }, 'debug(string, number)': function(msg, num){ alert(num + ': ' + msg); }, 'debug(com.site.ClassA, string)': function(c, method){ alert('debug ClassA'); c[method](); }, 'debug(com.site.ClassB, string)': function(c, method){ alert('debug ClassB'); c[method](); }, 'debug(element)': function(element){ alert('element:' + element.outerHTML); } }). Object('Web', { 'reload': function(){ alert('reload...'); }, 'reload(function)': function(fn){ fn(); } }). Package('com.site.util'). Function('myfn(string)', function(name){ alert(name); }). Function('myfn(number)', function(num){ alert(num*num); }). Class('ClassA', { ClassA: function(){ alert('ClassA!'); } }); var div = document.createElement("div"); var span = document.createElement("span"); div.appendChild(span); com.site.fn1(); com.site.fn2(); com.site.debug('info'); com.site.debug('info', 1); com.site.debug(new com.site.ClassA(), 'print'); com.site.debug(new com.site.ClassB(), 'print'); com.site.debug(div); com.site.Web.reload(); com.site.Web.reload(function(){alert('callback');}); com.site.util.myfn('Jim'); com.site.util.myfn(100); new com.site.util.ClassA(); //8.自描述信息 /* 自描述就是類或?qū)ο笤诙x完成后對自身的信息的描述,常常用于實現(xiàn)一些特殊的功能,庫中通過Type"保留字"存儲類或包的自描述信息。 下面為使用方法: */ var value = com.site.Student; alert('判斷是否一個類:' + (value.Type && value.Type.type == 'class')); alert('取完整類名:' + com.site.Student.Type.name); alert('取父類完整類名:' + com.site.MaleStudent.Type.baseClass.Type.name); alert('取短類名:' + com.site.Student.Type.shortName); alert('取一個類的包名:' + com.site.Student.Type.Package.Type.name); 二.源碼詳解 var lang = (function(){ /*********************************** Javascript面向?qū)ο髷U(kuò)展庫(lang.js v1.0) By: X!ao_f QQ: 120000512 Mail: xiao_f.mail#163.com ************************************/ var customToString = function(){ return '[' + this.Type.type + ' ' + this.Type.name + ']'; } //支持重載的方法定義 var createMethod = (function(){ //創(chuàng)建一個代理函數(shù) var createMethodProxy = function(context, name){ //當(dāng)調(diào)用重載的函數(shù)時,首先會執(zhí)行該函數(shù)分析傳入的參數(shù),進(jìn)行匹配和轉(zhuǎn)發(fā) var method = function(){ //在第一次調(diào)用時初始化,將映射信息緩存 if(!method.__initialized__){ initializeMethod(method); } //將參數(shù)類型拼接成函數(shù)簽名 var signature; if(arguments.length){ var list = []; for(var i=0; i<arguments.length; i++){ var typename; var argument = arguments[i]; if(argument === undefined || argument === null){ typename = 'object'; }else if(argument instanceof Array){ typename = 'array'; }else if(argument instanceof Date){ typename = 'date'; }else{ typename = typeof argument; if(typename == 'object'){ if('Class' in argument){ typename = argument.Class.Type.name; }else if('nodeType' in argument){ typename = 'element'; } } } list.push(typename); } signature = list.join(','); }else{ signature = ''; } //如果常規(guī)緩存中存在匹配的簽名,直接調(diào)用 if(method.__overloads__[signature]){ return method.__overloads__[signature].apply(this, arguments); }else{ //緩存中不存在時,嘗試?yán)谜齽t進(jìn)行模糊匹配 //首先判斷模糊匹配緩存中是否存在記錄,如存在直接調(diào)用 if(method.__overloadsCache__[signature]){ return method.__overloadsCache__[signature].apply(this, arguments); } //循環(huán)匹配 for(var i=0; i<method.__overloadsRegExp__.length; i++){ //如果匹配成功,將映射關(guān)系存入模糊匹配緩存,同時調(diào)用并返回 if(method.__overloadsRegExp__[i].regexp.test(signature)){ method.__overloadsCache__[signature] = method.__overloadsRegExp__[i].fn; return method.__overloadsRegExp__[i].fn.apply(this, arguments); } } //如果依然無法找到對應(yīng)的函數(shù),判斷是否存在默認(rèn)函數(shù) if(method.__overloads__['default']){ return method.__overloads__['default'].apply(this, arguments); }else if(method.__overloads__['']){ return method.__overloads__[''].apply(this, arguments); }else{ alert('Error: '+method.Type.name+'('+signature+') is undefined.'); } } }; //內(nèi)置對象 method.__context__ = context; method.__functions__ = {}; method.toString = customToString; //自描述信息 method.Type = { name: name, Method: method, type: 'method' }; return method; } //初始化 var initializeMethod = function(method){ //基礎(chǔ)簽名緩存 method.__overloads__ = {}; //模糊匹配正則緩存 method.__overloadsRegExp__ = []; //模糊匹配結(jié)果緩存 method.__overloadsCache__ = {}; //例舉所有定義的函數(shù) for(var signature in method.__functions__){ var fn = method.__functions__[signature]; var params = signature.substring(signature.indexOf('(') + 1, signature.length - 1); var pure = !/[\*\+\?\{]/.test(params); //如果不存在通配符直接保存到基礎(chǔ)簽名緩存 if(pure){ method.__overloads__[params] = fn; }else{ //生成模糊匹配正則 var regexp = '^' + params .replace(/([\w\.]+)(\{.*?\})?/g, '($1(,|$))$2') .replace(/\./g, '\\.') .replace(/((\()var(\())/g, '$2\\w+$3') .replace(/,\(/g, '(') + '$'; method.__overloadsRegExp__.push({ regexp: new RegExp(regexp), fn: fn }); } } method.__initialized__ = true; } //返回外部的定義函數(shù) return function(signature, fn, comp){ //如果傳入的為一個對象,視為定義匿名方法 if(typeof signature == 'object'){ var context = {}; var method; for(var key in signature){ method = createMethod.call(context, 'anonymous'+key, signature[key]); } return method; } signature = signature.replace(/\s+/g, ''); var index = signature.indexOf('('); var name = index > -1 ? signature.substring(0, signature.indexOf('(')) : signature; var context = this; var method = context[name]; //上下文中不存在函數(shù)定義,視為第一次定義 if(method === undefined){ context[name] = method = createMethodProxy(context, name); }else if(!method.Type || method.Type.type!='method'){ //上下文存在的函數(shù)是原生函數(shù),將這個函數(shù)作為默認(rèn)函數(shù)存入列表 var temp = method; context[name] = method = createMethodProxy(context, name); method.__functions__[name + '()'] = temp; }else{ //如果上下文不同,創(chuàng)建新的重載方法并將已經(jīng)存在的函數(shù)復(fù)制,這里主要解決類繼承中子類與父類沖突的問題 //如果上下文相同,直接將初始化標(biāo)記設(shè)為false,待下次調(diào)用時重新初始化 if(method.__context__ !== context){ var temp = method; context[name] = method = createMethodProxy(context); for(var sign in temp.__functions__){ method.__functions__[sign] = temp.__functions__[sign]; } }else{ method.__initialized__ = false; } } //將本次定義的函數(shù)添加到函數(shù)列表 //先入為主策略 if(comp){ if(fn.__functions__){ for(var key in fn.__functions__){ if(key in method.__functions__){ method.__functions__[key].__overridden__ = fn; }else{ method.__functions__[key] = fn; } } }else{ if(signature in method.__functions__){ method.__functions__[signature].__overridden__ = fn; }else{ method.__functions__[signature] = fn; } } }else{ //后入為主策略 if(fn.__functions__){ for(var key in fn.__functions__){ if(key in method.__functions__){ fn.__functions__[key].__overridden__ = method; } method.__functions__[key] = fn.__functions__[key]; } }else{ if(signature in method.__functions__){ fn.__overridden__ = method; } method.__functions__[signature] = fn; } } if(this.Type && this.Type.type == 'package'){ return this; }else{ return method; } }; })(); //類定義函數(shù) var createClass = (function(){ var slice = Array.prototype.slice; var emptyFn = function(){}; var createClass = function(name){ return function(){ this[name].apply(this, slice.call(arguments, 0)); }; } //用于調(diào)用被重寫函數(shù) var baseCaller = function(){ if(arguments.length){ var args = slice.call(arguments, 0); return baseCaller.caller.__overridden__.apply(this, args); }else{ return baseCaller.caller.__overridden__.call(this); } } //用于調(diào)用自身重載構(gòu)造函數(shù) var selfCaller = function(){ if(arguments.length){ var args = slice.call(arguments, 0); return selfCaller.caller.__self__.apply(this, args); }else{ return selfCaller.caller.__self__.call(this); } } var filter = {prototype:true, Type:true}; //快速淺拷貝 function clone(a){ var fn = function(){}; fn.prototype = a; return new fn; } //對象復(fù)制,替換存在的(后入為主) function replace(base, self){ for(var key in self){ if(!(key in filter)){ if(typeof self[key] == 'function'){ //如果子類函數(shù)包含重載簽名或父類函數(shù)已經(jīng)重載 if(key.indexOf('(') > -1 || (base[key] && base[key].__functions__)){ createMethod.call(base, key, self[key]); }else{ //常規(guī)函數(shù)定義 if(key in base){ //記錄重寫信息 self[key].__overridden__ = base[key]; } base[key] = self[key]; } }else{ base[key] = self[key]; } } } } //對象復(fù)制,只取補(bǔ)集(先入為主) function complement(self, base){ for(var key in base){ if(!(key in filter)){ if(typeof base[key] == 'function'){ if(key.indexOf('(') > -1 || (self[key] && self[key].__functions__)){ createMethod.call(self, key, base[key], true); }else{ if(key in self){ //記錄重寫信息 self[key].__overridden__ = base[key]; }else{ self[key] = base[key]; } } }else if(!(key in self)){ self[key] = base[key]; } } } } return function(){ //處理參數(shù) if(this.Type && this.Type.type == 'package'){ if(arguments.length == 2){ var name = arguments[0]; var body = arguments[1]; }else{ var name = arguments[0]; var config = arguments[1]; var body = arguments[2]; } }else{ if(arguments.length == 1){ var name = 'Anonymous'; var body = arguments[0]; }else{ var name = 'Anonymous'; var config = arguments[0]; var body = arguments[1]; } } //創(chuàng)建類的基礎(chǔ)函數(shù) var clazz = createClass(name); //獲取父類信息 var baseClass; if(config && config.extend){ baseClass = config.extend; } //如果傳入的主體為函數(shù),取其返回值 if(typeof body == 'function'){ body = body(clazz); } //處理靜態(tài)成員 if(body.Static){ complement(clazz, body.Static); delete body.Static; body = body.Public||body; }else{ body = body.Public||body; } //處理繼承 if(baseClass){ //通過快速淺拷貝復(fù)制父類成員 clazz.prototype = clone(baseClass.prototype); //繼承靜態(tài)成員 complement(clazz, baseClass); //繼承類成員 complement(clazz.prototype, body); }else{ //不存在繼承 clazz.prototype = {}; complement(clazz.prototype, body); } //處理混合 if(config && config.mixin){ var mixin = config.mixin; if(mixin instanceof Array){ for(var i=0; i<mixin.length; i++){ replace(clazz.prototype, mixin[i]); } }else{ replace(clazz.prototype, mixin); } } //添加內(nèi)置函數(shù) clazz.prototype.base = baseCaller; clazz.prototype.self = selfCaller; clazz.prototype.constructor = clazz; clazz.prototype.toString = customToString; clazz.toString = customToString; clazz.prototype.Class = clazz; if(clazz.prototype[name]){ var constructor = clazz.prototype[name]; if(constructor.__functions__){ for(var key in constructor.__functions__){ //存在重載時,添加自身引用,用于通過this.self調(diào)用重載構(gòu)造函數(shù) constructor.__functions__[key].__self__ = constructor; //存在繼承時,將父類的構(gòu)造函數(shù)作為被重寫的函數(shù),配置給當(dāng)前類的構(gòu)造函數(shù) //用于通過base調(diào)用父類構(gòu)造函數(shù) if(baseClass){ constructor.__functions__[key].__overridden__ = baseClass.prototype[baseClass.Type.shortName]; } } }else if(baseClass){ clazz.prototype[name].__overridden__ = baseClass.prototype[baseClass.Type.shortName]; } }else{ clazz.prototype[name] = emptyFn; } //類型自描述信息 //如果當(dāng)前上下文是一個包,將類添加到包中 if(this.Type && this.Type.type == 'package'){ clazz.Type = { type:'class', name: this.Type.name+'.'+name, shortName: name, Package: this, Class: clazz, baseClass: baseClass } clazz.prototype.Type = { type: 'object', name: this.Type.name+'.'+name } //將類添加到包 this[name] = clazz; //調(diào)用靜態(tài)構(gòu)造函數(shù) if(name in clazz){ clazz[name].call(clazz); } //返回this用于鏈?zhǔn)秸{(diào)用 return this; }else{ //上下文不是包則直接返回 clazz.Type = { type:'class', name: name, shortName: name, Class: clazz, baseClass: baseClass } clazz.prototype.Type = { type: 'object', name: name, baseClass: baseClass } if(name in clazz){ clazz[name].call(clazz); } return clazz; } }; })(); //用于創(chuàng)建支持重載的普通對象 var createObject = function(objects, config){ var target; if(this.Type && this.Type.type == 'package'){ target = this; }else{ target = {}; } if(typeof objects == 'string'){ target = this[objects] = {}; objects = config; }else if(typeof objects == 'function'){ objects = objects(); } for(var key in objects){ if(typeof objects[key] == 'function' && (key.indexOf('(') > -1 || typeof target[key] == 'function')){ createMethod.call(target, key, objects[key]); }else{ target[key] = objects[key]; } } if(this.Type && this.Type.type == 'package'){ return this; }else{ return target; } }; //用于創(chuàng)建包 var createPackage = (function(){ var root = this; return function(package){ var name = []; var path = package.split('.'); var parent = root; for(var i=0; i<path.length; i++){ name.push(path[i]); if(parent[path[i]]){ parent = parent[path[i]]; }else{ var pack = { Class: createClass, Object: createObject, Function: createMethod, Package: createPackage, toString: customToString }; pack.Type = { type: 'package', Package: pack, name: name.join('.') } parent = parent[path[i]] = pack; } } return parent; } })(); //默認(rèn)將Package暴露 window.Package = createPackage; return { Package: createPackage, Class: createClass, Function: createMethod, Object: createObject }; })(); 結(jié)束語: 到這里,lang.js的應(yīng)用和原理就介紹完畢了,該庫在主流瀏覽器中均已測試通過, 如果想使用lang.js,可以在這里免費(fèi)下載,如發(fā)現(xiàn)什么問題,或有好的建議可以反饋給我。
標(biāo)簽: Javascript, 框架設(shè)計 |
|