javascript面向?qū)ο蟀b類Class封裝類庫剖析
更新時間:2013年01月24日 18:06:56 作者:
一個從來沒有接觸過javascript的技術(shù)人員,幾小時內(nèi)就可以寫出一個簡單有用的程序代碼;想寫出高性能的代碼,同樣需要具備一個高級程序員的基本素養(yǎng),javascript也是如此
javascript是個入門門檻很低的語言,甚至一個從來沒有接觸過javascript的技術(shù)人員,幾小時內(nèi)就可以寫出一個簡單有用的程序代碼。
但是如果因此你就下結(jié)論:javascript是門簡單的語言。那你就大錯特錯了。想寫出高性能的代碼,同樣需要具備一個高級程序員的基本素養(yǎng)。
一個java或者c++程序員,不一定能寫出高性能的javascript代碼,但更容易寫出高性能的javascript代碼。
javascript的簡單是基于它“胸襟廣闊”的包容性。它聲明時,不需要指定類型,甚至可以任意的轉(zhuǎn)換類型。它面向?qū)ο螅瑓s沒有類(Class)的限制。它是一門崇尚自由又非常嚴(yán)謹(jǐn)?shù)恼Z言,如果你是一個自由主義者,那么,擁抱javascript吧!
面向?qū)ο缶幊?OOP)是一種流行的編程方法。但javascript的OOP,較之JAVA、c++有很大的同,主要體現(xiàn)它的繼承方式不同。javascript是基于原型PROTOTYPE繼承的。所有對象都是基于原型鏈,最終追述到Object對象。
這里不想討論過多的關(guān)于javascript的繼承方式和其它語言的繼承方式的不同之處。主要討論如何封裝javascript的Class,以便更好的管理和維護基礎(chǔ)代碼,減少重復(fù)代碼,以及更好的模塊化編程。
下面是幾個github上找到的比較好的Class封裝類庫:
一、MY-CLASS
項目地址:https://github.com/jiem/my-class
先看基本用法:
a、新建一個類
(function(){
//新建類
varPerson=my.Class({
//添加靜態(tài)方法
STATIC:{
AGE_OF_MAJORITY:18
},
//構(gòu)造函數(shù)
constructor:function(name,age){
this.name=name;
this.age=age;
},
//實例方法
sayHello:function(){
console.log('Hellofrom'+this.name+'!');
},
//實例方法
drinkAlcohol:function(){
this.age<Person.AGE_OF_MAJORITY?
console.log('Tooyoung!Drinkmilkinstead!'):
console.log('Whiskeyorbeer?');
}
});
//暴露給命名空間
myLib.Person=Person;
})();
varjohn=newmyLib.Person('John',16);
john.sayHello();//log"HellofromJohn!"
john.drinkAlcohol();//log"Tooyoung!Drinkmilkinstead!"
b、繼承一個類
(function(){
//Dreamer繼承Person
varDreamer=my.Class(Person,{
//構(gòu)造方法
constructor:function(name,age,dream){
Dreamer.Super.call(this,name,age);
this.dream=dream;
},
//實例方法
sayHello:function(){
superSayHello.call(this);
console.log('Idreamof'+this.dream+'!');
},
//實例方法
wakeUp:function(){
console.log('Wakeup!');
}
});
//Super訪問父類
varsuperSayHello=Dreamer.Super.prototype.sayHello;
//暴露給全局命名空間
myLib.Dreamer=Dreamer;
})();
varsylvester=newmyLib.Dreamer('Sylvester',30,'eatingTweety');
sylvester.sayHello();//log"HellofromSylvester!IdreamofeatingTweety!"
sylvester.wakeUp();//log"Wakeup!"
c、給類添加新方法
//給myLib.Dreamer添加新方法
my.extendClass(myLib.Dreamer,{
//添加靜態(tài)方法
STATIC:{
s_dongSomeThing:function(){
console.log("dosomething!");
}
},
//添加實例方法
touchTheSky:function(){
console.log('Touchingthesky');
},
//添加實例方法
reachTheStars:function(){
console.log('Sheissopretty!');
}
});
d、實現(xiàn)一個類的方法
//聲明一個新類
myLib.ImaginaryTraveler=my.Class({
travel:function(){console.log('Travelingonacarpet!');},
crossOceans:function(){console.log('SayinghitoMobyDick!');}
});
(function(){
//Dreamer繼承Person實現(xiàn)ImaginaryTraveler的方法
varDreamer=my.Class(Person,ImaginaryTraveler,{
//構(gòu)造方法
constructor:function(name,age,dream){
Dreamer.Super.call(this,name,age);
this.dream=dream;
}
//...
});
//暴露給全局命名空間
myLib.Dreamer=Dreamer;
})();
varaladdin=newDreamer('Aladdin');
aladdininstanceofPerson;//true
aladdininstanceofImaginaryTraveler;//false
aladdin.travel();
aladdin.wakeUp();
aladdin.sayHello();
如果怕忘記new操作符
varPerson=my.Class({
//youcannowcalltheconstructorwithorwithoutnew
constructor:function(name,city){
if(!(thisinstanceofPerson))
returnnewPerson(name,city);
this.name=name;
this.city=citye;
}
});
下面看一下my.class的源代碼解析:
my.Class實現(xiàn)思路基本是這樣的,如果只有一個參數(shù),那么聲明的是一個基礎(chǔ)類,這個參數(shù)是用來聲明新類的方法和屬以及構(gòu)造函數(shù)。它不是繼承而來,但它可以被繼承。
繼承的思路,就是如果有兩個參數(shù),第一個參數(shù)做為父類被繼承,第二參數(shù)用來聲明新類的方法和屬性以及構(gòu)造函數(shù),它同樣可以被繼承。
如果有三個以上參數(shù)那么,除出第一個參數(shù)做為繼承的父類,最后一個參數(shù)用聲明新類的方法和屬性以及構(gòu)造函數(shù)。中間的參數(shù)是用類來擴展新類的方法。當(dāng)然也可以通過my.extendClass擴展新方法。
同時,類庫為commonJS和瀏覽環(huán)境都提供了支持!
/*globalsdefine:true,window:true,module:true*/
(function(){
//Namespaceobject
varmy={};
//保證AMD分模塊可用
if(typeofdefine!=='undefined')
define([],function(){
returnmy;
});
elseif(typeofwindow!=='undefined')
//保證客戶端可用
window.my=my;
else
//保證后臺可用
module.exports=my;
//============================================================================
//@methodmy.Class
//@paramsbody:Object
//@paramsSuperClass:function,ImplementClasses:function...,body:Object
//@returnfunction
my.Class=function(){
varlen=arguments.length;
varbody=arguments[len-1];//最后一個參數(shù)是指定本身的方法
varSuperClass=len>1?arguments[0]:null;//第一個參數(shù)是指繼承的方法,實例和靜態(tài)部分均繼承
varhasImplementClasses=len>2;//如果有第三個參數(shù),那么第二個就是implementClass,這里其實只繼承實例對象
varClass,SuperClassEmpty;
//保證構(gòu)造方法
if(body.constructor===Object){
Class=function(){};
}else{
Class=body.constructor;
//保證后面不覆蓋constructor
deletebody.constructor;
}
//處理superClass部分
if(SuperClass){
//中間件實現(xiàn)實例屬性的繼承
SuperClassEmpty=function(){};
SuperClassEmpty.prototype=SuperClass.prototype;
Class.prototype=newSuperClassEmpty();//原型繼承,解除引用
Class.prototype.constructor=Class;//保證constructor
Class.Super=SuperClass;//父對象訪問接口
//靜態(tài)方法繼承,重載superClass方法
extend(Class,SuperClass,false);
}
//處理ImplementClass部分,其實只繼承實例屬性部分,除SuperClass#arguments[0]#和body#arguments[length-1]#
if(hasImplementClasses)
for(vari=1;i<len-1;i++)
//implement是繼承的實例屬性部分,重載父對象implementClass方法
extend(Class.prototype,arguments[i].prototype,false);
//處理本身聲明body部分,靜態(tài)要STATIC指定,實例部分要刪除STATIC部分
extendClass(Class,body);
returnClass;
};
//============================================================================
//@methodmy.extendClass
//@paramsClass:function,extension:Object,?override:boolean=true
varextendClass=my.extendClass=function(Class,extension,override){
//靜態(tài)部分繼承靜態(tài)部分
if(extension.STATIC){
extend(Class,extension.STATIC,override);
//保證實例部分不繼承靜態(tài)方法
deleteextension.STATIC;
}
//實例屬性繼繼承實例部
extend(Class.prototype,extension,override);
};
//============================================================================
varextend=function(obj,extension,override){
varprop;
//其實這里的flase是表明,覆蓋父對象的方法
if(override===false){
for(propinextension)
if(!(propinobj))
obj[prop]=extension[prop];
}else{
//這里其實不覆蓋父對象的方法,包括toString
for(propinextension)
obj[prop]=extension[prop];
if(extension.toString!==Object.prototype.toString)
obj.toString=extension.toString;
}
};
})();
二、KLASS
項目地址:https://github.com/ded/klass
先看使用方法:
a、新建一個類
//聲明一個類
varPerson=klass(function(name){
this.name=name
})
.statics({//靜態(tài)方法
head:':)',
feet:'_|_'
})
.methods({//實例方法
walk:function(){}
})
b、繼承一個類
//SuperHuman繼承Person
varSuperHuman=Person.extend(function(name){
//自動調(diào)用父類的構(gòu)造方法
})
.methods({
walk:function(){
//顯式聲明調(diào)用父類的walk方法
this.supr()
this.fly()
},
fly:function(){}
})
newSuperHuman('Zelda').walk()
c、字面量方式聲明一個類
varFoo=klass({
foo:0,
initialize:function(){
this.foo=1
},
getFoo:function(){
returnthis.foo
},
setFoo:function(x){
this.foo=x
returnthis.getFoo()
}
})
d、實現(xiàn)一個類的方法
因為有時候你可能希望覆寫或者混合一個實例方法,可以這樣:
//可以傳遞一個字面量去繼承
varAlien=SuperHuman.extend({
beam:function(){
this.supr()
//beamintospace
}
})
varSpazoid=newAlien('Zoopo')
if(beamIsDown){
//覆寫beam方法
Spazoid.implement({
beam:function(){
this.supr()
//fallbacktojets
this.jets()
}
})
}
下面看一下klass源代碼解析:
klass的基本設(shè)計思路很明確,極力的模仿其它語言的繼承方式。比如:子類構(gòu)造方法調(diào)用父類的構(gòu)造方法,還可以顯式的聲明調(diào)用父類的方法。
這種判斷都是基于正則匹配:fnTest=/xyz/.test(function(){xyz;})?/\bsupr\b/:/.*/;關(guān)鍵字"super"
如果顯示的聲明了要調(diào)用父類的方法,那么聲明方法的時候,就包裝成一個內(nèi)部調(diào)用父類方法且返回相同值的函數(shù),給當(dāng)前類的方法。
另一方面,構(gòu)造方法,也是比較靈活的。如果顯示的聲明了initialize,那么這就是構(gòu)造方法。否則如果參數(shù)是個function那么它就做為構(gòu)造方法,否則就用父類的構(gòu)造方法。
通過statics方式添加靜態(tài)方法,通過實例的implements和靜態(tài)方法methods添加實例方法。
通過父類的extend實現(xiàn)繼承。
同時,類庫為commonJS和瀏覽環(huán)境都提供了支持!
/**
*Klass.js-copyright@dedfat
*version1.0
*https://github.com/ded/klass
*Followoursoftwarehttp://twitter.com/dedfat:)
*MITLicense
*/
!function(context,f){
//fnTest用來驗證是否可能通過正則找出調(diào)用super父類方法的方法
varfnTest=/xyz/.test(function(){xyz;})?/\bsupr\b/:/.*/,
noop=function(){},
proto='prototype',
isFn=function(o){
returntypeofo===f;
};
//基礎(chǔ)類
functionklass(o){
returnextend.call(typeofo==f?o:noop,o,1);
}
//包裝成一個借用super同名方法的函數(shù)
functionwrap(k,fn,supr){
returnfunction(){
//緩存原this.super
vartmp=this.supr;
//暫把this.super改造成借用super的同名方法above
//供o里顯式的聲明(fnTest.text(fn)==true)要借用super的同名方法使用
this.supr=supr[proto][k];
//借用執(zhí)行并保存返回值
varret=fn.apply(this,arguments);
//恢復(fù)原this.super
this.supr=tmp;
//返回返回值,保證wrap后的返回值跟原來一致
returnret;
};
}
//如果o和super有同名方法,且o顯式聲明借用super的同名方法,就wrap成一個待執(zhí)行函數(shù)供使用
//如果沒有顯式的聲明借用super的同名方法,或者是o獨有的方法,或者不是方法就直接用
functionprocess(what,o,supr){
for(varkino){
//如果是非繼承方法,按方法注釋規(guī)則執(zhí)行,最終都放進what
if(o.hasOwnProperty(k)){
what[k]=typeofo[k]==f
&&typeofsupr[proto][k]==f
&&fnTest.test(o[k])
?wrap(k,o[k],supr):o[k];
}
}
}
//繼承方法的實現(xiàn),fromSub是用來控制是否繼承而來,上面的klass里面fromSub是1,表明非繼承而來,構(gòu)造函數(shù)不借用super執(zhí)行
functionextend(o,fromSub){
//noop做為媒介類實現(xiàn)原型繼承的解除引用
noop[proto]=this[proto];
varsupr=this,
prototype=newnoop(),//創(chuàng)建實例對象供原型繼承使用,解除引用
isFunction=typeofo==f,
_constructor=isFunction?o:this,//如果o是一個構(gòu)造方法就用,否則由this來決定構(gòu)造函數(shù)
_methods=isFunction?{}:o,//如果o是一個{...}應(yīng)該用methods放到fn原型里,如果里面有initialize就是構(gòu)造函數(shù),如果o是函數(shù)就由上面_constructor決定o是構(gòu)造函數(shù)
fn=function(){//因為kclass借助了kclass,所以最終實際上返回的就是fn,fn其實就新類的構(gòu)造函數(shù)
//1如果o是{...}就會被methods直接過濾并添加到fn的原型里,如果o里面有initialize,那么fn的原型里就有initialize,那么它就是構(gòu)造方法
//2如果o是function,methods什么也添加不到fn的原型里,但是_constructor會接受o當(dāng)構(gòu)造函數(shù)
//3如果o是{....},同時里面也沒有initialize,那么就是this當(dāng)構(gòu)造函數(shù),如果在klass里由call決定,顯然構(gòu)造函數(shù)是noop,如果在非基礎(chǔ)類里,構(gòu)造函數(shù)就是父類的構(gòu)造函數(shù)
//由于o不是函數(shù)不會自動調(diào)用父類的構(gòu)造函數(shù),只是把父類的構(gòu)造函數(shù)當(dāng)做當(dāng)前類的構(gòu)造函數(shù)----這都是由于this的指向決定的
console.log(this);
if(this.initialize){
this.initialize.apply(this,arguments);
}else{
//調(diào)用父類構(gòu)造方法
//如上面3,o不是函數(shù),不會調(diào)用父類的構(gòu)造方法
//基礎(chǔ)類無父類,不會調(diào)用父類構(gòu)造方法
fromSub||isFn(o)&&supr.apply(this,arguments);
//調(diào)用本類構(gòu)造方法
//參考上面2,3要么是noop要么是o
console.log(_constructor==noop);
_constructor.apply(this,arguments);
}
};
//構(gòu)造原型方法的接口
fn.methods=function(o){
process(prototype,o,supr);
fn[proto]=prototype;
returnthis;
};
//執(zhí)行實現(xiàn)新類原型,保證新類的constructor
fn.methods.call(fn,_methods).prototype.constructor=fn;
//保證新類可以被繼承
fn.extend=arguments.callee;
//添加實例方法或者靜態(tài)方法,statics:靜態(tài)方法,implement實例方法
fn[proto].implement=fn.statics=function(o,optFn){
//保證o是一個object對象,如果o是一個字符串,那么就是添一個方法的情況,如果o是一個object對象說明是批量添加的
//因為要從o里面拷貝
o=typeofo=='string'?(function(){
varobj={};
obj[o]=optFn;
returnobj;
}()):o;
//添加實例方法或者靜態(tài)方法,statics:靜態(tài)方法,implement實例方法
process(this,o,supr);
returnthis;
};
returnfn;
}
//后臺用,nodejs
if(typeofmodule!=='undefined'&&module.exports){
module.exports=klass;
}else{
varold=context.klass;
//防沖突
klass.noConflict=function(){
context.klass=old;
returnthis;
};
//前臺瀏覽器用
//window.kclass=kclass;
context.klass=klass;
}
}(this,'function');
三、還有一種簡單實現(xiàn)
實現(xiàn)思路很簡單,就是利用ECMAScript5原型式繼承Object.create方法,封裝成一個方法,如果不支持ECMAScript5的環(huán)境,就平移退化到
functionF(){};
F.prototype=superCtor.prototype;
ctor.prototype=newF();
ctor.prototype.constructor=ctor;
同樣的,除最后一個參數(shù)是當(dāng)前類的方法聲明,其它參數(shù)均做為繼承父類,需要循環(huán)繼承,但當(dāng)這里處理的相對比較簡單,沒涉及到覆蓋。你可以自己動手添加。
varClass=(function(){
/**
*Inheritsfunction.(node.js)
*
*@paramctorsubclass'sconstructor.
*@paramsuperctorsuperclass'sconstructor.
*/
varinherits=function(ctor,superCtor){
//顯式的指定父類
ctor.super_=superCtor;
//ECMAScript5原型式繼承并解除引用
if(Object.create){
ctor.prototype=Object.create(superCtor.prototype,{
constructor:{
value:ctor,
enumerable:false,
writable:true,
configurable:true
}
});
}else{
//無Object.create方法的平穩(wěn)退化
functionF(){};
F.prototype=superCtor.prototype;
ctor.prototype=newF();
ctor.prototype.constructor=ctor;
}
};
/**
*Classfunction.
*/
returnfunction(){
//最后一個參數(shù)是新類方法、屬性和構(gòu)造函數(shù)聲明
varsubClazz=arguments[arguments.length-1]||function(){};
//initialize是構(gòu)造函數(shù),否構(gòu)造函數(shù)就是一個空函數(shù)
varfn=subClazz.initialize==null?function(){}:subClazz.initialize;
//繼承除最一個參數(shù)以的類,多繼承,也可以用作擴展方法
for(varindex=0;index<arguments.length-1;index++){
inherits(fn,arguments[index]);
}
//實現(xiàn)新類的方法
for(varpropinsubClazz){
if(prop=="initialize"){
continue;
}
fn.prototype[prop]=subClazz[prop];
}
returnfn;
}
})();
看下面實例:
/**
*ThedefinitionofCatClass.
*/
varCat=Class({
/**
*Constructor.
*
*@paramnameCat'sname
*/
initialize:function(name){
this.name=name;
},
/**
*Eatfunction.
*/
eat:function(){
alert(this.name+"iseatingfish.");
}
});
/**
*ThedefinitionofBlackCatClass.
*/
varBlackCat=Class(Cat,{
/**
*Constructor.
*
*@paramnameCat'sname.
*@paramageCat'sage.
*/
initialize:function(name,age){
//calltheconstructorofsuperclass.
BlackCat.super_.call(this,name);
this.age=age;
},
/**
*Eatfunction.
*/
eat:function(){
alert(this.name+"("+this.age+")iseatingdog.");
}
});
/**
*ThedefinitionofBlackFatCatClass.
*/
varBlackFatCat=Class(BlackCat,{
/**
*Constructor.
*
*@paramnameCat'sname.
*@paramageCat'sage.
*@paramweightCat'sweight.
*/
initialize:function(name,age,weight){
//calltheconstructorofsuperclass.
BlackFatCat.super_.call(this,name,age);
this.weight=weight;
},
/**
*Eatfunction.
*/
eat:function(){
alert(this.name+"("+this.age+")iseatingdog.Myweight:"+this.weight);
}
});
/**
*ThedefinitionofDogClass.
*/
varDog=Class({});
varcat=newBlackFatCat("John",24,"100kg");
cat.eat();
//true
alert(catinstanceofCat);
//true
alert(catinstanceofBlackCat);
//true
alert(catinstanceofBlackFatCat);
//true
alert(cat.constructor===BlackFatCat);
//false
alert(catinstanceofDog);
四、mootools類庫的Class
源碼解析可以看這里:http://www.cnblogs.com/hmking/archive/2011/09/30/2196504.html
看具體用法:
a、新建一個類
varCat=newClass({
initialize:function(name){
this.name=name;
}
});
varmyCat=newCat('Micia');
alert(myCat.name);//alerts'Micia'
varCow=newClass({
initialize:function(){
alert('moooo');
}
});
b、繼承的實現(xiàn)
varAnimal=newClass({
initialize:function(age){
this.age=age;
}
});
varCat=newClass({
Extends:Animal,
initialize:function(name,age){
this.parent(age);//callsinitalizemethodofAnimalclass
this.name=name;
}
});
varmyCat=newCat('Micia',20);
alert(myCat.name);//alerts'Micia'.
alert(myCat.age);//alerts20.
c、擴充類的實現(xiàn)
varAnimal=newClass({
initialize:function(age){
this.age=age;
}
});
varCat=newClass({
Implements:Animal,
setName:function(name){
this.name=name
}
});
varmyAnimal=newCat(20);
myAnimal.setName('Micia');
alert(myAnimal.name);//alerts'Micia'.
五、悟透javascript:語法甘露
先看用法實例
a、創(chuàng)建類
//創(chuàng)建類Person
varPerson=Class(object,{
Create:function(name,age){
this.name=name;
this.age=age;
},
SayHello:function(){
alert("Hello,I'm"+this.name+","+this.age+"yearsold.");
}
});
varBillGates=New(Person,["BillGates",53]);
BillGates.SayHello();
b、繼承類
//Employee繼承Person
varEmployee=Class(Person,{
Create:function(name,age,salary){
Person.Create.call(this,name,age);
//調(diào)用基類的構(gòu)造函數(shù)
this.salary=salary;
},
ShowMeTheMoney:function(){
alert(this.name+"$"+this.salary);
}
});
varSteveJobs=New(Employee,["SteveJobs",53,1234]);
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
下面是源碼分析:顯然,多了一個New方法,創(chuàng)建類和新建類的實例都被巧妙的封裝了。形成了一個有意義的整體!還有一點不同的地方,所有的類都基于字面量,而不是基于函數(shù)。代碼很簡短,但其中原理卻很豐富也很巧妙,可以細細品味一番!
//創(chuàng)建類的函數(shù),用于聲明類及繼承關(guān)系
functionClass(aBaseClass,aClassDefine){
//創(chuàng)建類的臨時函數(shù)殼
functionclass_(){
this.Type=aBaseClass;
//我們給每一個類約定一個Type屬性,引用其繼承的類
for(varmemberinaClassDefine)
this[member]=aClassDefine[member];
//復(fù)制類的全部定義到當(dāng)前創(chuàng)建的類
};
class_.prototype=aBaseClass;
returnnewclass_();
};
//創(chuàng)建對象的函數(shù),用于任意類的對象創(chuàng)建
functionNew(aClass,aParams){
//創(chuàng)建對象的臨時函數(shù)殼
functionnew_(){
this.Type=aClass;
//我們也給每一個對象約定一個Type屬性,據(jù)此可以訪問到對象所屬的類
if(aClass.Create)
aClass.Create.apply(this,aParams);
//我們約定所有類的構(gòu)造函數(shù)都叫Create,這和DELPHI比較相似
};
new_.prototype=aClass;
returnnewnew_();
};
由于寫的比較籠統(tǒng),可能有很多地方?jīng)]有解析到,也可能有不準(zhǔn)確的地方,還望指正。
看完上面幾種解析,相信息自己也可以寫出一個自己的封裝類庫出來,至于,怎么實現(xiàn)看個人喜好了。但基本的思都是一樣的基于原型的繼承方式和循環(huán)拷貝新方法。
原文來自:穆乙 http://www.cnblogs.com/pigtail/
但是如果因此你就下結(jié)論:javascript是門簡單的語言。那你就大錯特錯了。想寫出高性能的代碼,同樣需要具備一個高級程序員的基本素養(yǎng)。
一個java或者c++程序員,不一定能寫出高性能的javascript代碼,但更容易寫出高性能的javascript代碼。
javascript的簡單是基于它“胸襟廣闊”的包容性。它聲明時,不需要指定類型,甚至可以任意的轉(zhuǎn)換類型。它面向?qū)ο螅瑓s沒有類(Class)的限制。它是一門崇尚自由又非常嚴(yán)謹(jǐn)?shù)恼Z言,如果你是一個自由主義者,那么,擁抱javascript吧!
面向?qū)ο缶幊?OOP)是一種流行的編程方法。但javascript的OOP,較之JAVA、c++有很大的同,主要體現(xiàn)它的繼承方式不同。javascript是基于原型PROTOTYPE繼承的。所有對象都是基于原型鏈,最終追述到Object對象。
這里不想討論過多的關(guān)于javascript的繼承方式和其它語言的繼承方式的不同之處。主要討論如何封裝javascript的Class,以便更好的管理和維護基礎(chǔ)代碼,減少重復(fù)代碼,以及更好的模塊化編程。
下面是幾個github上找到的比較好的Class封裝類庫:
一、MY-CLASS
項目地址:https://github.com/jiem/my-class
先看基本用法:
a、新建一個類
復(fù)制代碼 代碼如下:
(function(){
//新建類
varPerson=my.Class({
//添加靜態(tài)方法
STATIC:{
AGE_OF_MAJORITY:18
},
//構(gòu)造函數(shù)
constructor:function(name,age){
this.name=name;
this.age=age;
},
//實例方法
sayHello:function(){
console.log('Hellofrom'+this.name+'!');
},
//實例方法
drinkAlcohol:function(){
this.age<Person.AGE_OF_MAJORITY?
console.log('Tooyoung!Drinkmilkinstead!'):
console.log('Whiskeyorbeer?');
}
});
//暴露給命名空間
myLib.Person=Person;
})();
varjohn=newmyLib.Person('John',16);
john.sayHello();//log"HellofromJohn!"
john.drinkAlcohol();//log"Tooyoung!Drinkmilkinstead!"
b、繼承一個類
復(fù)制代碼 代碼如下:
(function(){
//Dreamer繼承Person
varDreamer=my.Class(Person,{
//構(gòu)造方法
constructor:function(name,age,dream){
Dreamer.Super.call(this,name,age);
this.dream=dream;
},
//實例方法
sayHello:function(){
superSayHello.call(this);
console.log('Idreamof'+this.dream+'!');
},
//實例方法
wakeUp:function(){
console.log('Wakeup!');
}
});
//Super訪問父類
varsuperSayHello=Dreamer.Super.prototype.sayHello;
//暴露給全局命名空間
myLib.Dreamer=Dreamer;
})();
varsylvester=newmyLib.Dreamer('Sylvester',30,'eatingTweety');
sylvester.sayHello();//log"HellofromSylvester!IdreamofeatingTweety!"
sylvester.wakeUp();//log"Wakeup!"
c、給類添加新方法
復(fù)制代碼 代碼如下:
//給myLib.Dreamer添加新方法
my.extendClass(myLib.Dreamer,{
//添加靜態(tài)方法
STATIC:{
s_dongSomeThing:function(){
console.log("dosomething!");
}
},
//添加實例方法
touchTheSky:function(){
console.log('Touchingthesky');
},
//添加實例方法
reachTheStars:function(){
console.log('Sheissopretty!');
}
});
d、實現(xiàn)一個類的方法
復(fù)制代碼 代碼如下:
//聲明一個新類
myLib.ImaginaryTraveler=my.Class({
travel:function(){console.log('Travelingonacarpet!');},
crossOceans:function(){console.log('SayinghitoMobyDick!');}
});
(function(){
//Dreamer繼承Person實現(xiàn)ImaginaryTraveler的方法
varDreamer=my.Class(Person,ImaginaryTraveler,{
//構(gòu)造方法
constructor:function(name,age,dream){
Dreamer.Super.call(this,name,age);
this.dream=dream;
}
//...
});
//暴露給全局命名空間
myLib.Dreamer=Dreamer;
})();
varaladdin=newDreamer('Aladdin');
aladdininstanceofPerson;//true
aladdininstanceofImaginaryTraveler;//false
aladdin.travel();
aladdin.wakeUp();
aladdin.sayHello();
如果怕忘記new操作符
復(fù)制代碼 代碼如下:
varPerson=my.Class({
//youcannowcalltheconstructorwithorwithoutnew
constructor:function(name,city){
if(!(thisinstanceofPerson))
returnnewPerson(name,city);
this.name=name;
this.city=citye;
}
});
下面看一下my.class的源代碼解析:
my.Class實現(xiàn)思路基本是這樣的,如果只有一個參數(shù),那么聲明的是一個基礎(chǔ)類,這個參數(shù)是用來聲明新類的方法和屬以及構(gòu)造函數(shù)。它不是繼承而來,但它可以被繼承。
繼承的思路,就是如果有兩個參數(shù),第一個參數(shù)做為父類被繼承,第二參數(shù)用來聲明新類的方法和屬性以及構(gòu)造函數(shù),它同樣可以被繼承。
如果有三個以上參數(shù)那么,除出第一個參數(shù)做為繼承的父類,最后一個參數(shù)用聲明新類的方法和屬性以及構(gòu)造函數(shù)。中間的參數(shù)是用類來擴展新類的方法。當(dāng)然也可以通過my.extendClass擴展新方法。
同時,類庫為commonJS和瀏覽環(huán)境都提供了支持!
復(fù)制代碼 代碼如下:
/*globalsdefine:true,window:true,module:true*/
(function(){
//Namespaceobject
varmy={};
//保證AMD分模塊可用
if(typeofdefine!=='undefined')
define([],function(){
returnmy;
});
elseif(typeofwindow!=='undefined')
//保證客戶端可用
window.my=my;
else
//保證后臺可用
module.exports=my;
//============================================================================
//@methodmy.Class
//@paramsbody:Object
//@paramsSuperClass:function,ImplementClasses:function...,body:Object
//@returnfunction
my.Class=function(){
varlen=arguments.length;
varbody=arguments[len-1];//最后一個參數(shù)是指定本身的方法
varSuperClass=len>1?arguments[0]:null;//第一個參數(shù)是指繼承的方法,實例和靜態(tài)部分均繼承
varhasImplementClasses=len>2;//如果有第三個參數(shù),那么第二個就是implementClass,這里其實只繼承實例對象
varClass,SuperClassEmpty;
//保證構(gòu)造方法
if(body.constructor===Object){
Class=function(){};
}else{
Class=body.constructor;
//保證后面不覆蓋constructor
deletebody.constructor;
}
//處理superClass部分
if(SuperClass){
//中間件實現(xiàn)實例屬性的繼承
SuperClassEmpty=function(){};
SuperClassEmpty.prototype=SuperClass.prototype;
Class.prototype=newSuperClassEmpty();//原型繼承,解除引用
Class.prototype.constructor=Class;//保證constructor
Class.Super=SuperClass;//父對象訪問接口
//靜態(tài)方法繼承,重載superClass方法
extend(Class,SuperClass,false);
}
//處理ImplementClass部分,其實只繼承實例屬性部分,除SuperClass#arguments[0]#和body#arguments[length-1]#
if(hasImplementClasses)
for(vari=1;i<len-1;i++)
//implement是繼承的實例屬性部分,重載父對象implementClass方法
extend(Class.prototype,arguments[i].prototype,false);
//處理本身聲明body部分,靜態(tài)要STATIC指定,實例部分要刪除STATIC部分
extendClass(Class,body);
returnClass;
};
//============================================================================
//@methodmy.extendClass
//@paramsClass:function,extension:Object,?override:boolean=true
varextendClass=my.extendClass=function(Class,extension,override){
//靜態(tài)部分繼承靜態(tài)部分
if(extension.STATIC){
extend(Class,extension.STATIC,override);
//保證實例部分不繼承靜態(tài)方法
deleteextension.STATIC;
}
//實例屬性繼繼承實例部
extend(Class.prototype,extension,override);
};
//============================================================================
varextend=function(obj,extension,override){
varprop;
//其實這里的flase是表明,覆蓋父對象的方法
if(override===false){
for(propinextension)
if(!(propinobj))
obj[prop]=extension[prop];
}else{
//這里其實不覆蓋父對象的方法,包括toString
for(propinextension)
obj[prop]=extension[prop];
if(extension.toString!==Object.prototype.toString)
obj.toString=extension.toString;
}
};
})();
二、KLASS
項目地址:https://github.com/ded/klass
先看使用方法:
a、新建一個類
復(fù)制代碼 代碼如下:
//聲明一個類
varPerson=klass(function(name){
this.name=name
})
.statics({//靜態(tài)方法
head:':)',
feet:'_|_'
})
.methods({//實例方法
walk:function(){}
})
b、繼承一個類
復(fù)制代碼 代碼如下:
//SuperHuman繼承Person
varSuperHuman=Person.extend(function(name){
//自動調(diào)用父類的構(gòu)造方法
})
.methods({
walk:function(){
//顯式聲明調(diào)用父類的walk方法
this.supr()
this.fly()
},
fly:function(){}
})
newSuperHuman('Zelda').walk()
c、字面量方式聲明一個類
復(fù)制代碼 代碼如下:
varFoo=klass({
foo:0,
initialize:function(){
this.foo=1
},
getFoo:function(){
returnthis.foo
},
setFoo:function(x){
this.foo=x
returnthis.getFoo()
}
})
d、實現(xiàn)一個類的方法
因為有時候你可能希望覆寫或者混合一個實例方法,可以這樣:
復(fù)制代碼 代碼如下:
//可以傳遞一個字面量去繼承
varAlien=SuperHuman.extend({
beam:function(){
this.supr()
//beamintospace
}
})
varSpazoid=newAlien('Zoopo')
if(beamIsDown){
//覆寫beam方法
Spazoid.implement({
beam:function(){
this.supr()
//fallbacktojets
this.jets()
}
})
}
下面看一下klass源代碼解析:
klass的基本設(shè)計思路很明確,極力的模仿其它語言的繼承方式。比如:子類構(gòu)造方法調(diào)用父類的構(gòu)造方法,還可以顯式的聲明調(diào)用父類的方法。
這種判斷都是基于正則匹配:fnTest=/xyz/.test(function(){xyz;})?/\bsupr\b/:/.*/;關(guān)鍵字"super"
如果顯示的聲明了要調(diào)用父類的方法,那么聲明方法的時候,就包裝成一個內(nèi)部調(diào)用父類方法且返回相同值的函數(shù),給當(dāng)前類的方法。
另一方面,構(gòu)造方法,也是比較靈活的。如果顯示的聲明了initialize,那么這就是構(gòu)造方法。否則如果參數(shù)是個function那么它就做為構(gòu)造方法,否則就用父類的構(gòu)造方法。
通過statics方式添加靜態(tài)方法,通過實例的implements和靜態(tài)方法methods添加實例方法。
通過父類的extend實現(xiàn)繼承。
同時,類庫為commonJS和瀏覽環(huán)境都提供了支持!
復(fù)制代碼 代碼如下:
/**
*Klass.js-copyright@dedfat
*version1.0
*https://github.com/ded/klass
*Followoursoftwarehttp://twitter.com/dedfat:)
*MITLicense
*/
!function(context,f){
//fnTest用來驗證是否可能通過正則找出調(diào)用super父類方法的方法
varfnTest=/xyz/.test(function(){xyz;})?/\bsupr\b/:/.*/,
noop=function(){},
proto='prototype',
isFn=function(o){
returntypeofo===f;
};
//基礎(chǔ)類
functionklass(o){
returnextend.call(typeofo==f?o:noop,o,1);
}
//包裝成一個借用super同名方法的函數(shù)
functionwrap(k,fn,supr){
returnfunction(){
//緩存原this.super
vartmp=this.supr;
//暫把this.super改造成借用super的同名方法above
//供o里顯式的聲明(fnTest.text(fn)==true)要借用super的同名方法使用
this.supr=supr[proto][k];
//借用執(zhí)行并保存返回值
varret=fn.apply(this,arguments);
//恢復(fù)原this.super
this.supr=tmp;
//返回返回值,保證wrap后的返回值跟原來一致
returnret;
};
}
//如果o和super有同名方法,且o顯式聲明借用super的同名方法,就wrap成一個待執(zhí)行函數(shù)供使用
//如果沒有顯式的聲明借用super的同名方法,或者是o獨有的方法,或者不是方法就直接用
functionprocess(what,o,supr){
for(varkino){
//如果是非繼承方法,按方法注釋規(guī)則執(zhí)行,最終都放進what
if(o.hasOwnProperty(k)){
what[k]=typeofo[k]==f
&&typeofsupr[proto][k]==f
&&fnTest.test(o[k])
?wrap(k,o[k],supr):o[k];
}
}
}
//繼承方法的實現(xiàn),fromSub是用來控制是否繼承而來,上面的klass里面fromSub是1,表明非繼承而來,構(gòu)造函數(shù)不借用super執(zhí)行
functionextend(o,fromSub){
//noop做為媒介類實現(xiàn)原型繼承的解除引用
noop[proto]=this[proto];
varsupr=this,
prototype=newnoop(),//創(chuàng)建實例對象供原型繼承使用,解除引用
isFunction=typeofo==f,
_constructor=isFunction?o:this,//如果o是一個構(gòu)造方法就用,否則由this來決定構(gòu)造函數(shù)
_methods=isFunction?{}:o,//如果o是一個{...}應(yīng)該用methods放到fn原型里,如果里面有initialize就是構(gòu)造函數(shù),如果o是函數(shù)就由上面_constructor決定o是構(gòu)造函數(shù)
fn=function(){//因為kclass借助了kclass,所以最終實際上返回的就是fn,fn其實就新類的構(gòu)造函數(shù)
//1如果o是{...}就會被methods直接過濾并添加到fn的原型里,如果o里面有initialize,那么fn的原型里就有initialize,那么它就是構(gòu)造方法
//2如果o是function,methods什么也添加不到fn的原型里,但是_constructor會接受o當(dāng)構(gòu)造函數(shù)
//3如果o是{....},同時里面也沒有initialize,那么就是this當(dāng)構(gòu)造函數(shù),如果在klass里由call決定,顯然構(gòu)造函數(shù)是noop,如果在非基礎(chǔ)類里,構(gòu)造函數(shù)就是父類的構(gòu)造函數(shù)
//由于o不是函數(shù)不會自動調(diào)用父類的構(gòu)造函數(shù),只是把父類的構(gòu)造函數(shù)當(dāng)做當(dāng)前類的構(gòu)造函數(shù)----這都是由于this的指向決定的
console.log(this);
if(this.initialize){
this.initialize.apply(this,arguments);
}else{
//調(diào)用父類構(gòu)造方法
//如上面3,o不是函數(shù),不會調(diào)用父類的構(gòu)造方法
//基礎(chǔ)類無父類,不會調(diào)用父類構(gòu)造方法
fromSub||isFn(o)&&supr.apply(this,arguments);
//調(diào)用本類構(gòu)造方法
//參考上面2,3要么是noop要么是o
console.log(_constructor==noop);
_constructor.apply(this,arguments);
}
};
//構(gòu)造原型方法的接口
fn.methods=function(o){
process(prototype,o,supr);
fn[proto]=prototype;
returnthis;
};
//執(zhí)行實現(xiàn)新類原型,保證新類的constructor
fn.methods.call(fn,_methods).prototype.constructor=fn;
//保證新類可以被繼承
fn.extend=arguments.callee;
//添加實例方法或者靜態(tài)方法,statics:靜態(tài)方法,implement實例方法
fn[proto].implement=fn.statics=function(o,optFn){
//保證o是一個object對象,如果o是一個字符串,那么就是添一個方法的情況,如果o是一個object對象說明是批量添加的
//因為要從o里面拷貝
o=typeofo=='string'?(function(){
varobj={};
obj[o]=optFn;
returnobj;
}()):o;
//添加實例方法或者靜態(tài)方法,statics:靜態(tài)方法,implement實例方法
process(this,o,supr);
returnthis;
};
returnfn;
}
//后臺用,nodejs
if(typeofmodule!=='undefined'&&module.exports){
module.exports=klass;
}else{
varold=context.klass;
//防沖突
klass.noConflict=function(){
context.klass=old;
returnthis;
};
//前臺瀏覽器用
//window.kclass=kclass;
context.klass=klass;
}
}(this,'function');
三、還有一種簡單實現(xiàn)
實現(xiàn)思路很簡單,就是利用ECMAScript5原型式繼承Object.create方法,封裝成一個方法,如果不支持ECMAScript5的環(huán)境,就平移退化到
復(fù)制代碼 代碼如下:
functionF(){};
F.prototype=superCtor.prototype;
ctor.prototype=newF();
ctor.prototype.constructor=ctor;
同樣的,除最后一個參數(shù)是當(dāng)前類的方法聲明,其它參數(shù)均做為繼承父類,需要循環(huán)繼承,但當(dāng)這里處理的相對比較簡單,沒涉及到覆蓋。你可以自己動手添加。
復(fù)制代碼 代碼如下:
varClass=(function(){
/**
*Inheritsfunction.(node.js)
*
*@paramctorsubclass'sconstructor.
*@paramsuperctorsuperclass'sconstructor.
*/
varinherits=function(ctor,superCtor){
//顯式的指定父類
ctor.super_=superCtor;
//ECMAScript5原型式繼承并解除引用
if(Object.create){
ctor.prototype=Object.create(superCtor.prototype,{
constructor:{
value:ctor,
enumerable:false,
writable:true,
configurable:true
}
});
}else{
//無Object.create方法的平穩(wěn)退化
functionF(){};
F.prototype=superCtor.prototype;
ctor.prototype=newF();
ctor.prototype.constructor=ctor;
}
};
/**
*Classfunction.
*/
returnfunction(){
//最后一個參數(shù)是新類方法、屬性和構(gòu)造函數(shù)聲明
varsubClazz=arguments[arguments.length-1]||function(){};
//initialize是構(gòu)造函數(shù),否構(gòu)造函數(shù)就是一個空函數(shù)
varfn=subClazz.initialize==null?function(){}:subClazz.initialize;
//繼承除最一個參數(shù)以的類,多繼承,也可以用作擴展方法
for(varindex=0;index<arguments.length-1;index++){
inherits(fn,arguments[index]);
}
//實現(xiàn)新類的方法
for(varpropinsubClazz){
if(prop=="initialize"){
continue;
}
fn.prototype[prop]=subClazz[prop];
}
returnfn;
}
})();
看下面實例:
復(fù)制代碼 代碼如下:
/**
*ThedefinitionofCatClass.
*/
varCat=Class({
/**
*Constructor.
*
*@paramnameCat'sname
*/
initialize:function(name){
this.name=name;
},
/**
*Eatfunction.
*/
eat:function(){
alert(this.name+"iseatingfish.");
}
});
/**
*ThedefinitionofBlackCatClass.
*/
varBlackCat=Class(Cat,{
/**
*Constructor.
*
*@paramnameCat'sname.
*@paramageCat'sage.
*/
initialize:function(name,age){
//calltheconstructorofsuperclass.
BlackCat.super_.call(this,name);
this.age=age;
},
/**
*Eatfunction.
*/
eat:function(){
alert(this.name+"("+this.age+")iseatingdog.");
}
});
/**
*ThedefinitionofBlackFatCatClass.
*/
varBlackFatCat=Class(BlackCat,{
/**
*Constructor.
*
*@paramnameCat'sname.
*@paramageCat'sage.
*@paramweightCat'sweight.
*/
initialize:function(name,age,weight){
//calltheconstructorofsuperclass.
BlackFatCat.super_.call(this,name,age);
this.weight=weight;
},
/**
*Eatfunction.
*/
eat:function(){
alert(this.name+"("+this.age+")iseatingdog.Myweight:"+this.weight);
}
});
/**
*ThedefinitionofDogClass.
*/
varDog=Class({});
varcat=newBlackFatCat("John",24,"100kg");
cat.eat();
//true
alert(catinstanceofCat);
//true
alert(catinstanceofBlackCat);
//true
alert(catinstanceofBlackFatCat);
//true
alert(cat.constructor===BlackFatCat);
//false
alert(catinstanceofDog);
四、mootools類庫的Class
源碼解析可以看這里:http://www.cnblogs.com/hmking/archive/2011/09/30/2196504.html
看具體用法:
a、新建一個類
復(fù)制代碼 代碼如下:
varCat=newClass({
initialize:function(name){
this.name=name;
}
});
varmyCat=newCat('Micia');
alert(myCat.name);//alerts'Micia'
varCow=newClass({
initialize:function(){
alert('moooo');
}
});
b、繼承的實現(xiàn)
復(fù)制代碼 代碼如下:
varAnimal=newClass({
initialize:function(age){
this.age=age;
}
});
varCat=newClass({
Extends:Animal,
initialize:function(name,age){
this.parent(age);//callsinitalizemethodofAnimalclass
this.name=name;
}
});
varmyCat=newCat('Micia',20);
alert(myCat.name);//alerts'Micia'.
alert(myCat.age);//alerts20.
c、擴充類的實現(xiàn)
復(fù)制代碼 代碼如下:
varAnimal=newClass({
initialize:function(age){
this.age=age;
}
});
varCat=newClass({
Implements:Animal,
setName:function(name){
this.name=name
}
});
varmyAnimal=newCat(20);
myAnimal.setName('Micia');
alert(myAnimal.name);//alerts'Micia'.
五、悟透javascript:語法甘露
先看用法實例
a、創(chuàng)建類
復(fù)制代碼 代碼如下:
//創(chuàng)建類Person
varPerson=Class(object,{
Create:function(name,age){
this.name=name;
this.age=age;
},
SayHello:function(){
alert("Hello,I'm"+this.name+","+this.age+"yearsold.");
}
});
varBillGates=New(Person,["BillGates",53]);
BillGates.SayHello();
b、繼承類
復(fù)制代碼 代碼如下:
//Employee繼承Person
varEmployee=Class(Person,{
Create:function(name,age,salary){
Person.Create.call(this,name,age);
//調(diào)用基類的構(gòu)造函數(shù)
this.salary=salary;
},
ShowMeTheMoney:function(){
alert(this.name+"$"+this.salary);
}
});
varSteveJobs=New(Employee,["SteveJobs",53,1234]);
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
下面是源碼分析:顯然,多了一個New方法,創(chuàng)建類和新建類的實例都被巧妙的封裝了。形成了一個有意義的整體!還有一點不同的地方,所有的類都基于字面量,而不是基于函數(shù)。代碼很簡短,但其中原理卻很豐富也很巧妙,可以細細品味一番!
復(fù)制代碼 代碼如下:
//創(chuàng)建類的函數(shù),用于聲明類及繼承關(guān)系
functionClass(aBaseClass,aClassDefine){
//創(chuàng)建類的臨時函數(shù)殼
functionclass_(){
this.Type=aBaseClass;
//我們給每一個類約定一個Type屬性,引用其繼承的類
for(varmemberinaClassDefine)
this[member]=aClassDefine[member];
//復(fù)制類的全部定義到當(dāng)前創(chuàng)建的類
};
class_.prototype=aBaseClass;
returnnewclass_();
};
//創(chuàng)建對象的函數(shù),用于任意類的對象創(chuàng)建
functionNew(aClass,aParams){
//創(chuàng)建對象的臨時函數(shù)殼
functionnew_(){
this.Type=aClass;
//我們也給每一個對象約定一個Type屬性,據(jù)此可以訪問到對象所屬的類
if(aClass.Create)
aClass.Create.apply(this,aParams);
//我們約定所有類的構(gòu)造函數(shù)都叫Create,這和DELPHI比較相似
};
new_.prototype=aClass;
returnnewnew_();
};
由于寫的比較籠統(tǒng),可能有很多地方?jīng)]有解析到,也可能有不準(zhǔn)確的地方,還望指正。
看完上面幾種解析,相信息自己也可以寫出一個自己的封裝類庫出來,至于,怎么實現(xiàn)看個人喜好了。但基本的思都是一樣的基于原型的繼承方式和循環(huán)拷貝新方法。
原文來自:穆乙 http://www.cnblogs.com/pigtail/
相關(guān)文章
在js中判斷checkboxlist(.net控件客戶端id)是否有選中
添加或修改內(nèi)容時,需要對關(guān)鍵數(shù)據(jù)進行判空處理,checkboxlist是否有選擇項如何使用js判斷實現(xiàn),接下來為大家詳細介紹下實現(xiàn)方法,感興趣的朋友可以參考下哈2013-04-04在JavaScript中使用for循環(huán)的方法詳解
在本文中,我們將學(xué)習(xí) JavaScript 中提供,的 for 循環(huán),我們將了解如何在 JavaScript 中使用 for...in 循環(huán)語句、其語法、工作原理示例、何時使用或避免使用它以及我們可以使用哪些其他類型的循環(huán),需要的朋友可以參考下2023-07-07微信小程序ReferenceError:xxx?is?not?defined報錯解決辦法
最近在學(xué)習(xí)微信小程序的開發(fā),在一個練手項目中竟然報錯,所以下面這篇文章主要給大家介紹了關(guān)于微信小程序ReferenceError:xxx?is?not?defined報錯的解決辦法,需要的朋友可以參考下2023-12-12BootStrap學(xué)習(xí)系列之Bootstrap Typeahead 組件實現(xiàn)百度下拉效果(續(xù))
這篇文章主要介紹了BootStrap學(xué)習(xí)系列之Bootstrap Typeahead 組件實現(xiàn)百度下拉效果的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07