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