posts - 5,  comments - 5,  trackbacks - 0
          JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~
          很少有人對(duì)JavaScript的面向?qū)ο筇匦赃M(jìn)行系統(tǒng)的分析。我希望接下來(lái)的文字讓你了解到這
          個(gè)語(yǔ)言最少為人知的一面。

          1.?JavaScript中的類型
          --------
          雖然JavaScript是一個(gè)基于對(duì)象的語(yǔ)言,但對(duì)象(Object)在JavaScript中不是第一型的。JS
          是以函數(shù)(Function)為第一型的語(yǔ)言。這樣說,不但是因?yàn)镴S中的函數(shù)具有高級(jí)語(yǔ)言中的函
          數(shù)的各種特性,而且也因?yàn)樵贘S中,Object也是由函數(shù)來(lái)實(shí)現(xiàn)的。——關(guān)于這一點(diǎn),可以在
          后文中“構(gòu)造與析構(gòu)”部分看到更進(jìn)一步的說明。

          JS中是弱類型的,他的內(nèi)置類型簡(jiǎn)單而且清晰:
          ---------------------------------------------------------
          undefined?:?未定義
          number????:?數(shù)字
          boolean???:?布爾值
          string????:?字符串
          function??:?函數(shù)
          object????:?對(duì)象

          ?1).?undefined類型
          ========================
          在IE5及以下版本中,除了直接賦值和typeof()之外,其它任何對(duì)undefined的操作都將導(dǎo)致
          異常。如果需要知道一個(gè)變量是否是undefined,只能采用typeof()的方法:
          <script>
          var?v;
          if?(typeof(v)?==?'undefined')?{
          ??//?...
          }
          </script>

          但是在IE5.5及以上版本中,undefined是一個(gè)已實(shí)現(xiàn)的系統(tǒng)保留字。因此可以用undefined來(lái)
          比較和運(yùn)算。檢測(cè)一個(gè)值是否是undefined的更簡(jiǎn)單方法可以是:
          <script>
          var?v;
          if?(v?===?undefined)?{
          ??//?...
          }
          </script>

          因此為了使得核心代碼能(部分地)兼容IE5及早期版本,Romo核心單元中有一行代碼用來(lái)
          “聲明”一個(gè)undefined值:
          //---------------------------------------------------------
          //?code?from?Qomolangma,?in?JSEnhance.js
          //---------------------------------------------------------
          var?undefined?=?void?null;

          這一行代碼還有一點(diǎn)是需要說明的,就是void語(yǔ)句的應(yīng)用。void表明“執(zhí)行其后的語(yǔ)句,且
          忽略返回值”。因此在void之后可以出現(xiàn)能被執(zhí)行的任何“單個(gè)”語(yǔ)句。而執(zhí)行的結(jié)果就是
          undefined。當(dāng)然,如果你愿意,你也可以用下面的代碼之一“定義undefined”。
          //---------------------------------------------------------
          //?1.?較復(fù)雜的方法,利用一個(gè)匿名的空函數(shù)執(zhí)行的返回
          //---------------------------------------------------------
          var?undefined?=?function(){}();

          //---------------------------------------------------------
          //?2.?代碼更簡(jiǎn)潔,但不易懂的方法
          //---------------------------------------------------------
          var?undefined?=?void?0;

          void也能像函數(shù)一樣使用,因此void(0)也是合法的。有些時(shí)候,一些復(fù)雜的語(yǔ)句可能不能
          使用void的關(guān)鍵字形式,而必須要使用void的函數(shù)形式。例如:
          //---------------------------------------------------------
          //?必須使用void()形式的復(fù)雜表達(dá)式
          //---------------------------------------------------------
          void(i=1);???????//?或如下語(yǔ)句:
          void(i=1,?i++);

          ?2).?number類型
          ========================
          JavaScript中總是處理浮點(diǎn)數(shù),因此它沒有象Delphi中的MaxInt這樣的常量,反而是有這
          樣兩個(gè)常值定義:
          ??Number.MAX_VALUE??:?返回?JScript?能表達(dá)的最大的數(shù)。約等于?1.79E+308。
          ??Number.MIN_VALUE??:?返回?JScript?最接近0的數(shù)。約等于?2.22E-308。

          因?yàn)闆]有整型的緣故,因此在一些關(guān)于CSS和DOM屬性的運(yùn)算中,如果你期望取值為整數(shù)2,
          你可能會(huì)得到字符串“2.0”——或者類似于此的一些情況。這種情況下,你可能需要用
          到全局對(duì)象(Gobal)的parseInt()方法。

          全局對(duì)象(Gobal)中還有兩個(gè)屬性與number類型的運(yùn)算有關(guān):
          ??NaN??????:?算術(shù)表達(dá)式的運(yùn)算結(jié)果不是數(shù)字,則返回NaN值。
          ??Infinity?:?比MAX_VALUE更大的數(shù)。

          如果一個(gè)值是NaN,那么他可以通過全局對(duì)象(Gobal)的isNaN()方法來(lái)檢測(cè)。然而兩個(gè)NaN
          值之間不是互等的。如下例:
          //---------------------------------------------------------
          //?NaN的運(yùn)算與檢測(cè)
          //---------------------------------------------------------
          var
          ??v1?=?10?*?'a';
          ??v2?=?10?*?'a';
          document.writeln(isNaN(v1));
          document.writeln(isNaN(v2));
          document.writeln(v1?==?v2);

          全局對(duì)象(Gobal)的Infinity表示比最大的數(shù)?(Number.MAX_VALUE)?更大的值。在JS中,
          它在數(shù)學(xué)運(yùn)算時(shí)的價(jià)值與正無(wú)窮是一樣的。——在一些實(shí)用技巧中,它也可以用來(lái)做一
          個(gè)數(shù)組序列的邊界檢測(cè)。

          Infinity在Number對(duì)象中被定義為POSITIVE_INFINITY。此外,負(fù)無(wú)窮也在Number中被定
          義:
          ??Number.POSITIVE_INFINITY??:?比最大正數(shù)(Number.MAX_VALUE)更大的值。正無(wú)窮。
          ??Number.NEGATIVE_INFINITY??:?比最小負(fù)數(shù)(-Number.MAX_VALUE)更小的值。負(fù)無(wú)窮。

          與NaN不同的是,兩個(gè)Infinity(或-Infinity)之間是互等的。如下例:
          //---------------------------------------------------------
          //?Infinity的運(yùn)算與檢測(cè)
          //---------------------------------------------------------
          var
          ??v1?=?Number.MAX_VALUE?*?2;
          ??v2?=?Number.MAX_VALUE?*?3;
          document.writeln(v1);
          document.writeln(v2);
          document.writeln(v1?==?v2);

          在Global中其它與number類型相關(guān)的方法有:
          ?isFinite()???:?如果值是NaN/正無(wú)窮/負(fù)無(wú)窮,返回false,否則返回true。
          ?parseFloat()?:?從字符串(的前綴部分)取一個(gè)浮點(diǎn)數(shù)。不成功則返回NaN。

          ?3).?boolean類型
          ========================
          ?(略)

          ?4).?string類型
          ========================
          JavaScript中的String類型原本沒有什么特殊的,但是JavaScript為了適應(yīng)
          “瀏覽器實(shí)現(xiàn)的超文本環(huán)境”,因此它具有一些奇怪的方法。例如:
          ??link()?:?把一個(gè)有HREF屬性的超鏈接標(biāo)簽<A>放在String對(duì)象中的文本兩端。
          ??big()??:?把一對(duì)<big>標(biāo)簽放在String對(duì)象中的文本兩端。
          以下方法與此類同:
          ??anchor()
          ??blink()
          ??bold()
          ??fixed()
          ??fontcolor()
          ??fontsize()
          ??italics()
          ??small()
          ??strike()
          ??sub()
          ??sup()

          除此之外,string的主要復(fù)雜性來(lái)自于在JavaScript中無(wú)所不在的toString()
          方法。這也是JavaScript為瀏覽器環(huán)境而提供的一個(gè)很重要的方法。例如我們
          聲明一個(gè)對(duì)象,但是要用document.writeln()來(lái)輸出它,在IE中會(huì)顯示什么呢?

          下例說明這個(gè)問題:
          //---------------------------------------------------------
          //?toString()的應(yīng)用
          //---------------------------------------------------------
          var
          ??s?=?new?Object();

          s.v1?=?'hi,';
          s.v2?=?'test!';
          document.writeln(s);
          document.writeln(s.toString());

          s.toString?=?function()?{
          ??return?s.v1?+?s.v2;
          }
          document.writeln(s);

          在這個(gè)例子中,我們看到,當(dāng)一個(gè)對(duì)象沒有重新聲明(覆蓋)自己toString()方
          法的時(shí)候,那么它作為字符串型態(tài)使用時(shí)(例如被writeln),就會(huì)調(diào)用Java?Script
          環(huán)境缺省的toString()。反過來(lái),你也可以重新定義JavaScript理解這個(gè)對(duì)象
          的方法。

          很多JavaScript框架,在實(shí)現(xiàn)“模板”機(jī)制的時(shí)候,就利用了這個(gè)特性。例如
          他們用這樣定義一個(gè)FontElement對(duì)象:
          //---------------------------------------------------------
          //?利用toString()實(shí)現(xiàn)模板機(jī)制的簡(jiǎn)單原理
          //---------------------------------------------------------
          function?FontElement(innerHTML)?{
          ??this.face?=?'宋體';
          ??this.color?=?'red';
          ??//?more...

          ??var?ctx?=?innerHTML;
          ??this.toString?=?function()?{
          ????return?'<Font?FACE="'?+?this.face?+?'"?COLOR="'?+?this.color?+?'">'
          ??????+?ctx
          ??????+?'</FONT>';
          ??}
          }

          var?obj?=?new?FontElement('這是一個(gè)測(cè)試。');

          //?留意下面這行代碼的寫法
          document.writeln(obj);

          ?5).?function類型
          ========================
          javascript函數(shù)具有很多特性,除了面向?qū)ο蟮牟糠种?這在后面講述),它自
          已的一些獨(dú)特特性應(yīng)用也很廣泛。

          首先javascript中的每個(gè)函數(shù),在調(diào)用過程中可以執(zhí)有一個(gè)arguments對(duì)象。這個(gè)
          對(duì)象是由腳本解釋環(huán)境創(chuàng)建的,你沒有別的方法來(lái)自己創(chuàng)建一個(gè)arguments對(duì)象。

          arguments可以看成一個(gè)數(shù)組:它有l(wèi)ength屬性,并可以通過arguments[n]的方式
          來(lái)訪問每一個(gè)參數(shù)。然而它最重要的,卻是可以通過?callee?屬性來(lái)得到正在執(zhí)行
          的函數(shù)對(duì)象的引用。

          接下的問題變得很有趣:Function對(duì)象有一個(gè)?caller?屬性,指向正在調(diào)用當(dāng)前
          函數(shù)的父函數(shù)對(duì)象的引用。

          ——我們已經(jīng)看到,我們可以在JavaScript里面,通過callee/caller來(lái)遍歷執(zhí)行
          期的調(diào)用棧。由于arguments事實(shí)上也是Function的一個(gè)屬性,因此我們事實(shí)上也
          能遍歷執(zhí)行期調(diào)用棧上的每一個(gè)函數(shù)的參數(shù)。下面的代碼是一個(gè)簡(jiǎn)單的示例:

          //---------------------------------------------------------
          //?調(diào)用棧的遍歷
          //---------------------------------------------------------
          function?foo1(v1,?v2)?{
          ??foo2(v1?*?100);
          }

          function?foo2(v1)?{
          ??foo3(v1?*?200);
          }

          function?foo3(v1)?{
          ??var?foo?=?arguments.callee;
          ??while?(foo?&&?(foo?!=?window))?{
          ????document.writeln('調(diào)用參數(shù):<br>',?'---------------<br>');

          ????var?args?=?foo.arguments,?argn?=?args.length;
          ????for?(var?i=0;?i<argn;?i++)?{
          ??????document.writeln('args[',?i,?']:?',?args[i],?'<br>');
          ????}
          ????document.writeln('<br>');

          ????//?上一級(jí)
          ????foo?=?foo.caller;
          ??}
          }

          //?運(yùn)行測(cè)試
          foo1(1,?2);

          2.?JavaScript面向?qū)ο蟮闹С?br />--------
          在前面的例子中其實(shí)已經(jīng)講到了object類型的“類型聲明”與“實(shí)例創(chuàng)建”。
          在JavaScript中,我們需要通過一個(gè)函數(shù)來(lái)聲明自己的object類型:
          //---------------------------------------------------------
          //?JavaScript中對(duì)象的類型聲明的形式代碼
          //?(以后的文檔中,“對(duì)象名”通常用MyObject來(lái)替代)
          //---------------------------------------------------------
          function?對(duì)象名(參數(shù)表)?{
          ??this.屬性?=?初始值;

          ??this.方法?=?function(方法參數(shù)表)?{
          ????//?方法實(shí)現(xiàn)代碼
          ??}
          }

          然后,我們可以通過這樣的代碼來(lái)創(chuàng)建這個(gè)對(duì)象類型的一個(gè)實(shí)例:
          //---------------------------------------------------------
          //?創(chuàng)建實(shí)例的形式代碼
          //?(以后的文檔中,“實(shí)例變量名”通常用obj來(lái)替代)
          //---------------------------------------------------------
          var?實(shí)例變量名?=?new?對(duì)象名(參數(shù)表);

          接下來(lái)我們來(lái)看“對(duì)象”在JavaScript中的一些具體實(shí)現(xiàn)和奇怪特性。

          ?1).?函數(shù)在JavaScript的面向?qū)ο髾C(jī)制中的五重身份
          ?------
          “對(duì)象名”——如MyObject()——這個(gè)函數(shù)充當(dāng)了以下語(yǔ)言角色:
          ??(1)?普通函數(shù)
          ??(2)?類型聲明
          ??(3)?類型的實(shí)現(xiàn)
          ??(4)?類引用
          ??(5)?對(duì)象的構(gòu)造函數(shù)

          一些程序員(例如Delphi程序員)習(xí)慣于類型聲明與實(shí)現(xiàn)分開。例如在delphi
          中,Interface節(jié)用于聲明類型或者變量,而implementation節(jié)用于書寫類型
          的實(shí)現(xiàn)代碼,或者一些用于執(zhí)行的函數(shù)、代碼流程。

          但在JavaScript中,類型的聲明與實(shí)現(xiàn)是混在一起的。一個(gè)對(duì)象的類型(類)
          通過函數(shù)來(lái)聲明,this.xxxx表明了該對(duì)象可具有的屬性或者方法。

          這個(gè)函數(shù)的同時(shí)也是“類引用”。在JavaScript,如果你需要識(shí)別一個(gè)對(duì)象
          的具體型別,你需要執(zhí)有一個(gè)“類引用”。——當(dāng)然,也就是這個(gè)函數(shù)的名
          字。instanceof?運(yùn)算符就用于識(shí)別實(shí)例的類型,我們來(lái)看一下它的應(yīng)用:
          //---------------------------------------------------------
          //?JavaScript中對(duì)象的類型識(shí)別
          //???語(yǔ)法:??對(duì)象實(shí)例?instanceof?類引用
          //---------------------------------------------------------
          function?MyObject()?{
          ??this.data?=?'test?data';
          }

          //?這里MyObject()作為構(gòu)造函數(shù)使用
          var?obj?=?new?MyObject();
          var?arr?=?new?Array();

          //?這里MyObject作為類引用使用
          document.writeln(obj?instanceof?MyObject);
          document.writeln(arr?instanceof?MyObject);

          ================
          (未完待續(xù))
          ================
          接下來(lái)的內(nèi)容:

          2.?JavaScript面向?qū)ο蟮闹С?br />--------

          ?2).?反射機(jī)制在JavaScript中的實(shí)現(xiàn)
          ?3).?this與with關(guān)鍵字的使用
          ?4).?使用in關(guān)鍵字的運(yùn)算
          ?5).?使用instanceof關(guān)鍵字的運(yùn)算
          ?6).?其它與面向?qū)ο笙嚓P(guān)的關(guān)鍵字

          3.?構(gòu)造與析構(gòu)

          4.?實(shí)例和實(shí)例引用

          5.?原型問題

          6.?函數(shù)的上下文環(huán)境

          7.?對(duì)象的類型檢查問題

          2).?反射機(jī)制在JavaScript中的實(shí)現(xiàn)
          ?------
          ??JavaScript中通過for..in語(yǔ)法來(lái)實(shí)現(xiàn)了反射機(jī)制。但是JavaScript中并不
          明確區(qū)分“屬性”與“方法”,以及“事件”。因此,對(duì)屬性的類型考查在JS
          中是個(gè)問題。下面的代碼簡(jiǎn)單示例for..in的使用與屬性識(shí)別:
          //---------------------------------------------------------
          //?JavaScript中for..in的使用和屬性識(shí)別
          //---------------------------------------------------------
          var?_r_event?=?_r_event?=?/^[Oo]n.*/;
          var?colorSetting?=?{
          ??method:?'red',
          ??event:?'blue',
          ??property:?''
          }

          var?obj2?=?{
          ??a_method?:?function()?{},
          ??a_property:?1,
          ??onclick:?undefined
          }

          function?propertyKind(obj,?p)?{
          ??return??(_r_event.test(p)?&&?(obj[p]==undefined?||?typeof(obj[p])=='function'))???'event'
          ????:?(typeof(obj[p])=='function')???'method'
          ????:?'property';
          }

          var?objectArr?=?['window',?'obj2'];

          for?(var?i=0;?i<objectArr.length;?i++)?{
          ??document.writeln('<p>for?',?objectArr[i],?'<hr>');

          ??var?obj?=?eval(objectArr[i]);
          ??for?(var?p?in?obj)?{
          ????var?kind?=?propertyKind(obj,?p);
          ????document.writeln('obj.',?p,?'?is?a?',?kind.fontcolor(colorSetting[kind]),?':?',?obj[p],?'<br>');
          ??}

          ??document.writeln('</p>');
          }

          一個(gè)常常被開發(fā)者忽略的事實(shí)是:JavaScript本身是沒有事件(Event)系統(tǒng)的。通
          常我們?cè)贘avaScript用到的onclick等事件,其實(shí)是IE的DOM模型提供的。從更內(nèi)核
          的角度上講:IE通過COM的接口屬性公布了一組事件接口給DOM。

          有兩個(gè)原因,使得在JS中不能很好的識(shí)別“一個(gè)屬性是不是事件”:
          ??-?COM接口中本身只有方法,屬性與事件,都是通過一組get/set方法來(lái)公布的。
          ??-?JavaScript中,本身并沒有獨(dú)立的“事件”機(jī)制。

          因此我們看到event的識(shí)別方法,是檢測(cè)屬性名是否是以'on'字符串開頭(以'On'開
          頭的是Qomo的約定)。接下來(lái),由于DOM對(duì)象中的事件是可以不指定處理函數(shù)的,這
          種情況下事件句柄為null值(Qomo采用相同的約定);在另外的一些情況下,用戶可
          能象obj2這樣,定義一個(gè)值為?undefined的事件。因此“事件”的判定條件被處理
          成一個(gè)復(fù)雜的表達(dá)式:
          ???("屬性以on/On開頭"?&&?("值為null/undefined"?||?"類型為function"))

          另外,從上面的這段代碼的運(yùn)行結(jié)果來(lái)看。對(duì)DOM對(duì)象使用for..in,是不能列舉出
          對(duì)象方法來(lái)的。

          最后說明一點(diǎn)。事實(shí)上,在很多語(yǔ)言的實(shí)現(xiàn)中,“事件”都不是“面向?qū)ο蟆钡恼Z(yǔ)
          言特性,而是由具體的編程模型來(lái)提供的。例如Delphi中的事件驅(qū)動(dòng)機(jī)制,是由Win32
          操作系統(tǒng)中的窗口消息機(jī)制來(lái)提供,或者由用戶代碼在Component/Class中主動(dòng)調(diào)用
          事件處理函數(shù)來(lái)實(shí)現(xiàn)。

          “事件”是一個(gè)“如何驅(qū)動(dòng)編程模型”的機(jī)制/問題,而不是語(yǔ)言本身的問題。然
          而以PME(property/method/event)為框架的OOP概念,已經(jīng)深入人心,所以當(dāng)編程語(yǔ)
          言或系統(tǒng)表現(xiàn)出這些特性來(lái)的時(shí)候,就已經(jīng)沒人關(guān)心“event究竟是誰(shuí)實(shí)現(xiàn)”的了。

          ?3).?this與with關(guān)鍵字的使用
          ?------
          ?在JavaScript的對(duì)象系統(tǒng)中,this關(guān)鍵字用在兩種地方:
          ???-?在構(gòu)造器函數(shù)中,指代新創(chuàng)建的對(duì)象實(shí)例
          ???-?在對(duì)象的方法被調(diào)用時(shí),指代調(diào)用該方法的對(duì)象實(shí)例

          ?如果一個(gè)函數(shù)被作為普通函數(shù)(而不是對(duì)象方法)調(diào)用,那么在函數(shù)中的this關(guān)鍵字
          將指向window對(duì)象。與此相同的,如果this關(guān)鍵字不在任何函數(shù)中,那么他也指向
          window對(duì)象。

          ?由于在JavaScript中不明確區(qū)分函數(shù)與方法。因此有些代碼看起來(lái)很奇怪:
          //---------------------------------------------------------
          //?函數(shù)的幾種可能調(diào)用形式
          //---------------------------------------------------------
          function?foo()?{
          ??//?下面的this指代調(diào)用該方法的對(duì)象實(shí)例
          ??if?(this===window)?{
          ????document.write('call?a?function.',?'<BR>');
          ??}
          ??else?{
          ????document.write('call?a?method,?by?object:?',?this.name,?'<BR>');
          ??}
          }

          function?MyObject(name)?{
          ??//?下面的this指代new關(guān)鍵字新創(chuàng)建實(shí)例
          ??this.name?=?name;
          ??this.foo?=?foo;
          }

          var?obj1?=?new?MyObject('obj1');
          var?obj2?=?new?MyObject('obj2');

          //?測(cè)試1:?作為函數(shù)調(diào)用
          foo();

          //?測(cè)試2:?作為對(duì)象方法的調(diào)用
          obj1.foo();
          obj2.foo();

          //?測(cè)試3:?將函數(shù)作為“指定對(duì)象的”方法調(diào)用
          foo.call(obj1);
          foo.apply(obj2);

          在上面的代碼里,obj1/obj2對(duì)foo()的調(diào)用是很普通的調(diào)用方法。——也就
          是在構(gòu)造器上,將一個(gè)函數(shù)指定為對(duì)象的方法。

          而測(cè)試3中的call()與apply()就比較特殊。

          在這個(gè)測(cè)試中,foo()仍然作為普通函數(shù)來(lái)調(diào)用,只是JavaScript的語(yǔ)言特性
          允許在call()/apply()時(shí),傳入一個(gè)對(duì)象實(shí)例來(lái)指定foo()的上下文環(huán)境中所
          出現(xiàn)的this關(guān)鍵字的引用。——需要注意的是,此時(shí)的foo()仍舊是一個(gè)普通
          函數(shù)調(diào)用,而不是對(duì)象方法調(diào)用。

          與this“指示調(diào)用該方法的對(duì)象實(shí)例”有些類同的,with()語(yǔ)法也用于限定
          “在一段代碼片段中默認(rèn)使用對(duì)象實(shí)例”。——如果不使用with()語(yǔ)法,那
          么這段代碼將受到更外層with()語(yǔ)句的影響;如果沒有更外層的with(),那
          么這段代碼的“默認(rèn)使用的對(duì)象實(shí)例”將是window。

          然而需要注意的是this與with關(guān)鍵字不是互為影響的。如下面的代碼:
          //---------------------------------------------------------
          //?測(cè)試:?this與with關(guān)鍵字不是互為影響的
          //---------------------------------------------------------
          function?test()?{
          ??with?(obj2)?{
          ????this.value?=?8;
          ??}
          }
          var?obj2?=?new?Object();
          obj2.value?=?10;

          test();
          document.writeln('obj2.value:?',?obj2.value,?'<br>');
          document.writeln('window.value:?',?window.value,?'<br>');

          你不能指望這樣的代碼在調(diào)用結(jié)束后,會(huì)使obj2.value屬性置值為8。這幾行
          代碼的結(jié)果是:window對(duì)象多了一個(gè)value屬性,并且值為8。

          with(obj){...}這個(gè)語(yǔ)法,只能限定對(duì)obj的既有屬性的讀取,而不能主動(dòng)的
          聲明它。一旦with()里的對(duì)象沒有指定的屬性,或者with()限定了一個(gè)不是對(duì)
          象的數(shù)據(jù),那么結(jié)果會(huì)產(chǎn)生一個(gè)異常。

          ?4).?使用in關(guān)鍵字的運(yùn)算
          ?------
          ?除了用for..in來(lái)反射對(duì)象的成員信息之外,JavaScript中也允許直接用in
          關(guān)鍵字去檢測(cè)對(duì)象是否有指定名字的屬性。

          ?in關(guān)鍵字經(jīng)常被提及的原因并不是它檢測(cè)屬性是否存在的能力,因此在早期
          的代碼中,很多可喜歡用“if?(!obj.propName)?{}”?這樣的方式來(lái)檢測(cè)propName
          是否是有效的屬性。——很多時(shí)候,檢測(cè)有效性比檢測(cè)“是否存有該屬性”更
          有實(shí)用性。因此這種情況下,in只是一個(gè)可選的、官方的方案。

          ?in關(guān)鍵字的重要應(yīng)用是高速字符串檢索。尤其是在只需要判定“字符串是否
          存在”的情況下。例如10萬(wàn)個(gè)字符串,如果存儲(chǔ)在數(shù)組中,那么檢索效率將會(huì)
          極差。
          //---------------------------------------------------------
          //?使用對(duì)象來(lái)檢索
          //---------------------------------------------------------
          function?arrayToObject(arr)?{
          ??for?(var?obj=new?Object(),?i=0,?imax=arr.length;?i<imax;?i++)?{
          ????obj[arr[i]]=null;
          ??}
          ??return?obj;
          }

          var
          ??arr?=?['abc',?'def',?'ghi'];?//?more?and?more...
          ??obj?=?arrayToObject(arr);

          function?valueInArray(v)?{
          ??for?(var?i=0,?imax=arr.length;?i<imax;?i++)?{
          ????if?(arr[i]==v)?return?true;
          ??}

          ??return?false;
          }

          function?valueInObject(v)?{
          ??return?v?in?obj;
          }

          這種使用關(guān)鍵字in的方法,也存在一些限制。例如只能查找字符串,而數(shù)
          組元素可以是任意值。另外,arrayToObject()也存在一些開銷,這使得它
          不適合于頻繁變動(dòng)的查找集。最后,(我想你可能已經(jīng)注意到了)使用對(duì)象
          來(lái)查找的時(shí)候并不能準(zhǔn)確定位到查找數(shù)據(jù),而數(shù)組中可以指向結(jié)果的下標(biāo)。

          八、JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~
          (續(xù))

          2.?JavaScript面向?qū)ο蟮闹С?br />--------
          (續(xù))

          ?5).?使用instanceof關(guān)鍵字的運(yùn)算
          ?------
          ?在JavaScript中提供了instanceof關(guān)鍵字來(lái)檢測(cè)實(shí)例的類型。這在前面討
          論它的“五重身份”時(shí)已經(jīng)講過。但instanceof的問題是,它總是列舉整個(gè)
          原型鏈以檢測(cè)類型(關(guān)于原型繼承的原理在“構(gòu)造與析構(gòu)”小節(jié)講述),如:
          //---------------------------------------------------------
          //?instanceof使用中的問題
          //---------------------------------------------------------
          function?MyObject()?{
          ??//?...
          }

          function?MyObject2()?{
          ??//?...
          }
          MyObject2.prototype?=?new?MyObject();

          obj1?=?new?MyObject();
          obj2?=?new?MyObject2();

          document.writeln(obj1?instanceof?MyObject,?'<BR>');
          document.writeln(obj2?instanceof?MyObject,?'<BR>');

          我們看到,obj1與obj2都是MyObject的實(shí)例,但他們是不同的構(gòu)造函數(shù)產(chǎn)生
          的。——注意,這在面向?qū)ο罄碚撝姓_的:因?yàn)閛bj2是MyObject的子類實(shí)
          例,因此它具有與obj1相同的特性。在應(yīng)用中這是obj2的多態(tài)性的體現(xiàn)之一。

          但是,即便如此,我們也必須面臨這樣的問題:如何知道obj2與obj1是否是
          相同類型的實(shí)例呢?——也就是說,連構(gòu)造器都相同?

          instanceof關(guān)鍵字不提供這樣的機(jī)制。一個(gè)提供實(shí)現(xiàn)這種檢測(cè)的能力的,是
          Object.constructor屬性。——但請(qǐng)先記住,它的使用遠(yuǎn)比你想象的要難。

          好的,問題先到這里。constructor屬性已經(jīng)涉及到“構(gòu)造與析構(gòu)”的問題,
          這個(gè)我們后面再講。“原型繼承”、“構(gòu)造與析構(gòu)”是JavaScript的OOP中
          的主要問題、核心問題,以及“致命問題”。

          ?6).?null與undefined
          ?------
          ?在JavaScript中,null與undefined曾一度使我迷惑。下面的文字,有利于
          你更清晰的認(rèn)知它(或者讓你更迷惑):
          ???-?null是關(guān)鍵字;undefined是Global對(duì)象的一個(gè)屬性。
          ???-?null是對(duì)象(空對(duì)象,?沒有任何屬性和方法);undefined是undefined類
          ?????型的值。試試下面的代碼:
          ???????document.writeln(typeof?null);
          ???????document.writeln(typeof?undefined);
          ???-?對(duì)象模型中,所有的對(duì)象都是Object或其子類的實(shí)例,但null對(duì)象例外:
          ???????document.writeln(null?instanceof?Object);
          ???-?null“等值(==)”于undefined,但不“全等值(===)”于undefined:
          ???????document.writeln(null?==?undefined);
          ???????document.writeln(null?==?undefined);
          ???-?運(yùn)算時(shí)null與undefined都可以被類型轉(zhuǎn)換為false,但不等值于false:
          ???????document.writeln(!null,?!undefined);
          ???????document.writeln(null==false);
          ???????document.writeln(undefined==false);

          八、JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~
          (續(xù))

          3.?構(gòu)造、析構(gòu)與原型問題
          --------
          ?我們已經(jīng)知道一個(gè)對(duì)象是需要通過構(gòu)造器函數(shù)來(lái)產(chǎn)生的。我們先記住幾點(diǎn):
          ???-?構(gòu)造器是一個(gè)普通的函數(shù)
          ???-?原型是一個(gè)對(duì)象實(shí)例
          ???-?構(gòu)造器有原型屬性,對(duì)象實(shí)例沒有
          ???-?(如果正常地實(shí)現(xiàn)繼承模型,)對(duì)象實(shí)例的constructor屬性指向構(gòu)造器
          ???-?從三、四條推出:obj.constructor.prototype指向該對(duì)象的原型

          ?好,我們接下來(lái)分析一個(gè)例子,來(lái)說明JavaScript的“繼承原型”聲明,以
          及構(gòu)造過程。
          //---------------------------------------------------------
          //?理解原型、構(gòu)造、繼承的示例
          //---------------------------------------------------------
          function?MyObject()?{
          ??this.v1?=?'abc';
          }

          function?MyObject2()?{
          ??this.v2?=?'def';
          }
          MyObject2.prototype?=?new?MyObject();

          var?obj1?=?new?MyObject();
          var?obj2?=?new?MyObject2();

          ?1).?new()關(guān)鍵字的形式化代碼
          ?------
          ?我們先來(lái)看“obj1?=?new?MyObject()”這行代碼中的這個(gè)new關(guān)鍵字。

          new關(guān)鍵字用于產(chǎn)生一個(gè)新的實(shí)例(說到這里補(bǔ)充一下,我習(xí)慣于把保留字叫關(guān)鍵
          字。另外,在JavaScript中new關(guān)鍵字同時(shí)也是一個(gè)運(yùn)算符),這個(gè)實(shí)例的缺省屬性
          中,(至少)會(huì)執(zhí)有構(gòu)造器函數(shù)的原型屬性(prototype)的一個(gè)引用(在ECMA?Javascript
          規(guī)范中,對(duì)象的這個(gè)屬性名定義為__proto__)。

          每一個(gè)函數(shù),無(wú)論它是否用作構(gòu)造器,都會(huì)有一個(gè)獨(dú)一無(wú)二的原型對(duì)象(prototype)。
          對(duì)于JavaScript“內(nèi)置對(duì)象的構(gòu)造器”來(lái)說,它指向內(nèi)部的一個(gè)原型。缺省時(shí)JavaScript
          構(gòu)造出一個(gè)“空的初始對(duì)象實(shí)例(不是null)”并使原型引用指向它。然而如果你給函
          數(shù)的這個(gè)prototype賦一個(gè)新的對(duì)象,那么新的對(duì)象實(shí)例將執(zhí)有它的一個(gè)引用。

          接下來(lái),構(gòu)造過程將調(diào)用MyObject()來(lái)完成初始化。——注意,這里只是“初始
          化”。

          為了清楚地解釋這個(gè)過程,我用代碼形式化地描述一下這個(gè)過程:
          //---------------------------------------------------------
          //?new()關(guān)鍵字的形式化代碼
          //---------------------------------------------------------
          function?new(aFunction)?{
          ??//?基本對(duì)象實(shí)例
          ??var?_this?=?{};

          ??//?原型引用
          ??var?_proto=?aFunction.prototype;

          /*?if?compat?ECMA?Script
          ??_this.__proto__?=?_proto;
          */

          ??//?為存取原型中的屬性添加(內(nèi)部的)getter
          ??_this._js_GetAttributes=?function(name)?{
          ????if?(_existAttribute.call(this,?name))
          ??????return?this[name]
          ????else?if?(_js_LookupProperty.call(_proto,?name))
          ??????retrun?OBJ_GET_ATTRIBUTES.call(_proto,?name)
          ????else
          ??????return?undefined;
          ??}

          ??//?為存取原型中的屬性添加(內(nèi)部的)setter
          ??_this._js_GetAttributes?=?function(name,?value)?{
          ????if?(_existAttribute.call(this,?name))?
          ??????this[name]?=?value
          ????else?if?(OBJ_GET_ATTRIBUTES.call(_proto,?name)?!==?value)?{
          ??????this[name]?=?value????//?創(chuàng)建當(dāng)前實(shí)例的新成員
          ????}
          ??}

          ??//?調(diào)用構(gòu)造函數(shù)完成初始化,?(如果有,)傳入args
          ??aFunction.call(_this);

          ??//?返回對(duì)象
          ??return?_this;
          }

          所以我們看到以下兩點(diǎn):
          ??-?構(gòu)造函數(shù)(aFunction)本身只是對(duì)傳入的this實(shí)例做“初始化”處理,而
          ????不是構(gòu)造一個(gè)對(duì)象實(shí)例。
          ??-?構(gòu)造的過程實(shí)際發(fā)生在new()關(guān)鍵字/運(yùn)算符的內(nèi)部。

          而且,構(gòu)造函數(shù)(aFunction)本身并不需要操作prototype,也不需要回傳this。

          ?2).?由用戶代碼維護(hù)的原型(prototype)鏈
          ?------
          ?接下來(lái)我們更深入的討論原型鏈與構(gòu)造過程的問題。這就是:
          ??-?原型鏈?zhǔn)怯脩舸a創(chuàng)建的,new()關(guān)鍵字并不協(xié)助維護(hù)原型鏈

          以Delphi代碼為例,我們?cè)诼暶骼^承關(guān)系的時(shí)候,可以用這樣的代碼:
          //---------------------------------------------------------
          //?delphi中使用的“類”類型聲明
          //---------------------------------------------------------
          type
          ??TAnimal?=?class(TObject);?//?動(dòng)物
          ??TMammal?=?class(TAnimal);?//?哺乳動(dòng)物
          ??TCanine?=?class(TMammal);?//?犬科的哺乳動(dòng)物
          ??TDog?=?class(TCanine);????//?狗

          這時(shí),Delphi的編譯器會(huì)通過編譯技術(shù)來(lái)維護(hù)一個(gè)繼承關(guān)系鏈表。我們可以通
          過類似以下的代碼來(lái)查詢這個(gè)鏈表:
          //---------------------------------------------------------
          //?delphi中使用繼關(guān)系鏈表的關(guān)鍵代碼
          //---------------------------------------------------------
          function?isAnimal(obj:?TObject):?boolean;
          begin
          ??Result?:=?obj?is?TAnimal;
          end;

          var
          ??dog?:=?TDog;

          //?...
          dog?:=?TDog.Create();
          writeln(isAnimal(dog));

          可以看到,在Delphi的用戶代碼中,不需要直接繼護(hù)繼承關(guān)系的鏈表。這是因
          為Delphi是強(qiáng)類型語(yǔ)言,在處理用class()關(guān)鍵字聲明類型時(shí),delphi的編譯器
          已經(jīng)為用戶構(gòu)造了這個(gè)繼承關(guān)系鏈。——注意,這個(gè)過程是聲明,而不是執(zhí)行
          代碼。

          而在JavaScript中,如果需要獲知對(duì)象“是否是某個(gè)基類的子類對(duì)象”,那么
          你需要手工的來(lái)維護(hù)(與delphi這個(gè)例子類似的)一個(gè)鏈表。當(dāng)然,這個(gè)鏈表不
          叫類型繼承樹,而叫“(對(duì)象的)原型鏈表”。——在JS中,沒有“類”類型。

          參考前面的JS和Delphi代碼,一個(gè)類同的例子是這樣:
          //---------------------------------------------------------
          //?JS中“原型鏈表”的關(guān)鍵代碼
          //---------------------------------------------------------
          //?1.?構(gòu)造器
          function?Animal()?{};
          function?Mammal()?{};
          function?Canine()?{};
          function?Dog()?{};

          //?2.?原型鏈表
          Mammal.prototype?=?new?Animal();
          Canine.prototype?=?new?Mammal();
          Dog.prototype?=?new?Canine();

          //?3.?示例函數(shù)
          function?isAnimal(obj)?{
          ??return?obj?instanceof?Animal;
          }

          var
          ??dog?=?new?Dog();
          document.writeln(isAnimal(dog));

          可以看到,在JS的用戶代碼中,“原型鏈表”的構(gòu)建方法是一行代碼:
          ??"當(dāng)前類的構(gòu)造器函數(shù)".prototype?=?"直接父類的實(shí)例"

          這與Delphi一類的語(yǔ)言不同:維護(hù)原型鏈的實(shí)質(zhì)是在執(zhí)行代碼,而非聲明。

          那么,“是執(zhí)行而非聲明”到底有什么意義呢?

          JavaScript是會(huì)有編譯過程的。這個(gè)過程主要處理的是“語(yǔ)法檢錯(cuò)”、“語(yǔ)
          法聲明”和“條件編譯指令”。而這里的“語(yǔ)法聲明”,主要處理的就是函
          數(shù)聲明。——這也是我說“函數(shù)是第一類的,而對(duì)象不是”的一個(gè)原因。

          如下例:
          //---------------------------------------------------------
          //?函數(shù)聲明與執(zhí)行語(yǔ)句的關(guān)系(firefox?兼容)
          //---------------------------------------------------------
          //?1.?輸出1234
          testFoo(1234);

          //?2.?嘗試輸出obj1
          //?3.?嘗試輸出obj2
          testFoo(obj1);
          try?{
          ??testFoo(obj2);
          }
          catch(e)?{
          ??document.writeln('Exception:?',?e.description,?'<BR>');
          }

          //?聲明testFoo()
          function?testFoo(v)?{
          ??document.writeln(v,?'<BR>');
          }

          //??聲明object
          var?obj1?=?{};
          obj2?=?{
          ??toString:?function()?{return?'hi,?object.'}
          }

          //?4.?輸出obj1
          //?5.?輸出obj2
          testFoo(obj1);
          testFoo(obj2);

          這個(gè)示例代碼在JS環(huán)境中執(zhí)行的結(jié)果是:
          ------------------------------------
          ??1234
          ??undefined
          ??Exception:?'obj2'?未定義
          ??[object?Object]
          ??hi,?obj
          ------------------------------------
          問題是,testFoo()是在它被聲明之前被執(zhí)行的;而同樣用“直接聲明”的
          形式定義的object變量,卻不能在聲明之前引用。——例子中,第二、三
          個(gè)輸入是不正確的。

          函數(shù)可以在聲明之前引用,而其它類型的數(shù)值必須在聲明之后才能被使用。
          這說明“聲明”與“執(zhí)行期引用”在JavaScript中是兩個(gè)過程。

          另外我們也可以發(fā)現(xiàn),使用"var"來(lái)聲明的時(shí)候,編譯器會(huì)先確認(rèn)有該變量
          存在,但變量的值會(huì)是“undefined”。——因此“testFoo(obj1)”不會(huì)發(fā)
          生異常。但是,只有等到關(guān)于obj1的賦值語(yǔ)句被執(zhí)行過,才會(huì)有正常的輸出。
          請(qǐng)對(duì)照第二、三與第四、五行輸出的差異。

          由于JavaScript對(duì)原型鏈的維護(hù)是“執(zhí)行”而不是“聲明”,這說明“原型
          鏈?zhǔn)怯捎脩舸a來(lái)維護(hù)的,而不是編譯器維護(hù)的。

          由這個(gè)推論,我們來(lái)看下面這個(gè)例子:
          //---------------------------------------------------------
          //?示例:錯(cuò)誤的原型鏈
          //---------------------------------------------------------
          //?1.?構(gòu)造器
          function?Animal()?{};?//?動(dòng)物
          function?Mammal()?{};?//?哺乳動(dòng)物
          function?Canine()?{};?//?犬科的哺乳動(dòng)物

          //?2.?構(gòu)造原型鏈
          var?instance?=?new?Mammal();
          Mammal.prototype?=?new?Animal();
          Canine.prototype?=?instance;

          //?3.?測(cè)試輸出
          var?obj?=?new?Canine();
          document.writeln(obj?instanceof?Animal);

          這個(gè)輸出結(jié)果,使我們看到一個(gè)錯(cuò)誤的原型鏈導(dǎo)致的結(jié)果“犬科的哺乳動(dòng)
          物‘不是’一種動(dòng)物”。

          根源在于“2.?構(gòu)造原型鏈”下面的幾行代碼是解釋執(zhí)行的,而不是象var和
          function那樣是“聲明”并在編譯期被理解的。解決問題的方法是修改那三
          行代碼,使得它的“執(zhí)行過程”符合邏輯:
          //---------------------------------------------------------
          //?上例的修正代碼(部分)
          //---------------------------------------------------------
          //?2.?構(gòu)造原型鏈
          Mammal.prototype?=?new?Animal();
          var?instance?=?new?Mammal();
          Canine.prototype?=?instance;

          ?3).?原型實(shí)例是如何被構(gòu)造過程使用的
          ?------
          ?仍以Delphi為例。構(gòu)造過程中,delphi中會(huì)首先創(chuàng)建一個(gè)指定實(shí)例大小的
          “空的對(duì)象”,然后逐一給屬性賦值,以及調(diào)用構(gòu)造過程中的方法、觸發(fā)事
          件等。

          JavaScript中的new()關(guān)鍵字中隱含的構(gòu)造過程,與Delphi的構(gòu)造過程并不完全一致。但
          在構(gòu)造器函數(shù)中發(fā)生的行為卻與上述的類似:
          //---------------------------------------------------------
          //?JS中的構(gòu)造過程(形式代碼)
          //---------------------------------------------------------
          function?MyObject2()?{
          ??this.prop?=?3;
          ??this.method?=?a_method_function;

          ??if?(you_want)?{
          ????this.method();
          ????this.fire_OnCreate();
          ??}
          }
          MyObject2.prototype?=?new?MyObject();?//?MyObject()的聲明略

          var?obj?=?new?MyObject2();

          如果以單個(gè)類為參考對(duì)象的,這個(gè)構(gòu)造過程中JavaScript可以擁有與Delphi
          一樣豐富的行為。然而,由于Delphi中的構(gòu)造過程是“動(dòng)態(tài)的”,因此事實(shí)上
          Delphi還會(huì)調(diào)用父類(MyObject)的構(gòu)造過程,以及觸發(fā)父類的OnCreate()事件。

          JavaScript沒有這樣的特性。父類的構(gòu)造過程僅僅發(fā)生在為原型(prototype
          屬性)賦值的那一行代碼上。其后,無(wú)論有多少個(gè)new?MyObject2()發(fā)生,
          MyObject()這個(gè)構(gòu)造器都不會(huì)被使用。——這也意味著:
          ??-?構(gòu)造過程中,原型對(duì)象是一次性生成的;新對(duì)象只持有這個(gè)原型實(shí)例的引用
          ????(并用“寫復(fù)制”的機(jī)制來(lái)存取其屬性),而并不再調(diào)用原型的構(gòu)造器。

          由于不再調(diào)用父類的構(gòu)造器,因此Delphi中的一些特性無(wú)法在JavaScript中實(shí)現(xiàn)。
          這主要影響到構(gòu)造階段的一些事件和行為。——無(wú)法把一些“對(duì)象構(gòu)造過程中”
          的代碼寫到父類的構(gòu)造器中。因?yàn)闊o(wú)論子類構(gòu)造多少次,這次對(duì)象的構(gòu)造過程根
          本不會(huì)激活父類構(gòu)造器中的代碼。

          JavaScript中屬性的存取是動(dòng)態(tài)的,因?yàn)閷?duì)象存取父類屬性依賴于原型鏈表,構(gòu)造
          過程卻是靜態(tài)的,并不訪問父類的構(gòu)造器;而在Delphi等一些編譯型語(yǔ)言中,(不使
          用讀寫器的)屬性的存取是靜態(tài)的,而對(duì)象的構(gòu)造過程則動(dòng)態(tài)地調(diào)用父類的構(gòu)造函數(shù)。
          所以再一次請(qǐng)大家看清楚new()關(guān)鍵字的形式代碼中的這一行:
          //---------------------------------------------------------
          //?new()關(guān)鍵字的形式化代碼
          //---------------------------------------------------------
          function?new(aFunction)?{
          ??//?原型引用
          ??var?_proto=?aFunction.prototype;

          ??//?...
          }

          這個(gè)過程中,JavaScript做的是“get?a?prototype_Ref”,而Delphi等其它語(yǔ)言做
          的是“Inherited?Create()”。

          八、JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~
          (續(xù))

          ?4).?需要用戶維護(hù)的另一個(gè)屬性:constructor
          ?------
          ?回顧前面的內(nèi)容,我們提到過:
          ???-?(如果正常地實(shí)現(xiàn)繼承模型,)對(duì)象實(shí)例的constructor屬性指向構(gòu)造器
          ???-?obj.constructor.prototype指向該對(duì)象的原型
          ???-?通過Object.constructor屬性,可以檢測(cè)obj2與obj1是否是相同類型的實(shí)例

          ??與原型鏈要通過用戶代碼來(lái)維護(hù)prototype屬性一樣,實(shí)例的構(gòu)造器屬性constructor
          也需要用戶代碼維護(hù)。

          ??對(duì)于JavaScript的內(nèi)置對(duì)象來(lái)說,constructor屬性指向內(nèi)置的構(gòu)造器函數(shù)。如:
          //---------------------------------------------------------
          //?內(nèi)置對(duì)象實(shí)例的constructor屬性
          //---------------------------------------------------------
          var?_object_types?=?{
          ??'function'??:?Function,
          ??'boolean'???:?Boolean,
          ??'regexp'????:?RegExp,
          //?'math'?????:?Math,
          //?'debug'????:?Debug,
          //?'image'????:?Image;
          //?'undef'????:?undefined,
          //?'dom'??????:?undefined,
          //?'activex'??:?undefined,
          ??'vbarray'???:?VBArray,
          ??'array'?????:?Array,
          ??'string'????:?String,
          ??'date'??????:?Date,
          ??'error'?????:?Error,
          ??'enumerator':?Enumerator,
          ??'number'????:?Number,
          ??'object'????:?Object
          }

          function?objectTypes(obj)?{
          ??if?(typeof?obj?!==?'object')?return?typeof?obj;
          ??if?(obj?===?null)?return?'null';

          ??for?(var?i?in?_object_types)?{
          ????if?(obj.constructor===_object_types[i])?return?i;
          ??}
          ??return?'unknow';
          }

          //?測(cè)試數(shù)據(jù)和相關(guān)代碼
          function?MyObject()?{
          }
          function?MyObject2()?{
          }
          MyObject2.prototype?=?new?MyObject();

          window.execScript(''+
          'Function?CreateVBArray()'?+
          '??Dim?a(2,?2)'?+
          '??CreateVBArray?=?a'?+
          'End?Function',?'VBScript');

          document.writeln('<div?id=dom?style="display:none">dom<',?'/div>');

          //?測(cè)試代碼
          var?ax?=?new?ActiveXObject("Microsoft.XMLHTTP");
          var?dom?=?document.getElementById('dom');
          var?vba?=?new?VBArray(CreateVBArray());
          var?obj?=?new?MyObject();
          var?obj2?=?new?MyObject2();

          document.writeln(objectTypes(vba),?'<br>');
          document.writeln(objectTypes(ax),?'<br>');
          document.writeln(objectTypes(obj),?'<br>');
          document.writeln(objectTypes(obj2),?'<br>');
          document.writeln(objectTypes(dom),?'<br>');

          在這個(gè)例子中,我們發(fā)現(xiàn)constructor屬性被實(shí)現(xiàn)得并不完整。對(duì)于DOM對(duì)象、ActiveX對(duì)象
          來(lái)說這個(gè)屬性都沒有正確的返回。

          確切的說,DOM(包括Image)對(duì)象與ActiveX對(duì)象都不是標(biāo)準(zhǔn)JavaScript的對(duì)象體系中的,
          因此它們也可能會(huì)具有自己的constructor屬性,并有著與JavaScript不同的解釋。因此,
          JavaScript中不維護(hù)它們的constructor屬性,是具有一定的合理性的。

          另外的一些單體對(duì)象(而非構(gòu)造器),也不具有constructor屬性,例如“Math”和“Debug”、
          “Global”和“RegExp對(duì)象”。他們是JavaScript內(nèi)部構(gòu)造的,不應(yīng)該公開構(gòu)造的細(xì)節(jié)。

          我們也發(fā)現(xiàn)實(shí)例obj的constructor指向function?MyObject()。這說明JavaScript維護(hù)了對(duì)
          象的constructor屬性。——這與一些人想象的不一樣。

          然而再接下來(lái),我們發(fā)現(xiàn)MyObject2()的實(shí)例obj2的constructor仍然指向function?MyObject()。
          盡管這很說不通,然而現(xiàn)實(shí)的確如此。——這到底是為什么呢?

          事實(shí)上,僅下面的代碼:
          --------
          function?MyObject2()?{
          }

          obj2?=?new?MyObject2();
          document.writeln(MyObject2.prototype.constructor?===?MyObject2);
          --------
          構(gòu)造的obj2.constructor將正確的指向function?MyObject2()。事實(shí)上,我們也會(huì)注意到這
          種情況下,MyObject2的原型屬性的constructor也正確的指向該函數(shù)。然而,由于JavaScript
          要求指定prototype對(duì)象來(lái)構(gòu)造原型鏈:
          --------
          function?MyObject2()?{
          }
          MyObject2.prototype?=?new?MyObject();

          obj2?=?new?MyObject2();
          --------
          這時(shí),再訪問obj2,將會(huì)得到新的原型(也就是MyObject2.prototype)的constructor屬性。
          因此,一切很明了:原型的屬性影響到構(gòu)造過程對(duì)對(duì)象的constructor的初始設(shè)定。

          作為一種補(bǔ)充的解決問題的手段,JavaScript開發(fā)規(guī)范中說“need?to?remember?to?reset
          the?constructor?property',要求用戶自行設(shè)定該屬性。

          所以你會(huì)看到更規(guī)范的JavaScript代碼要求這樣書寫:
          //---------------------------------------------------------
          //?維護(hù)constructor屬性的規(guī)范代碼
          //---------------------------------------------------------
          function?MyObject2()?{
          }
          MyObject2.prototype?=?new?MyObject();
          MyObject2.prototype.constructor?=?MyObject2;

          obj2?=?new?MyObject2();

          更外一種解決問題的方法,是在function?MyObject()中去重置該值。當(dāng)然,這樣會(huì)使
          得執(zhí)行效率稍低一點(diǎn)點(diǎn):
          //---------------------------------------------------------
          //?維護(hù)constructor屬性的第二種方式
          //---------------------------------------------------------
          function?MyObject2()?{
          ??this.constructor?=?arguments.callee;
          ??//?or,?this.constructor?=?MyObject2;

          ??//?...
          }
          MyObject2.prototype?=?new?MyObject();

          obj2?=?new?MyObject2();

          ?5).?析構(gòu)問題
          ?------
          ?JavaScript中沒有析構(gòu)函數(shù),但卻有“對(duì)象析構(gòu)”的問題。也就是說,盡管我們不
          知道一個(gè)對(duì)象什么時(shí)候會(huì)被析構(gòu),也不能截獲它的析構(gòu)過程并處理一些事務(wù)。然而,
          在一些不多見的時(shí)候,我們會(huì)遇到“要求一個(gè)對(duì)象立即析構(gòu)”的問題。

          問題大多數(shù)的時(shí)候出現(xiàn)在對(duì)ActiveX?Object的處理上。因?yàn)槲覀兛赡茉贘avaScript
          里創(chuàng)建了一個(gè)ActiveX?Object,在做完一些處理之后,我們又需要再創(chuàng)建一個(gè)。而
          如果原來(lái)的對(duì)象供應(yīng)者(Server)不允許創(chuàng)建多個(gè)實(shí)例,那么我們就需要在JavaScript
          中確保先前的實(shí)例是已經(jīng)被釋放過了。接下來(lái),即使Server允許創(chuàng)建多個(gè)實(shí)例,而
          在多個(gè)實(shí)例間允許共享數(shù)據(jù)(例如OS的授權(quán),或者資源、文件的鎖),那么我們?cè)谛?br />實(shí)例中的操作就可能會(huì)出問題。

          可能還是有人不明白我們?cè)谡f什么,那么我就舉一個(gè)例子:如果創(chuàng)建一個(gè)Excel對(duì)象,
          打開文件A,然后我們save它,然后關(guān)閉這個(gè)實(shí)例。然后我們?cè)賱?chuàng)建Excel對(duì)象并打開
          同一文件。——注意這時(shí)JavaScript可能還沒有來(lái)得及析構(gòu)前一個(gè)對(duì)象。——這時(shí)我們
          再想Save這個(gè)文件,就發(fā)現(xiàn)失敗了。下面的代碼示例這種情況:
          //---------------------------------------------------------
          //?JavaScript中的析構(gòu)問題(ActiveX?Object示例)
          //---------------------------------------------------------
          <script>
          var?strSaveLocation?=?'file:///E:/1.xls'

          function?createXLS()?{
          ??var?excel?=?new?ActiveXObject("Excel.Application");
          ??var?wk?=?excel.Workbooks.Add();
          ??wk.SaveAs(strSaveLocation);
          ??wk.Saved?=?true;

          ??excel.Quit();
          }

          function?writeXLS()?{
          ??var?excel?=?new?ActiveXObject("Excel.Application");
          ??var?wk?=?excel.Workbooks.Open(strSaveLocation);
          ??var?sheet?=?wk.Worksheets(1);
          ??sheet.Cells(1,?1).Value?=?'測(cè)試字符串';
          ??wk.SaveAs(strSaveLocation);
          ??wk.Saved?=?true;

          ??excel.Quit();
          }
          </script>
          <body>
          ??<button?onclick="createXLS()">創(chuàng)建</button>
          ??<button?onclick="writeXLS()">重寫</button>
          </body>?

          在這個(gè)例子中,在本地文件操作時(shí)并不會(huì)出現(xiàn)異常。——最多只是有一些內(nèi)存垃
          圾而已。然而,如果strSaveLocation是一個(gè)遠(yuǎn)程的URL,這時(shí)本地將會(huì)保存一個(gè)
          文件存取權(quán)限的憑證,而且同時(shí)只能一個(gè)(遠(yuǎn)程的)實(shí)例來(lái)開啟該excel文檔并存
          儲(chǔ)。于是如果反復(fù)點(diǎn)擊"重寫"按鈕,就會(huì)出現(xiàn)異常。

          ——注意,這是在SPS中操作共享文件時(shí)的一個(gè)實(shí)例的簡(jiǎn)化代碼。因此,它并非
          “學(xué)術(shù)的”無(wú)聊討論,而且工程中的實(shí)際問題。

          解決這個(gè)問題的方法很復(fù)雜。它涉及到兩個(gè)問題:
          ??-?本地憑證的釋放
          ??-?ActiveX?Object實(shí)例的釋放

          下面我們先從JavaScript中對(duì)象的“失效”問題說起。簡(jiǎn)單的說:
          ??-?一個(gè)對(duì)象在其生存的上下文環(huán)境之外,即會(huì)失效。
          ??-?一個(gè)全局的對(duì)象在沒有被執(zhí)用(引用)的情況下,即會(huì)失效。

          例如:
          //---------------------------------------------------------
          //?JavaScript對(duì)象何時(shí)失效
          //---------------------------------------------------------
          function?testObject()?{
          ??var?_obj1?=?new?Object();
          }

          function?testObject2()?{
          ??var?_obj2?=?new?Object();
          ??return?_obj2;
          }

          //?示例1
          testObject();

          //?示例2
          testObject2()

          //?示例3
          var?obj3?=?testObject2();
          obj3?=?null;

          //?示例4
          var?obj4?=?testObject2();
          var?arr?=?[obj4];
          obj3?=?null;
          arr?=?[];

          在這四個(gè)示例中:
          ??-?“示例1”在函數(shù)testObject()中構(gòu)造了_obj1,但是在函數(shù)退出時(shí),
          ????它就已經(jīng)離開了函數(shù)的上下文環(huán)境,因此_obj1失效了;
          ??-?“示例2”中,testObject2()中也構(gòu)造了一個(gè)對(duì)象_obj2并傳出,因
          ????此對(duì)象有了“函數(shù)外”的上下文環(huán)境(和生存周期),然而由于函數(shù)
          ????的返回值沒有被其它變量“持有”,因此_obj2也立即失效了;
          ??-?“示例3”中,testObject2()構(gòu)造的_obj2被外部的變量obj3持用了,
          ????這時(shí),直到“obj3=null”這行代碼生效時(shí),_obj2才會(huì)因?yàn)橐藐P(guān)系
          ????消失而失效。
          ??-?與示例3相同的原因,“示例4”中的_obj2會(huì)在“arr=[]”這行代碼
          ????之后才會(huì)失效。

          但是,對(duì)象的“失效”并不等會(huì)“釋放”。在JavaScript運(yùn)行環(huán)境的內(nèi)部,沒
          有任何方式來(lái)確切地告訴用戶“對(duì)象什么時(shí)候會(huì)釋放”。這依賴于JavaScript
          的內(nèi)存回收機(jī)制。——這種策略與.NET中的回收機(jī)制是類同的。

          在前面的Excel操作示例代碼中,對(duì)象的所有者,也就是"EXCEL.EXE"這個(gè)進(jìn)程
          只能在“ActiveX?Object實(shí)例的釋放”之后才會(huì)發(fā)生。而文件的鎖,以及操作
          系統(tǒng)的權(quán)限憑證是與進(jìn)程相關(guān)的。因此如果對(duì)象僅是“失效”而不是“釋放”,
          那么其它進(jìn)程處理文件和引用操作系統(tǒng)的權(quán)限憑據(jù)時(shí)就會(huì)出問題。

          ——有些人說這是JavaScript或者COM機(jī)制的BUG。其實(shí)不是,這是OS、IE
          和JavaScript之間的一種復(fù)雜關(guān)系所導(dǎo)致的,而非獨(dú)立的問題。

          Microsoft公開了解決這種問題的策略:主動(dòng)調(diào)用內(nèi)存回收過程。

          在(微軟的)JScript中提供了一個(gè)CollectGarbage()過程(通常簡(jiǎn)稱GC過程),
          GC過程用于清理當(dāng)前IE中的“失效的對(duì)象失例”,也就是調(diào)用對(duì)象的析構(gòu)過程。

          在上例中調(diào)用GC過程的代碼是:
          //---------------------------------------------------------
          //?處理ActiveX?Object時(shí),GC過程的標(biāo)準(zhǔn)調(diào)用方式
          //---------------------------------------------------------
          function?writeXLS()?{
          ??//(略...)

          ??excel.Quit();
          ??excel?=?null;
          ??setTimeout(CollectGarbage,?1);
          }

          第一行代碼調(diào)用excel.Quit()方法來(lái)使得excel進(jìn)程中止并退出,這時(shí)由于JavaScript
          環(huán)境執(zhí)有excel對(duì)象實(shí)例,因此excel進(jìn)程并不實(shí)際中止。

          第二行代碼使excel為null,以清除對(duì)象引用,從而使對(duì)象“失效”。然而由于
          對(duì)象仍舊在函數(shù)上下文環(huán)境中,因此如果直接調(diào)用GC過程,對(duì)象仍然不會(huì)被清理。

          第三行代碼使用setTimeout()來(lái)調(diào)用CollectGarbage函數(shù),時(shí)間間隔設(shè)為'1',只
          是使得GC過程發(fā)生在writeXLS()函數(shù)執(zhí)行完之后。這樣excel對(duì)象就滿足了“能被
          GC清理”的兩個(gè)條件:沒有引用和離開上下文環(huán)境。

          GC過程的使用,在使用了ActiveX?Object的JS環(huán)境中很有效。一些潛在的ActiveX
          Object包括XML、VML、OWC(Office?Web?Componet)、flash,甚至包括在JS中的VBArray。
          從這一點(diǎn)來(lái)看,ajax架構(gòu)由于采用了XMLHTTP,并且同時(shí)要滿足“不切換頁(yè)面”的
          特性,因此在適當(dāng)?shù)臅r(shí)候主動(dòng)調(diào)用GC過程,會(huì)得到更好的效率用UI體驗(yàn)。

          事實(shí)上,即使使用GC過程,前面提到的excel問題仍然不會(huì)被完全解決。因?yàn)镮E還
          緩存了權(quán)限憑據(jù)。使頁(yè)的權(quán)限憑據(jù)被更新的唯一方法,只能是“切換到新的頁(yè)面”,
          因此事實(shí)上在前面提到的那個(gè)SPS項(xiàng)目中,我采用的方法并不是GC,而是下面這一
          段代碼:
          //---------------------------------------------------------
          //?處理ActiveX?Object時(shí)采用的頁(yè)面切換代碼
          //---------------------------------------------------------
          function?writeXLS()?{
          ??//(略...)

          ??excel.Quit();
          ??excel?=?null;
          ?
          ??//?下面代碼用于解決IE?call?Excel的一個(gè)BUG,?MSDN中提供的方法:
          ??//???setTimeout(CollectGarbage,?1);
          ??//?由于不能清除(或同步)網(wǎng)頁(yè)的受信任狀態(tài),?所以將導(dǎo)致SaveAs()等方法在
          ??//?下次調(diào)用時(shí)無(wú)效.
          ??location.reload();
          }

          最后之最后,關(guān)于GC的一個(gè)補(bǔ)充說明:在IE窗體被最小化時(shí),IE將會(huì)主動(dòng)調(diào)用一次
          CollectGarbage()函數(shù)。這使得IE窗口在最小化之后,內(nèi)存占用會(huì)有明顯改善。

          八、JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~
          (續(xù))

          4.?實(shí)例和實(shí)例引用
          --------
          在.NET?Framework對(duì)CTS(Common?Type?System)約定“一切都是對(duì)象”,并分為“值類型”和“引用類型”兩種。其中“值類型”的對(duì)象在轉(zhuǎn)換成“引用類型”數(shù)據(jù)的過程中,需要進(jìn)行一個(gè)“裝箱”和“拆箱”的過程。

          在JavaScript也有同樣的問題。我們看到的typeof關(guān)鍵字,返回以下六種數(shù)據(jù)類型:
          "number"、"string"、"boolean"、"object"、"function"?和?"undefined"。

          我們也發(fā)現(xiàn)JavaScript的對(duì)象系統(tǒng)中,有String、Number、Function、Boolean這四種對(duì)象構(gòu)造器。那么,我們的問題是:如果有一個(gè)數(shù)字A,typeof(A)的結(jié)果,到底會(huì)是'number'呢,還是一個(gè)構(gòu)造器指向function?Number()的對(duì)象呢?

          //---------------------------------------------------------
          //?關(guān)于JavaScript的類型的測(cè)試代碼
          //---------------------------------------------------------
          function?getTypeInfo(V)?{
          ??return?(typeof?V?==?'object'????'Object,?construct?by?'+V.constructor
          ???:?'Value,?type?of?'+typeof?V);
          }

          var?A1?=?100;
          var?A2?=?new?Number(100);

          document.writeln('A1?is?',?getTypeInfo(A1),?'<BR>');
          document.writeln('A2?is?',?getTypeInfo(A2),?'<BR>');
          document.writeln([A1.constructor?===?A2.constructor,?A2.constructor?===?Number]);

          測(cè)試代碼的執(zhí)行結(jié)果如下:
          -----------
          ?A1?is?Value,?type?of?number
          ?A2?is?Object,?construct?by?function?Number()?{?[native?code]?}?
          ?true,true
          -----------

          我們注意到,A1和A2的構(gòu)造器都指向Number。這意味著通過constructor屬性來(lái)識(shí)別對(duì)象,(有時(shí))比typeof更加有效。因?yàn)椤爸殿愋蛿?shù)據(jù)”A1作為一個(gè)對(duì)象來(lái)看待時(shí),與A2有完全相同的特性。

          ——除了與實(shí)例引用有關(guān)的問題。

          參考JScript手冊(cè),我們對(duì)其它基礎(chǔ)類型和構(gòu)造器做相同考察,可以發(fā)現(xiàn):
          ??-?基礎(chǔ)類型中的undefined、number、boolean和string,是“值類型”變量
          ??-?基礎(chǔ)類型中的array、function和object,是“引用類型”變量
          ??-?使用new()方法構(gòu)造出對(duì)象,是“引用類型”變量

          下面的代碼說明“值類型”與“引用類型”之間的區(qū)別:
          //---------------------------------------------------------
          //?關(guān)于JavaScript類型系統(tǒng)中的值/引用問題
          //---------------------------------------------------------
          var?str1?=?'abcdefgh',?str2?=?'abcdefgh';
          var?obj1?=?new?String('abcdefgh'),?obj2?=?new?String('abcdefgh');

          document.writeln([str1==str2,?str1===str2],?'<br>');
          document.writeln([obj1==obj2,?obj1===obj2]);

          測(cè)試代碼的執(zhí)行結(jié)果如下:
          -----------
          ?true,?true
          ?false,?false
          -----------

          我們看到,無(wú)論是等值運(yùn)算(==),還是全等運(yùn)算(===),對(duì)“對(duì)象”和“值”的理解都是不一樣的。

          更進(jìn)一步的理解這種現(xiàn)象,我們知道:
          ??-?運(yùn)算結(jié)果為值類型,或變量為值類型時(shí),等值(或全等)比較可以得到預(yù)想結(jié)果
          ??-?(即使包含相同的數(shù)據(jù),)不同的對(duì)象實(shí)例之間是不等值(或全等)的
          ??-?同一個(gè)對(duì)象的不同引用之間,是等值(==)且全等(===)的

          但對(duì)于String類型,有一點(diǎn)補(bǔ)充:根據(jù)JScript的描述,兩個(gè)字符串比較時(shí),只要有一個(gè)是值類型,則按值比較。這意味著在上面的例子中,代碼“str1==obj1”會(huì)得到結(jié)果true。而全等(===)運(yùn)算需要檢測(cè)變量類型的一致性,因此“str1===obj1”的結(jié)果返回false。

          JavaScript中的函數(shù)參數(shù)總是傳入值參,引用類型(的實(shí)例)是作為指針值傳入的。因此函數(shù)可以隨意重寫入口變量,而不用擔(dān)心外部變量被修改。但是,需要留意傳入的引用類型的變量,因?yàn)閷?duì)它方法調(diào)用和屬性讀寫可能會(huì)影響到實(shí)例本身。——但,也可以通過引用類型的參數(shù)來(lái)傳出數(shù)據(jù)。

          最后補(bǔ)充說明一下,值類型比較會(huì)逐字節(jié)檢測(cè)對(duì)象實(shí)例中的數(shù)據(jù),效率低但準(zhǔn)確性高;而引用類型只檢測(cè)實(shí)例指針和數(shù)據(jù)類型,因此效率高而準(zhǔn)確性低。如果你需要檢測(cè)兩個(gè)引用類型是否真的包含相同的數(shù)據(jù),可能你需要嘗試把它轉(zhuǎn)換成“字符串值”再來(lái)比較。

          6.?函數(shù)的上下文環(huán)境
          --------
          只要寫過代碼,你應(yīng)該知道變量是有“全局變量”和“局部變量”之分的。絕大多數(shù)的
          JavaScript程序員也知道下面這些概念:
          //---------------------------------------------------------
          //?JavaScript中的全局變量與局部變量
          //---------------------------------------------------------
          var?v1?=?'全局變量-1';
          v2?=?'全局變量-2';

          function?foo()?{
          ??v3?=?'全局變量-3';

          ??var?v4?=?'只有在函數(shù)內(nèi)部并使用var定義的,才是局部變量';
          }

          按照通常對(duì)語(yǔ)言的理解來(lái)說,不同的代碼調(diào)用函數(shù),都會(huì)擁有一套獨(dú)立的局部變量。
          因此下面這段代碼很容易理解:
          //---------------------------------------------------------
          //?JavaScript的局部變量
          //---------------------------------------------------------
          function?MyObject()?{
          ??var?o?=?new?Object;

          ??this.getValue?=?function()?{
          ????return?o;
          ??}
          }

          var?obj1?=?new?MyObject();
          var?obj2?=?new?MyObject();
          document.writeln(obj1.getValue()?==?obj2.getValue());

          結(jié)果顯示false,表明不同(實(shí)例的方法)調(diào)用返回的局部變量“obj1/obj2”是不相同。

          變量的局部、全局特性與OOP的封裝性中的“私有(private)”、“公開(public)”具有類同性。因此絕大多數(shù)資料總是以下面的方式來(lái)說明JavaScript的面向?qū)ο笙到y(tǒng)中的“封裝權(quán)限級(jí)別”問題:
          //---------------------------------------------------------
          //?JavaScript中OOP封裝性
          //---------------------------------------------------------
          function?MyObject()?{
          ??//?1.?私有成員和方法
          ??var?private_prop?=?0;
          ??var?private_method_1?=?function()?{
          ????//?...
          ????return?1
          ??}
          ??function?private_method_2()?{
          ????//?...
          ????return?1
          ??}

          ??//?2.?特權(quán)方法
          ??this.privileged_method?=?function?()?{
          ????private_prop++;
          ????return?private_prop?+?private_method_1()?+?private_method_2();
          ??}

          ??//?3.?公開成員和方法
          ??this.public_prop_1?=?'';
          ??this.public_method_1?=?function?()?{
          ????//?...
          ??}
          }

          //?4.?公開成員和方法(2)
          MyObject.prototype.public_prop_1?=?'';
          MyObject.prototype.public_method_1?=?function?()?{
          ??//?...
          }

          var?obj1?=?new?MyObject();
          var?obj2?=?new?MyObject();

          document.writeln(obj1.privileged_method(),?'<br>');
          document.writeln(obj2.privileged_method());

          在這里,“私有(private)”表明只有在(構(gòu)造)函數(shù)內(nèi)部可訪問,而“特權(quán)(privileged)”是特指一種存取“私有域”的“公開(public)”方法。“公開(public)”表明在(構(gòu)造)函數(shù)外可以調(diào)用和存取。

          除了上述的封裝權(quán)限之外,一些文檔還介紹了其它兩種相關(guān)的概念:
          ??-?原型屬性:Classname.prototype.propertyName?=?someValue
          ??-?(類)靜態(tài)屬性:Classname.propertyName?=?someValue?

          然而,從面向?qū)ο蟮慕嵌壬蟻?lái)講,上面這些概念都很難自圓其說:JavaScript究竟是為何、以及如何劃分出這些封裝權(quán)限和概念來(lái)的呢?

          ——因?yàn)槲覀儽仨氉⒁獾较旅孢@個(gè)例子所帶來(lái)的問題:
          //---------------------------------------------------------
          //?JavaScript中的局部變量
          //---------------------------------------------------------
          function?MyFoo()?{
          ??var?i;

          ??MyFoo.setValue?=?function?(v)?{
          ?????i?=?v;
          ??}
          ??MyFoo.getValue?=?function?()?{
          ?????return?i;
          ??}
          }
          MyFoo();

          var?obj1?=?new?Object();
          var?obj2?=?new?Object();

          //?測(cè)試一
          MyFoo.setValue.call(obj1,?'obj1');
          document.writeln(MyFoo.getValue.call(obj1),?'<BR>');

          //?測(cè)試二
          MyFoo.setValue.call(obj2,?'obj2');
          document.writeln(MyFoo.getValue.call(obj2));
          document.writeln(MyFoo.getValue.call(obj1));
          document.writeln(MyFoo.getValue());

          在這個(gè)測(cè)試代碼中,obj1/obj2都是Object()實(shí)例。我們使用function.call()的方式來(lái)調(diào)用setValue/getValue,使得在MyFoo()調(diào)用的過程中替換this為obj1/obj2實(shí)例。

          然而我們發(fā)現(xiàn)“測(cè)試二”完成之后,obj2、obj1以及function?MyFoo()所持有的局部變量都返回了“obj2”。——這表明三個(gè)函數(shù)使用了同一個(gè)局部變量。

          由此可見,JavaScript在處理局部變量時(shí),對(duì)“普通函數(shù)”與“構(gòu)造器”是分別對(duì)待的。這種處理策略在一些JavaScript相關(guān)的資料中被解釋作“面向?qū)ο笾械乃接杏颉眴栴}。而事實(shí)上,我更愿意從源代碼一級(jí)來(lái)告訴你真相:這是對(duì)象的上下文環(huán)境的問題。——只不過從表面看去,“上下文環(huán)境”的問題被轉(zhuǎn)嫁到對(duì)象的封裝性問題上了。

          (在閱讀下面的文字之前,)先做一個(gè)概念性的說明:
          ??-?在普通函數(shù)中,上下文環(huán)境被window對(duì)象所持有
           -?在“構(gòu)造器和對(duì)象方法”中,上下文環(huán)境被對(duì)象實(shí)例所持有

          在JavaScript的實(shí)現(xiàn)代碼中,每次創(chuàng)建一個(gè)對(duì)象,解釋器將為對(duì)象創(chuàng)建一個(gè)上下文環(huán)境鏈,用于存放對(duì)象在進(jìn)入“構(gòu)造器和對(duì)象方法”時(shí)對(duì)function()內(nèi)部數(shù)據(jù)的一個(gè)備份。JavaScript保證這個(gè)對(duì)象在以后再進(jìn)入“構(gòu)造器和對(duì)象方法”內(nèi)部時(shí),總是持有該上下文環(huán)境,和一個(gè)與之相關(guān)的this對(duì)象。由于對(duì)象可能有多個(gè)方法,且每個(gè)方法可能又存在多層嵌套函數(shù),因此這事實(shí)上構(gòu)成了一個(gè)上下文環(huán)境的樹型鏈表結(jié)構(gòu)。而在構(gòu)造器和對(duì)象方法之外,JavaScript不提供任何訪問(該構(gòu)造器和對(duì)象方法的)上下文環(huán)境的方法。

          簡(jiǎn)而言之:
          ??-?上下文環(huán)境與對(duì)象實(shí)例調(diào)用“構(gòu)造器和對(duì)象方法”時(shí)相關(guān),而與(普通)函數(shù)無(wú)關(guān)
          ??-?上下文環(huán)境記錄一個(gè)對(duì)象在“構(gòu)造函數(shù)和對(duì)象方法”內(nèi)部的私有數(shù)據(jù)
          ??-?上下文環(huán)境采用鏈?zhǔn)浇Y(jié)構(gòu),以記錄多層的嵌套函數(shù)中的上下文

          由于上下文環(huán)境只與構(gòu)造函數(shù)及其內(nèi)部的嵌套函數(shù)有關(guān),重新閱讀前面的代碼:
          //---------------------------------------------------------
          //?JavaScript中的局部變量
          //---------------------------------------------------------
          function?MyFoo()?{
          ??var?i;

          ??MyFoo.setValue?=?function?(v)?{
          ?????i?=?v;
          ??}
          ??MyFoo.getValue?=?function?()?{
          ?????return?i;
          ??}
          }
          MyFoo();

          var?obj1?=?new?Object();
          MyFoo.setValue.call(obj1,?'obj1');

          我們發(fā)現(xiàn)setValue()的確可以訪問到位于MyFoo()函數(shù)內(nèi)部的“局部變量i”,但是由于setValue()方法的執(zhí)有者是MyFoo對(duì)象(記住函數(shù)也是對(duì)象),因此MyFoo對(duì)象擁有MyFoo()函數(shù)的唯一一份“上下文環(huán)境”。

          接下來(lái)MyFoo.setValue.call()調(diào)用雖然為setValue()傳入了新的this對(duì)象,但實(shí)際上擁有“上下文環(huán)境”的仍舊是MyFoo對(duì)象。因此我們看到無(wú)論創(chuàng)建多少個(gè)obj1/obj2,最終操作的都是同一個(gè)私有變量i。

          全局函數(shù)/變量的“上下文環(huán)境”持有者為window,因此下面的代碼說明了“為什么全局變量能被任意的對(duì)象和函數(shù)訪問”:
          //---------------------------------------------------------
          //?全局函數(shù)的上下文
          //---------------------------------------------------------
          /*
          function?Window()?{
          */
          ??var?global_i?=?0;
          ??var?global_j?=?1;

          ??function?foo_0()?{
          ??}

          ??function?foo_1()?{
          ??}
          /*
          }

          window?=?new?Window();
          */

          因此我們可以看到foo_0()與foo_1()能同時(shí)訪問global_i和global_j。接下來(lái)的推論是,上下文環(huán)境決定了變量的“全局”與“私有”。而不是反過來(lái)通過變量的私有與全局來(lái)討論上下文環(huán)境問題。

          更進(jìn)一步的推論是:JavaScript中的全局變量與函數(shù),本質(zhì)上是window對(duì)象的私有變量與方法。而這個(gè)上下文環(huán)境塊,位于所有(window對(duì)象內(nèi)部的)對(duì)象實(shí)例的上下文環(huán)境鏈表的頂端,因此都可能訪問到。

          用“上下文環(huán)境”的理論,你可以順利地解釋在本小節(jié)中,有關(guān)變量的“全局/局部”作用域的問題,以及有關(guān)對(duì)象方法的封裝權(quán)限問題。事實(shí)上,在實(shí)現(xiàn)JavaScript的C源代碼中,這個(gè)“上下文環(huán)境”被叫做“JSContext”,并作為函數(shù)/方法的第一個(gè)參數(shù)傳入。——如果你有興趣,你可以從源代碼中證實(shí)本小節(jié)所述的理論。

          另外,《JavaScript權(quán)威指南》這本書中第4.7節(jié)也講述了這個(gè)問題,但被叫做“變量的作用域”。然而重要的是,這本書把問題講反了。——作者試圖用“全局、局部的作用域”,來(lái)解釋產(chǎn)生這種現(xiàn)象的“上下文環(huán)境”的問題。因此這個(gè)小節(jié)顯得凌亂而且難以自圓其說。

          不過在4.6.3小節(jié),作者也提到了執(zhí)行環(huán)境(execution?context)的問題,這就與我們這里說的“上下文環(huán)境”是一致的了。然而更麻煩的是,作者又將讀者引錯(cuò)了方法,試圖用函數(shù)的上下文環(huán)境去解釋DOM和ScriptEngine中的問題。

          但這本書在“上下文環(huán)境鏈表”的查詢方式上的講述,是正確的而合理的。只是把這個(gè)叫成“作用域”有點(diǎn)不對(duì),或者不妥。?
          <2025年8月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          常用鏈接

          留言簿(3)

          隨筆檔案

          文章分類

          文章檔案

          相冊(cè)

          .net

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 白玉县| 徐水县| 固始县| 资阳市| 嵊州市| 德安县| 六枝特区| 汪清县| 漳浦县| 高安市| 义马市| 监利县| 易门县| 鹤峰县| 青阳县| 桐乡市| 太原市| 新和县| 武胜县| 惠州市| 南安市| 泽普县| 高要市| 兴城市| 双桥区| 论坛| 田阳县| 阜新市| 临湘市| 额济纳旗| 陆良县| 阿尔山市| 盈江县| 山东| 房产| 右玉县| 磐安县| 南平市| 长泰县| 齐齐哈尔市| 巫山县|