JavaScript物件導向的支援(6)

來源:互聯網
上載者:User

================================================================================
Qomolangma OpenProject v0.9

類別    :Rich Web Client
關鍵詞  :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
          DOM,DTHML,CSS,JavaScript,JScript

項目發起:aimingoo (aim@263.net)
項目團隊:aimingoo, leon(pfzhou@gmail.com)
有貢獻者:JingYu(zjy@cnpack.org)
================================================================================

八、JavaScript物件導向的支援
~~~~~~~~~~~~~~~~~~
(續)

4. 執行個體和執行個體引用
--------
在.NET Framework對CTS(Common Type System)約定“一切都是對象”,並分為“值
類型”和“參考型別”兩種。其中“實值型別”的對象在轉換成“參考型別”資料的
過程中,需要進行一個“裝箱”和“拆箱”的過程。

在JavaScript也有同樣的問題。我們看到的typeof關鍵字,返回以下六種資料類型:
"number"、"string"、"boolean"、"object"、"function" 和 "undefined"。

我們也發現JavaScript的對象系統中,有String、Number、Function、Boolean這四
種物件建構器。那麼,我們的問題是:如果有一個數字A,typeof(A)的結果,到底會
是'number'呢,還是一個構造器指向function Number()的對象呢?

//---------------------------------------------------------
// 關於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]);

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

我們注意到,A1和A2的構造器都指向Number。這意味著通過constructor屬性來識別
對象,(有時)比typeof更加有效。因為“實值型別資料”A1作為一個對象來看待時,
與A2有完全相同的特性。

——除了與執行個體引用有關的問題。

參考JScript手冊,我們對其它基礎類型和構造器做相同考察,可以發現:
  - 基礎類型中的undefined、number、boolean和string,是“實值型別”變數
  - 基礎類型中的array、function和object,是“參考型別”變數
  - 使用new()方法構造出對象,是“參考型別”變數

下面的代碼說明“實值型別”與“參考型別”之間的區別:
//---------------------------------------------------------
// 關於JavaScript類型系統中的值/引用問題
//---------------------------------------------------------
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]);

測試代碼的執行結果如下:
-----------
 true, true
 false, false
-----------

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

更進一步的理解這種現象,我們知道:
  - 運算結果為實值型別,或變數為實值型別時,等值(或全等)比較可以得到預想結果
  - (即使包含相同的資料,)不同的對象執行個體之間是不等值(或全等)的
  - 同一個對象的不同引用之間,是等值(==)且全等(===)的

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

JavaScript中的函數參數總是傳入值參,參考型別(的執行個體)是作為指標值傳入的。因此
函數可以隨意重寫入口變數,而不用擔心外部變數被修改。但是,需要留意傳入的引用
類型的變數,因為對它方法調用和屬性讀寫可能會影響到執行個體本身。——但,也可以通
過參考型別的參數來傳出資料。

最後補充說明一下,實值型別比較會逐位元組檢測對象執行個體中的資料,效率低但準確性高;
而參考型別只檢測執行個體指標和資料類型,因此效率高而準確性低。如果你需要檢測兩個
參考型別是否真的包含相同的資料,可能你需要嘗試把它轉換成“字串值”再來比較。

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

function foo() {
  v3 = '全域變數-3';

  var v4 = '只有在函數內部並使用var定義的,才是局部變數';
}

按照通常對語言的理解來說,不同的代碼調用函數,都會擁有一套獨立的局部變數。
因此下面這段代碼很容易理解:
//---------------------------------------------------------
// 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());

結果顯示false,表明不同(執行個體的方法)調用返回的局部變數“obj1/obj2”是不相同。

變數的局部、全域特性與OOP的封裝性中的“私人(private)”、“公開(public)”具
有類同性。因此絕大多數資料總是以下面的方式來說明JavaScript的物件導向系統中
的“封裝權限等級”問題:
//---------------------------------------------------------
// JavaScript中OOP封裝性
//---------------------------------------------------------
function MyObject() {
  // 1. 私人成員和方法
  var private_prop = 0;
  var private_method_1 = function() {
    // ...
    return 1
  }
  function private_method_2() {
    // ...
    return 1
  }

  // 2. 特權方法
  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)”表明只有在(構造)函數內部可訪問,而“特權(privileged)”
是特指一種存取“私人域”的“公開(public)”方法。“公開(public)”表明在(構造)函
數外可以調用和存取。

除了上述的封裝許可權之外,一些文檔還介紹了其它兩種相關的概念:
  - 原型屬性:Classname.prototype.propertyName = someValue
  - (類)靜態屬性:Classname.propertyName = someValue

然而,從物件導向的角度上來講,上面這些概念都很難自圓其說:JavaScript究竟是為何、
以及如何劃分出這些封裝許可權和概念來的呢?

——因為我們必須注意到下面這個例子所帶來的問題:
//---------------------------------------------------------
// 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()的方式
來調用setValue/getValue,使得在MyFoo()調用的過程中替換this為obj1/obj2執行個體。

然而我們發現“測試二”完成之後,obj2、obj1以及function MyFoo()所持有的局部
變數都返回了“obj2”。——這表明三個函數使用了同一個局部變數。

由此可見,JavaScript在處理局部變數時,對“普通函數”與“構造器”是分別對待
的。這種處理策略在一些JavaScript相關的資料中被解釋作“物件導向中的私人域”
問題。而事實上,我更願意從原始碼一級來告訴你真相:這是對象的上下文環境的問
題。——只不過從表面看去,“上下文環境”的問題被轉嫁到對象的封裝性問題上了。

(在閱讀下面的文字之前,)先做一個概念性的說明:
  - 在普通函數中,上下文環境被window對象所持有
 - 在“構造器和對象方法”中,上下文環境被對象執行個體所持有

在JavaScript的實現代碼中,每次建立一個對象,解譯器將為對象建立一個上下文環境
鏈,用於存放對象在進入“構造器和對象方法”時對function()內部資料的一個備份。
JavaScript保證這個對象在以後再進入“構造器和對象方法”內部時,總是持有該上下
文環境,和一個與之相關的this對象。由於對象可能有多個方法,且每個方法可能又存
在多層嵌套函數,因此這事實上構成了一個上下文環境的樹型鏈表結構。而在構造器和
對象方法之外,JavaScript不提供任何訪問(該構造器和對象方法的)上下文環境的方法。

簡而言之:
  - 上下文環境與對象執行個體調用“構造器和對象方法”時相關,而與(普通)函數無關
  - 上下文環境記錄一個對象在“建構函式和對象方法”內部的私人資料
  - 上下文環境採用鏈式結構,以記錄多層的嵌套函數中的上下文

由於上下文環境只與建構函式及其內部的嵌套函數有關,重新閱讀前面的代碼:
//---------------------------------------------------------
// 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');

我們發現setValue()的確可以訪問到位於MyFoo()函數內部的“局部變數i”,但是由於
setValue()方法的執有者是MyFoo對象(記住函數也是對象),因此MyFoo對象擁有MyFoo()
函數的唯一一份“上下文環境”。

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

全域函數/變數的“上下文環境”持有人為window,因此下面的代碼說明了“為什麼全
局變數能被任意的對象和函數訪問”:
//---------------------------------------------------------
// 全域函數的上下文
//---------------------------------------------------------
/*
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。接下來的推論是,
上下文環境決定了變數的“全域”與“私人”。而不是反過來通過變數的私人與全域來
討論上下文環境問題。

更進一步的推論是:JavaScript中的全域變數與函數,本質上是window對象的私人變數
與方法。而這個上下文環境塊,位於所有(window對象內部的)對象執行個體的上下文環境鏈
表的頂端,因此都可能訪問到。

用“上下文環境”的理論,你可以順利地解釋在本小節中,有關變數的“全域/局部”
範圍的問題,以及有關對象方法的封裝許可權問題。事實上,在實現JavaScript的C源
代碼中,這個“上下文環境”被叫做“JSContext”,並作為函數/方法的第一個參數
傳入。——如果你有興趣,你可以從原始碼中證實本小節所述的理論。

另外,《JavaScript權威指南》這本書中第4.7節也講述了這個問題,但被叫做“變數
的範圍”。然而重要的是,這本書把問題講反了。——作者試圖用“全域、局部的作
用域”,來解釋產生這種現象的“上下文環境”的問題。因此這個小節顯得淩亂而且難
以自圓其說。

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

但這本書在“上下文環境鏈表”的查詢方式上的講述,是正確的而合理的。只是把這個
叫成“範圍”有點不對,或者不妥。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.