posts - 0, comments - 77, trackbacks - 0, articles - 356
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Javascript面向?qū)ο筇匦裕ㄞD(zhuǎn)載)

          Posted on 2008-06-26 14:21 semovy 閱讀(351) 評論(0)  編輯  收藏 所屬分類: JavaScript
          ~~~~~~~~~~~~~~~~~~
          很少有人對JavaScript的面向?qū)ο筇匦赃M行系統(tǒng)的分析。我希望接下來的文字讓你了解到這
          個語言最少為人知的一面。

          1. JavaScript中的類型
          --------
          雖然JavaScript是一個基于對象的語言,但對象(Object)在JavaScript中不是第一型的。JS
          是以函數(shù)(Function)為第一型的語言。這樣說,不但是因為JS中的函數(shù)具有高級語言中的函
          數(shù)的各種特性,而且也因為在JS中,Object也是由函數(shù)來實現(xiàn)的?!P(guān)于這一點,可以在
          后文中“構(gòu)造與析構(gòu)”部分看到更進一步的說明。

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

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

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

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

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

          //---------------------------------------------------------
          // 2. 代碼更簡潔,但不易懂的方法
          //---------------------------------------------------------
          var undefined = void 0;

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

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

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

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

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

          全局對象(Gobal)的Infinity表示比最大的數(shù) (Number.MAX_VALUE) 更大的值。在JS中,
          它在數(shù)學運算時的價值與正無窮是一樣的?!谝恍嵱眉记芍?,它也可以用來做一
          個數(shù)組序列的邊界檢測。

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

          與NaN不同的是,兩個Infinity(或-Infinity)之間是互等的。如下例:
          //---------------------------------------------------------
          // Infinity的運算與檢測
          //---------------------------------------------------------
          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/正無窮/負無窮,返回false,否則返回true。
           parseFloat() : 從字符串(的前綴部分)取一個浮點數(shù)。不成功則返回NaN。

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

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

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

          下例說明這個問題:
          //---------------------------------------------------------
          // toString()的應用
          //---------------------------------------------------------
          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);

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

          很多JavaScript框架,在實現(xiàn)“模板”機制的時候,就利用了這個特性。例如
          他們用這樣定義一個FontElement對象:
          //---------------------------------------------------------
          // 利用toString()實現(xià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('這是一個測試。');

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

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

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

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

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

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

          //---------------------------------------------------------
          // 調(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>');

              // 上一級
              foo = foo.caller;
            }
          }

          // 運行測試
          foo1(1, 2);

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

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

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

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

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

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

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

          這個函數(shù)的同時也是“類引用”。在JavaScript,如果你需要識別一個對象
          的具體型別,你需要執(zhí)有一個“類引用”?!斎唬簿褪沁@個函數(shù)的名
          字。instanceof 運算符就用于識別實例的類型,我們來看一下它的應用:
          //---------------------------------------------------------
          // JavaScript中對象的類型識別
          //   語法:  對象實例 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ù))
          ================
          接下來的內(nèi)容:

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

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

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

          4. 實例和實例引用

          5. 原型問題

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

          7. 對象的類型檢查問題

          2). 反射機制在JavaScript中的實現(xiàn)
           ------
            JavaScript中通過for..in語法來實現(xiàn)了反射機制。但是JavaScript中并不
          明確區(qū)分“屬性”與“方法”,以及“事件”。因此,對屬性的類型考查在JS
          中是個問題。下面的代碼簡單示例for..in的使用與屬性識別:
          //---------------------------------------------------------
          // JavaScript中for..in的使用和屬性識別
          //---------------------------------------------------------
          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>');
          }

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

          有兩個原因,使得在JS中不能很好的識別“一個屬性是不是事件”:
            - COM接口中本身只有方法,屬性與事件,都是通過一組get/set方法來公布的。
            - JavaScript中,本身并沒有獨立的“事件”機制。

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

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

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

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

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

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

           由于在JavaScript中不明確區(qū)分函數(shù)與方法。因此有些代碼看起來很奇怪:
          //---------------------------------------------------------
          // 函數(shù)的幾種可能調(diào)用形式
          //---------------------------------------------------------
          function foo() {
            // 下面的this指代調(diào)用該方法的對象實例
            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)建實例
            this.name = name;
            this.foo = foo;
          }

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

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

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

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

          在上面的代碼里,obj1/obj2對foo()的調(diào)用是很普通的調(diào)用方法?!簿?br /> 是在構(gòu)造器上,將一個函數(shù)指定為對象的方法。

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

          在這個測試中,foo()仍然作為普通函數(shù)來調(diào)用,只是JavaScript的語言特性
          允許在call()/apply()時,傳入一個對象實例來指定foo()的上下文環(huán)境中所
          出現(xiàn)的this關(guān)鍵字的引用?!枰⒁獾氖?,此時的foo()仍舊是一個普通
          函數(shù)調(diào)用,而不是對象方法調(diào)用。

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

          然而需要注意的是this與with關(guān)鍵字不是互為影響的。如下面的代碼:
          //---------------------------------------------------------
          // 測試: 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é)束后,會使obj2.value屬性置值為8。這幾行
          代碼的結(jié)果是:window對象多了一個value屬性,并且值為8。

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

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

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

           in關(guān)鍵字的重要應用是高速字符串檢索。尤其是在只需要判定“字符串是否
          存在”的情況下。例如10萬個字符串,如果存儲在數(shù)組中,那么檢索效率將會
          極差。
          //---------------------------------------------------------
          // 使用對象來檢索
          //---------------------------------------------------------
          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()也存在一些開銷,這使得它
          不適合于頻繁變動的查找集。最后,(我想你可能已經(jīng)注意到了)使用對象
          來查找的時候并不能準確定位到查找數(shù)據(jù),而數(shù)組中可以指向結(jié)果的下標。

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

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

           5). 使用instanceof關(guān)鍵字的運算
           ------
           在JavaScript中提供了instanceof關(guān)鍵字來檢測實例的類型。這在前面討
          論它的“五重身份”時已經(jīng)講過。但instanceof的問題是,它總是列舉整個
          原型鏈以檢測類型(關(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的實例,但他們是不同的構(gòu)造函數(shù)產(chǎn)生
          的?!⒁?,這在面向?qū)ο罄碚撝姓_的:因為obj2是MyObject的子類實
          例,因此它具有與obj1相同的特性。在應用中這是obj2的多態(tài)性的體現(xiàn)之一。

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

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

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

           6). null與undefined
           ------
           在JavaScript中,null與undefined曾一度使我迷惑。下面的文字,有利于
          你更清晰的認知它(或者讓你更迷惑):
             - null是關(guān)鍵字;undefined是Global對象的一個屬性。
             - null是對象(空對象, 沒有任何屬性和方法);undefined是undefined類
               型的值。試試下面的代碼:
                 document.writeln(typeof null);
                 document.writeln(typeof undefined);
             - 對象模型中,所有的對象都是Object或其子類的實例,但null對象例外:
                 document.writeln(null instanceof Object);
             - null“等值(==)”于undefined,但不“全等值(===)”于undefined:
                 document.writeln(null == undefined);
                 document.writeln(null == undefined);
             - 運算時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òu)造器函數(shù)來產(chǎn)生的。我們先記住幾點:
             - 構(gòu)造器是一個普通的函數(shù)
             - 原型是一個對象實例
             - 構(gòu)造器有原型屬性,對象實例沒有
             - (如果正常地實現(xiàn)繼承模型,)對象實例的constructor屬性指向構(gòu)造器
             - 從三、四條推出:obj.constructor.prototype指向該對象的原型

           好,我們接下來分析一個例子,來說明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)鍵字的形式化代碼
           ------
           我們先來看“obj1 = new MyObject()”這行代碼中的這個new關(guān)鍵字。

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

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

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

          為了清楚地解釋這個過程,我用代碼形式化地描述一下這個過程:
          //---------------------------------------------------------
          // new()關(guān)鍵字的形式化代碼
          //---------------------------------------------------------
          function new(aFunction) {
            // 基本對象實例
            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)建當前實例的新成員
              }
            }

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

            // 返回對象
            return _this;
          }

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

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

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

          以Delphi代碼為例,我們在聲明繼承關(guān)系的時候,可以用這樣的代碼:
          //---------------------------------------------------------
          // delphi中使用的“類”類型聲明
          //---------------------------------------------------------
          type
            TAnimal = class(TObject); // 動物
            TMammal = class(TAnimal); // 哺乳動物
            TCanine = class(TMammal); // 犬科的哺乳動物
            TDog = class(TCanine);    // 狗

          這時,Delphi的編譯器會通過編譯技術(shù)來維護一個繼承關(guān)系鏈表。我們可以通
          過類似以下的代碼來查詢這個鏈表:
          //---------------------------------------------------------
          // 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的用戶代碼中,不需要直接繼護繼承關(guān)系的鏈表。這是因
          為Delphi是強類型語言,在處理用class()關(guān)鍵字聲明類型時,delphi的編譯器
          已經(jīng)為用戶構(gòu)造了這個繼承關(guān)系鏈。——注意,這個過程是聲明,而不是執(zhí)行
          代碼。

          而在JavaScript中,如果需要獲知對象“是否是某個基類的子類對象”,那么
          你需要手工的來維護(與delphi這個例子類似的)一個鏈表。當然,這個鏈表不
          叫類型繼承樹,而叫“(對象的)原型鏈表”?!贘S中,沒有“類”類型。

          參考前面的JS和Delphi代碼,一個類同的例子是這樣:
          //---------------------------------------------------------
          // 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)建方法是一行代碼:
            "當前類的構(gòu)造器函數(shù)".prototype = "直接父類的實例"

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

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

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

          如下例:
          //---------------------------------------------------------
          // 函數(shù)聲明與執(zhí)行語句的關(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);

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

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

          另外我們也可以發(fā)現(xiàn),使用"var"來聲明的時候,編譯器會先確認有該變量
          存在,但變量的值會是“undefined”?!虼?#8220;testFoo(obj1)”不會發(fā)
          生異常。但是,只有等到關(guān)于obj1的賦值語句被執(zhí)行過,才會有正常的輸出。
          請對照第二、三與第四、五行輸出的差異。

          由于JavaScript對原型鏈的維護是“執(zhí)行”而不是“聲明”,這說明“原型
          鏈是由用戶代碼來維護的,而不是編譯器維護的。

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

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

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

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

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

           3). 原型實例是如何被構(gòu)造過程使用的
           ------
           仍以Delphi為例。構(gòu)造過程中,delphi中會首先創(chuàng)建一個指定實例大小的
          “空的對象”,然后逐一給屬性賦值,以及調(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òu)造過程中JavaScript可以擁有與Delphi
          一樣豐富的行為。然而,由于Delphi中的構(gòu)造過程是“動態(tài)的”,因此事實上
          Delphi還會調(diào)用父類(MyObject)的構(gòu)造過程,以及觸發(fā)父類的OnCreate()事件。

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

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

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

            // ...
          }

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

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

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

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

            對于JavaScript的內(nèi)置對象來說,constructor屬性指向內(nèi)置的構(gòu)造器函數(shù)。如:
          //---------------------------------------------------------
          // 內(nèi)置對象實例的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';
          }

          // 測試數(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>');

          // 測試代碼
          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>');

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

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

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

          我們也發(fā)現(xiàn)實例obj的constructor指向function MyObject()。這說明JavaScript維護了對
          象的constructor屬性?!@與一些人想象的不一樣。

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

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

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

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

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

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

          obj2 = new MyObject2();

          更外一種解決問題的方法,是在function MyObject()中去重置該值。當然,這樣會使
          得執(zhí)行效率稍低一點點:
          //---------------------------------------------------------
          // 維護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ù),但卻有“對象析構(gòu)”的問題。也就是說,盡管我們不
          知道一個對象什么時候會被析構(gòu),也不能截獲它的析構(gòu)過程并處理一些事務。然而,
          在一些不多見的時候,我們會遇到“要求一個對象立即析構(gòu)”的問題。

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

          可能還是有人不明白我們在說什么,那么我就舉一個例子:如果創(chuàng)建一個Excel對象,
          打開文件A,然后我們save它,然后關(guān)閉這個實例。然后我們再創(chuàng)建Excel對象并打開
          同一文件。——注意這時JavaScript可能還沒有來得及析構(gòu)前一個對象?!@時我們
          再想Save這個文件,就發(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 = '測試字符串';
            wk.SaveAs(strSaveLocation);
            wk.Saved = true;

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

          在這個例子中,在本地文件操作時并不會出現(xiàn)異常?!疃嘀皇怯幸恍﹥?nèi)存垃
          圾而已。然而,如果strSaveLocation是一個遠程的URL,這時本地將會保存一個
          文件存取權(quán)限的憑證,而且同時只能一個(遠程的)實例來開啟該excel文檔并存
          儲。于是如果反復點擊"重寫"按鈕,就會出現(xiàn)異常。

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

          解決這個問題的方法很復雜。它涉及到兩個問題:
            - 本地憑證的釋放
            - ActiveX Object實例的釋放

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

          例如:
          //---------------------------------------------------------
          // JavaScript對象何時失效
          //---------------------------------------------------------
          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 = [];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          //---------------------------------------------------------
          // 關(guān)于JavaScript的類型的測試代碼
          //---------------------------------------------------------
          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]);

          測試代碼的執(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屬性來識別對象,(有時)比typeof更加有效。因為“值類型數(shù)據(jù)”A1作為一個對象來看待時,與A2有完全相同的特性。

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

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

          下面的代碼說明“值類型”與“引用類型”之間的區(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]);

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

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

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

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

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

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

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

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

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

          按照通常對語言的理解來說,不同的代碼調(diào)用函數(shù),都會擁有一套獨立的局部變量。
          因此下面這段代碼很容易理解:
          //---------------------------------------------------------
          // 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,表明不同(實例的方法)調(diào)用返回的局部變量“obj1/obj2”是不相同。

          變量的局部、全局特性與OOP的封裝性中的“私有(private)”、“公開(public)”具有類同性。因此絕大多數(shù)資料總是以下面的方式來說明JavaScript的面向?qū)ο笙到y(tǒng)中的“封裝權(quán)限級別”問題:
          //---------------------------------------------------------
          // 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ū)ο蟮慕嵌壬蟻碇v,上面這些概念都很難自圓其說:JavaScript究竟是為何、以及如何劃分出這些封裝權(quán)限和概念來的呢?

          ——因為我們必須注意到下面這個例子所帶來的問題:
          //---------------------------------------------------------
          // 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();

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

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

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

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

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

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

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

          簡而言之:
            - 上下文環(huán)境與對象實例調(diào)用“構(gòu)造器和對象方法”時相關(guān),而與(普通)函數(shù)無關(guān)
            - 上下文環(huán)境記錄一個對象在“構(gòu)造函數(shù)和對象方法”內(nèi)部的私有數(shù)據(jù)
            - 上下文環(huán)境采用鏈式結(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對象(記住函數(shù)也是對象),因此MyFoo對象擁有MyFoo()函數(shù)的唯一一份“上下文環(huán)境”。

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

          全局函數(shù)/變量的“上下文環(huán)境”持有者為window,因此下面的代碼說明了“為什么全局變量能被任意的對象和函數(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()能同時訪問global_i和global_j。接下來的推論是,上下文環(huán)境決定了變量的“全局”與“私有”。而不是反過來通過變量的私有與全局來討論上下文環(huán)境問題。

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

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

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

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

          但這本書在“上下文環(huán)境鏈表”的查詢方式上的講述,是正確的而合理的。只是把這個叫成“作用域”有點不對,或者不妥。
          主站蜘蛛池模板: 呈贡县| 崇左市| 泰顺县| 河东区| 广昌县| 正阳县| 衡南县| 株洲县| 平度市| 永城市| 来凤县| 石狮市| 亚东县| 漳州市| 奇台县| 遂川县| 沾益县| 瑞安市| 平昌县| 库尔勒市| 江口县| 泽州县| 德庆县| 伊宁县| 新平| 富裕县| 虞城县| 兖州市| 禹城市| 连平县| 孟津县| 响水县| 惠东县| 永春县| 西丰县| 荔波县| 济南市| 山阴县| 阿坝县| 宁波市| 仁布县|