沒那么簡(jiǎn)單 那些應(yīng)該吃透的JavaScript概念
文章的目的是讓更多的程序員深入理解JavaScript的一些概念,其實(shí)關(guān)于這些,我們?cè)诳梢栽诰W(wǎng)上看到很多類似的內(nèi)容,所以現(xiàn)在該是向深入理解的方向靠攏的時(shí)候了。
51CTO推薦閱讀:揭開JavaScript閉包的真實(shí)面目
一,function
從一開始接觸到j(luò)s就感覺好靈活,每個(gè)人的寫法都不一樣,比如一個(gè)function就有N種寫法。如:functionshowMsg(){},varshowMsg=function(){},showMsg=function(){}。似乎沒有什么區(qū)別,都是一樣的嘛,真的是一樣的嗎,大家看看下面的例子:
- //函數(shù)定義:命名函數(shù)(聲明式),匿名函數(shù)(引用式)
- //聲明式,定義代碼先于函數(shù)執(zhí)行代碼被解析
- functiont1(){
- dwn("t1");
- }
- t1();
- functiont1(){
- dwn("newt1");
- }
- t1();
- //引用式,在函數(shù)運(yùn)行中進(jìn)行動(dòng)態(tài)解析
- vart1=function(){
- dwn("newnewt1");
- }
- t1();
- vart1=function(){
- dwn("newnewnewt1");
- }
- t1();
- //以上輸出:newt1,newt1,newnewt1,newnewnewt1
可能想著應(yīng)該是輸出t1,newt1,newnewt1,newnewnewt1,結(jié)果卻并不是這樣,應(yīng)該理解這句話:聲明式,定義代碼先于函數(shù)執(zhí)行代碼被解析。如果深入一步,應(yīng)該說是scope鏈問題,實(shí)際上前面兩個(gè)方法等價(jià)于window.t1,可以理解為t1是window的一個(gè)公有屬性,被賦了兩次值,以最后一次賦值為最終值。而后面兩個(gè)方法,可以理解為是t1是個(gè)變量,第四個(gè)方法的var去掉之后的結(jié)果仍然不會(huì)改變。
然而,當(dāng)?shù)谒膫€(gè)方法改成functiont1(){}這樣的聲明式時(shí),結(jié)果變成了newnewnewt1,newnewnewt1,newnewt1,newnewt1。前面兩個(gè)按照我的理解可以很好的理解為什么是這個(gè)答案,第三個(gè)也可以理解,但是最后一個(gè)輸出讓我比較糾結(jié),希望有高手出現(xiàn)解答一下。另外匿名函數(shù)還有(function(){...})()這樣的寫法,最后一個(gè)括號(hào)用于參數(shù)輸入還有vart1=newfunction(){..}這樣的聲明,實(shí)際上t1已經(jīng)是一個(gè)對(duì)象了。
- vart2=newfunction()
- {
- vartemp=100;//私有成員
- this.temp=200;//公有成員,這兩個(gè)概念會(huì)在第三點(diǎn)以后展開說明
- returntemp+this.temp;
- }
- alert(typeof(t2));//object
- alert(t2.constructor());//300
- 除此之外,還有使用系統(tǒng)內(nèi)置函數(shù)對(duì)象來構(gòu)建一個(gè)函數(shù),例:
- vart3=newFunction('vartemp=100;this.temp=200;returntemp+this.temp;');//這個(gè)位置加不加new結(jié)果都一樣,WHY
- alert(typeof(t3));//function
- alert(t3());//300
二,創(chuàng)建對(duì)象
首先我們理解一下面向?qū)ο缶幊蹋∣bject-OrientedProgramming,OOP),使用OOP技術(shù),常常要使用許多代碼模塊,每個(gè)模塊都提供特定的功能,每個(gè)模塊都是孤立的,甚至與其它模塊完全獨(dú)立。這種模塊化編程方法提供了非常大的多樣性,大大增加了代碼的重用機(jī)會(huì)。可以舉例進(jìn)一步說明這個(gè)問題,假定計(jì)算機(jī)上的一個(gè)高性能應(yīng)用程序是一輛一流賽車。如果使用傳統(tǒng)的編程技巧,這輛賽車就是一個(gè)單元。
如果要改進(jìn)該車,就必須替換整個(gè)單元,把它送回廠商,讓汽車專家升級(jí)它,或者購買一個(gè)新車。如果使用OOP技術(shù),就只需從廠商處購買新的引擎,自己按照說明替換它,而不必用鋼鋸切割車體。不過大部分的論點(diǎn)是,JavaScript并不是直接的面向?qū)ο蟮恼Z言,但是通過模擬可以做到很多面向?qū)ο笳Z言才能做到的事,如繼承,多態(tài),封裝,JavaScript都能干。
- //以下三種構(gòu)造對(duì)象的方法
- //newObject,實(shí)例化一個(gè)Object
- vara=newObject();
- a.x=1,a.y=2;
- //對(duì)象直接量
- varb={x:1,y:2};
- //定義類型
- functionPoint(x,y){//類似于C#中的類
- this.x=x;
- this.y=y;
- }
- varp=newPoint(1,2);//實(shí)例化類
第一種方法通過構(gòu)造基本對(duì)象直接添加屬性的方法來實(shí)現(xiàn),第二種和第一種差不多,可以看成是第一種方法的快捷表示法。第三種方法中,可以以”類“為基礎(chǔ),創(chuàng)造多個(gè)類型相同的對(duì)象。
#p#
三,對(duì)象屬性的封裝(公有和私有)
以例子來說明:
- functionList(){
- varm_elements=[];//私有成員,在對(duì)象外無法訪問
- m_elements=Array.apply(m_elements,arguments);
- //此處模擬getter,使用時(shí)alist.length;
- //等價(jià)于getName()方式:this.length=function(){returnm_elements.length;},使用時(shí)alist.length();
- //公有屬性,可以通過"."運(yùn)算符或下標(biāo)來訪問
- this.length={
- valueOf:function(){
- returnm_elements.length;
- },
- toString:function(){
- returnm_elements.length;
- }
- }
- //公有方法,此方法使用得alert(alist)相當(dāng)于alert(alist.toString())
- this.toString=function(){
- returnm_elements.toString();
- }
- //公有方法
- this.add=function(){
- m_elements.push.apply(m_elements,arguments);
- }
- //私有方法如下形式,這里涉及到了閉包的概念,接下來繼續(xù)說明
- //varadd=function()或functionadd()
- //{
- //m_elements.push.apply(m_elements,arguments);
- //}
- }
- varalist=newList(1,2,3);
- dwn(alist);//=alert(alist.toString()),輸出1,2,3
- dwn(alist.length);//輸出3
- alist.add(4,5,6);
- dwn(alist);//輸出1,2,3,4,5,6
- dwn(alist.length);//輸出6
四,屬性和方法的類型
JavaScript里,對(duì)象的屬性和方法支持4種不同的類型:privateproperty(私有屬性),dynamicpublicproperty(動(dòng)態(tài)公有屬性),staticpublicproperty/prototypeproperty(靜態(tài)公有屬性或原型屬性),staticproperty(靜態(tài)屬性或類屬性)。私有屬性對(duì)外界完全不具備訪問性,可以通過內(nèi)部的getter和setter(都是模擬);動(dòng)態(tài)公有屬性外界可以訪問,每個(gè)對(duì)象實(shí)例持有一個(gè)副本,不會(huì)相互影響;原型屬性每個(gè)對(duì)象實(shí)例共享唯一副本;類屬性不作為實(shí)例的屬性,只作為類的屬性。以下是例子:
- //動(dòng)態(tài)公有類型,靜態(tài)公有類型(原型屬性)
- functionmyClass(){
- varp=100;//privateproperty
- this.x=10;//dynamicpublicproperty
- }
- myClass.prototype.y=20;
- //要想成為高級(jí)JavaScript階段,prototype和閉包必須得理解和適當(dāng)應(yīng)用
- myClass.z=30;//staticproperty
- vara=newmyClass();
- dwn(a.p)//undefined
- dwn(a.x)//10
- dwn(a.y)//20
- a.x=20;
- a.y=40;
- dwn(a.x);//20
- dwn(a.y);//40
- delete(a.x);//刪除對(duì)象a的屬性x
- delete(a.y);//刪除對(duì)象a的屬性y
- dwn(a.x);//undefined
- dwn(a.y);//20靜態(tài)公有屬性y被刪除后還原為原型屬性y
- dwn(a.z);//undefined類屬性無法通過對(duì)象訪問
- dwn(myClass.z);
五,原型(prototype)
這里只講部分,prototype和閉包都不是幾句話都能講清楚的,如果這里可以給你一些啟蒙,則萬幸矣。習(xí)語”照貓畫虎“,這里的貓就是原型,虎是類型,可以表示成:虎.prototype=某只貓or虎.prototype=new貓()。因?yàn)樵蛯傩悦總€(gè)對(duì)象實(shí)例共享唯一副本,所以當(dāng)實(shí)例中的一個(gè)調(diào)整了一個(gè)原型屬性的值時(shí),所有實(shí)例調(diào)用這個(gè)屬性時(shí)都將發(fā)生變化,這點(diǎn)需要注意,以下是原型關(guān)系的類型鏈:
- functionClassA(){
- }
- ClassA.prototype=newObject();
- functionClassB(){
- }
- ClassB.prototype=newClassA();
- functionClassC(){
- }
- ClassC.prototype=newClassB();
- varobj=newClassC();
- dwn(objinstanceofClassC);//true
- dwn(objinstanceofClassB);//true
- dwn(objinstanceofClassA);//true
- dwn(objinstanceofObject);//true
- 帶默認(rèn)值的Point對(duì)象:
- functionPoint2(x,y){
- if(x)this.x=x;
- if(y)this.y=y;
- }
- //設(shè)定Point2對(duì)象的x,y默認(rèn)值為0
- Point2.prototype.x=0;
- Point2.prototype.y=0;
- //p1是一個(gè)默認(rèn)(0,0)的對(duì)象
- varp1=newPoint2();//可以寫成varp1=newPoint2也不會(huì)出錯(cuò),WHY
- //p2賦值
- varp2=newPoint2(1,2);
- dwn(p1.x+","+p1.y);//0,0
- dwn(p2.x+","+p2.y);//1,2
- delete對(duì)象的屬性后,原型屬性將回到初始化的狀態(tài):
- functionClassD(){
- this.a=100;
- this.b=200;
- this.c=300
- }
- ClassD.prototype=newClassD();//將ClassD原有的屬性設(shè)為原型,包括其值
- ClassD.prototype.reset=function(){//將非原型屬性刪除
- for(vareachinthis){
- deletethis[each];
- }
- }
- vard=newClassD();
- dwn(d.a);//100
- d.a*=2;
- d.b*=2;
- d.c*=2;
- dwn(d.a);//200
- dwn(d.b);//400
- dwn(d.c);//600
- d.reset();//刪掉非原型屬性,所有回來原型
- dwn(d.a);//100
- dwn(d.b);//200
- dwn(d.c);//300
#p#
六,繼承
如果兩個(gè)類都是同一個(gè)實(shí)例的類型,那么它們之間存在著某種關(guān)系,我們把同一個(gè)實(shí)例的類型之間的泛化關(guān)系稱為繼承。C#和JAVA中都有這個(gè),具體的理解就不說了。在JavaScript中,并不直接從方法上支持繼承,但是就像前面說的,可以模擬。
方法可以歸納為四種:構(gòu)造繼承法,原型繼承法,實(shí)例繼承法和拷貝繼承法。融會(huì)貫通之后,還有混合繼續(xù)法,這是什么法,就是前面四種挑幾種混著來。以下例子來源于王者歸來,其中涉及到了apply,call和一些Array的用法,有興趣的可以自己在園子里搜索一下。
1,構(gòu)造繼續(xù)法例子:
- //定義一個(gè)Collection類型
- functionCollection(size)
- {
- this.size=function(){returnsize};//公有方法,可以被繼承
- }
- Collection.prototype.isEmpty=function(){//靜態(tài)方法,不能被繼承
- returnthis.size()==0;
- }
- //定義一個(gè)ArrayList類型,它"繼承"Collection類型
- functionArrayList()
- {
- varm_elements=[];//私有成員,不能被繼承
- m_elements=Array.apply(m_elements,arguments);
- //ArrayList類型繼承Collection
- this.base=Collection;
- this.base.call(this,m_elements.length);
- this.add=function()
- {
- returnm_elements.push.apply(m_elements,arguments);
- }
- this.toArray=function()
- {
- returnm_elements;
- }
- }
- ArrayList.prototype.toString=function()
- {
- returnthis.toArray().toString();
- }
- //定義一個(gè)SortedList類型,它繼承ArrayList類型
- functionSortedList()
- {
- //SortedList類型繼承ArrayList
- this.base=ArrayList;
- this.base.apply(this,arguments);
- this.sort=function()
- {
- vararr=this.toArray();
- arr.sort.apply(arr,arguments);
- }
- }
- //構(gòu)造一個(gè)ArrayList
- vara=newArrayList(1,2,3);
- dwn(a);
- dwn(a.size());//a從Collection繼承了size()方法
- dwn(a.isEmpty);//但是a沒有繼承到isEmpty()方法
- //構(gòu)造一個(gè)SortedList
- varb=newSortedList(3,1,2);
- b.add(4,0);//b從ArrayList繼承了add()方法
- dwn(b.toArray());//b從ArrayList繼承了toArray()方法
- b.sort();//b自己實(shí)現(xiàn)的sort()方法
- dwn(b.toArray());
- dwn(b);
- dwn(b.size());//b從Collection繼承了size()方法
2,原型繼承法例子
- //定義一個(gè)Point類型
- functionPoint(dimension)
- {
- this.dimension=dimension;
- }
- //定義一個(gè)Point2D類型,"繼承"Point類型
- functionPoint2D(x,y)
- {
- this.x=x;
- this.y=y;
- }
- Point2D.prototype.distance=function()
- {
- returnMath.sqrt(this.x*this.x+this.y*this.y);
- }
- Point2D.prototype=newPoint(2);//Point2D繼承了Point
- //定義一個(gè)Point3D類型,也繼承Point類型
- functionPoint3D(x,y,z)
- {
- this.x=x;
- this.y=y;
- this.z=z;
- }
- Point3D.prototype=newPoint(3);//Point3D也繼承了Point
- //構(gòu)造一個(gè)Point2D對(duì)象
- varp1=newPoint2D(0,0);
- //構(gòu)造一個(gè)Point3D對(duì)象
- varp2=newPoint3D(0,1,2);
- dwn(p1.dimension);
- dwn(p2.dimension);
- dwn(p1instanceofPoint2D);//p1是一個(gè)Point2D
- dwn(p1instanceofPoint);//p1也是一個(gè)Point
- dwn(p2instanceofPoint);//p2是一個(gè)Point
以上兩種方法是最常用。
3,實(shí)例繼承法例子
在說此法例子之前,說說構(gòu)造繼承法的局限,如下:
- functionMyDate()
- {
- this.base=Date;
- this.base.apply(this,arguments);
- }
- vardate=newMyDate();
- alert(date.toGMTString);//undefined,date并沒有繼承到Date類型,所以沒有toGMTString方法
核心對(duì)象的某些方法不能被構(gòu)造繼承,原因是核心對(duì)象并不像我們自定義的一般對(duì)象那樣在構(gòu)造函數(shù)里進(jìn)行賦值或初始化操作換成原型繼承法呢?,如下:
- functionMyDate(){}
- MyDate.prototype=newDate();
- vardate=newMyDate();
- alert(date.toGMTString);//'[object]'不是日期對(duì)象,仍然沒有繼承到Date類型!
現(xiàn)在,換成實(shí)例繼承法:
- functionMyDate()
- {
- varinstance=newDate();//instance是一個(gè)新創(chuàng)建的日期對(duì)象
- instance.printDate=function(){
- document.write("<p>"+instance.toLocaleString()+"</p>");
- }//對(duì)instance擴(kuò)展printDate()方法
- returninstance;//將instance作為構(gòu)造函數(shù)的返回值返回
- }
- varmyDate=newMyDate();
- dwn(myDate.toGMTString());//這回成功輸出了正確的時(shí)間字符串,看來myDate已經(jīng)是一個(gè)Date的實(shí)例了,繼承成功
- myDate.printDate();//如果沒有returninstance,將不能以下標(biāo)訪問,因?yàn)槭撬接袑?duì)象的方法
4,拷貝繼承法例子
- Function.prototype.extends=function(obj)
- {
- for(vareachinobj)
- {
- this.prototype[each]=obj[each];
- //對(duì)對(duì)象的屬性進(jìn)行一對(duì)一的復(fù)制,但是它又慢又容易引起問題
- //所以這種“繼承”方式一般不推薦使用
- }
- }
- varPoint2D=function(){
- //……
- }
- Point2D.extends(newPoint())
- {
- //……
- }
這種繼承法似乎是用得很少的。
5,混合繼承例子
- functionPoint2D(x,y)
- {
- this.x=x;
- this.y=y;
- }
- functionColorPoint2D(x,y,c)
- {
- Point2D.call(this,x,y);//這里是構(gòu)造繼承,調(diào)用了父類的構(gòu)造函數(shù)
- //從前面的例子看過來,這里等價(jià)于
- //this.base=Point2D;
- //this.base.call(this,x,y);
- this.color=c;
- }
- ColorPoint2D.prototype=newPoint2D();//這里用了原型繼承,讓ColorPoint2D以Point2D對(duì)象為原型
【編輯推薦】