[轉]JavaScript面向對象的支持

JavaScript面向對象的支持
~~~~~~~~~~~~~~~~~~
很少有人對JavaScript的面向對象特性進行系統的分析。我希望接下來的文字讓你瞭解到這
個語言最少爲人知的一面。

1. JavaScript中的類型
--------
雖然JavaScript是一個基於對象的語言,但對象(Object)在JavaScript中不是第一型的。JS
是以函數(Function)爲第一型的語言。這樣說,不但是因爲JS中的函數具有高級語言中的函
數的各種特性,而且也因爲在JS中,Object也是由函數來實現的。——關於這一點,可以在
後文中“構造與析構”部分看到更進一步的說明。

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

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

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

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

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

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

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

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

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

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

如果一個值是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表示比最大的數 (Number.MAX_VALUE) 更大的值。在JS中,
它在數學運算時的價值與正無窮是一樣的。——在一些實用技巧中,它也可以用來做一
個數組序列的邊界檢測。

Infinity在Number對象中被定義爲POSITIVE_INFINITY。此外,負無窮也在Number中被定
義:
  Number.POSITIVE_INFINITY  : 比最大正數(Number.MAX_VALUE)更大的值。正無窮。
  Number.NEGATIVE_INFINITY  : 比最小負數(-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類型相關的方法有:
isFinite()   : 如果值是NaN/正無窮/負無窮,返回false,否則返回true。
parseFloat() : 從字符串(的前綴部分)取一個浮點數。不成功則返回NaN。

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

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

除此之外,string的主要複雜性來自於在JavaScript中無所不在的toString()
方法。這也是JavaScript爲瀏覽器環境而提供的一個很重要的方法。例如我們
聲明一個對象,但是要用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()方
法的時候,那麼它作爲字符串型態使用時(例如被writeln),就會調用Java Script
環境缺省的toString()。反過來,你也可以重新定義JavaScript理解這個對象
的方法。

很多JavaScript框架,在實現“模板”機制的時候,就利用了這個特性。例如
他們用這樣定義一個FontElement對象:
//---------------------------------------------------------
// 利用toString()實現模板機制的簡單原理
//---------------------------------------------------------
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函數具有很多特性,除了面向對象的部分之外(這在後面講述),它自
已的一些獨特特性應用也很廣泛。

首先javascript中的每個函數,在調用過程中可以執有一個arguments對象。這個
對象是由腳本解釋環境創建的,你沒有別的方法來自己創建一個arguments對象。

arguments可以看成一個數組:它有length屬性,並可以通過arguments[n]的方式
來訪問每一個參數。然而它最重要的,卻是可以通過 callee 屬性來得到正在執行
的函數對象的引用。

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

——我們已經看到,我們可以在JavaScript裏面,通過callee/caller來遍歷執行
期的調用棧。由於arguments事實上也是Function的一個屬性,因此我們事實上也
能遍歷執行期調用棧上的每一個函數的參數。下面的代碼是一個簡單的示例:

//---------------------------------------------------------
// 調用棧的遍歷
//---------------------------------------------------------
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('調用參數:<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面向對象的支持
--------
在前面的例子中其實已經講到了object類型的“類型聲明”與“實例創建”。
在JavaScript中,我們需要通過一個函數來聲明自己的object類型:
//---------------------------------------------------------
// JavaScript中對象的類型聲明的形式代碼
// (以後的文檔中,“對象名”通常用MyObject來替代)
//---------------------------------------------------------
function 對象名(參數表) {
  this.屬性 = 初始值;

  this.方法 = function(方法參數表) {
    // 方法實現代碼
  }
}

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

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

1). 函數在JavaScript的面向對象機制中的五重身份
------
“對象名”——如MyObject()——這個函數充當了以下語言角色:
  (1) 普通函數
  (2) 類型聲明
  (3) 類型的實現
  (4) 類引用
  (5) 對象的構造函數

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

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

這個函數的同時也是“類引用”。在JavaScript,如果你需要識別一個對象
的具體型別,你需要執有一個“類引用”。——當然,也就是這個函數的名
字。instanceof 運算符就用於識別實例的類型,我們來看一下它的應用:
//---------------------------------------------------------
// JavaScript中對象的類型識別
//   語法:  對象實例 instanceof 類引用
//---------------------------------------------------------
function MyObject() {
  this.data = 'test data';
}

// 這裏MyObject()作爲構造函數使用
var obj = new MyObject();
var arr = new Array();

// 這裏MyObject作爲類引用使用
document.writeln(obj instanceof MyObject);
document.writeln(arr instanceof MyObject);

================
(未完待續)
================
接下來的內容:

2. JavaScript面向對象的支持
--------

2). 反射機制在JavaScript中的實現
3). this與with關鍵字的使用
4). 使用in關鍵字的運算
5). 使用instanceof關鍵字的運算
6). 其它與面向對象相關的關鍵字

3. 構造與析構

4. 實例和實例引用

5. 原型問題

6. 函數的上下文環境

7. 對象的類型檢查問題

2). 反射機制在JavaScript中的實現
------
  JavaScript中通過for..in語法來實現了反射機制。但是JavaScript中並不
明確區分“屬性”與“方法”,以及“事件”。因此,對屬性的類型考查在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>');
}

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

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

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

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

最後說明一點。事實上,在很多語言的實現中,“事件”都不是“面向對象”的語
言特性,而是由具體的編程模型來提供的。例如Delphi中的事件驅動機制,是由Win32
操作系統中的窗口消息機制來提供,或者由用戶代碼在Component/Class中主動調用
事件處理函數來實現。

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

3). this與with關鍵字的使用
------
在JavaScript的對象系統中,this關鍵字用在兩種地方:
   - 在構造器函數中,指代新創建的對象實例
   - 在對象的方法被調用時,指代調用該方法的對象實例

如果一個函數被作爲普通函數(而不是對象方法)調用,那麼在函數中的this關鍵字
將指向window對象。與此相同的,如果this關鍵字不在任何函數中,那麼他也指向
window對象。

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

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

// 測試1: 作爲函數調用
foo();

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

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

在上面的代碼裏,obj1/obj2對foo()的調用是很普通的調用方法。——也就
是在構造器上,將一個函數指定爲對象的方法。

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

在這個測試中,foo()仍然作爲普通函數來調用,只是JavaScript的語言特性
允許在call()/apply()時,傳入一個對象實例來指定foo()的上下文環境中所
出現的this關鍵字的引用。——需要注意的是,此時的foo()仍舊是一個普通
函數調用,而不是對象方法調用。

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

然而需要注意的是this與with關鍵字不是互爲影響的。如下面的代碼:
//---------------------------------------------------------
// 測試: this與with關鍵字不是互爲影響的
//---------------------------------------------------------
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>');

你不能指望這樣的代碼在調用結束後,會使obj2.value屬性置值爲8。這幾行
代碼的結果是:window對象多了一個value屬性,並且值爲8。

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

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

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

in關鍵字的重要應用是高速字符串檢索。尤其是在只需要判定“字符串是否
存在”的情況下。例如10萬個字符串,如果存儲在數組中,那麼檢索效率將會
極差。
//---------------------------------------------------------
// 使用對象來檢索
//---------------------------------------------------------
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;
}

這種使用關鍵字in的方法,也存在一些限制。例如只能查找字符串,而數
組元素可以是任意值。另外,arrayToObject()也存在一些開銷,這使得它
不適合於頻繁變動的查找集。最後,(我想你可能已經注意到了)使用對象
來查找的時候並不能準確定位到查找數據,而數組中可以指向結果的下標。

八、JavaScript面向對象的支持
~~~~~~~~~~~~~~~~~~
(續)

2. JavaScript面向對象的支持
--------
(續)

5). 使用instanceof關鍵字的運算
------
在JavaScript中提供了instanceof關鍵字來檢測實例的類型。這在前面討
論它的“五重身份”時已經講過。但instanceof的問題是,它總是列舉整個
原型鏈以檢測類型(關於原型繼承的原理在“構造與析構”小節講述),如:
//---------------------------------------------------------
// 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的實例,但他們是不同的構造函數產生
的。——注意,這在面向對象理論中正確的:因爲obj2是MyObject的子類實
例,因此它具有與obj1相同的特性。在應用中這是obj2的多態性的體現之一。

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

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

好的,問題先到這裏。constructor屬性已經涉及到“構造與析構”的問題,
這個我們後面再講。“原型繼承”、“構造與析構”是JavaScript的OOP中
的主要問題、核心問題,以及“致命問題”。

6). null與undefined
------
在JavaScript中,null與undefined曾一度使我迷惑。下面的文字,有利於
你更清晰的認知它(或者讓你更迷惑):
   - null是關鍵字;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都可以被類型轉換爲false,但不等值於false:
       document.writeln(!null, !undefined);
       document.writeln(null==false);
       document.writeln(undefined==false);

八、JavaScript面向對象的支持
~~~~~~~~~~~~~~~~~~
(續)

3. 構造、析構與原型問題
--------
我們已經知道一個對象是需要通過構造器函數來產生的。我們先記住幾點:
   - 構造器是一個普通的函數
   - 原型是一個對象實例
   - 構造器有原型屬性,對象實例沒有
   - (如果正常地實現繼承模型,)對象實例的constructor屬性指向構造器
   - 從三、四條推出:obj.constructor.prototype指向該對象的原型

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

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

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

1). new()關鍵字的形式化代碼
------
我們先來看“obj1 = new MyObject()”這行代碼中的這個new關鍵字。

new關鍵字用於產生一個新的實例(說到這裏補充一下,我習慣於把保留字叫關鍵
字。另外,在JavaScript中new關鍵字同時也是一個運算符),這個實例的缺省屬性
中,(至少)會執有構造器函數的原型屬性(prototype)的一個引用(在ECMA Javascript
規範中,對象的這個屬性名定義爲__proto__)。

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

接下來,構造過程將調用MyObject()來完成初始化。——注意,這裏只是“初始
化”。

爲了清楚地解釋這個過程,我用代碼形式化地描述一下這個過程:
//---------------------------------------------------------
// new()關鍵字的形式化代碼
//---------------------------------------------------------
function new(aFunction) {
  // 基本對象實例
  var _this = {};

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

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

  // 爲存取原型中的屬性添加(內部的)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;
  }

  // 爲存取原型中的屬性添加(內部的)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    // 創建當前實例的新成員
    }
  }

  // 調用構造函數完成初始化, (如果有,)傳入args
  aFunction.call(_this);

  // 返回對象
  return _this;
}

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

而且,構造函數(aFunction)本身並不需要操作prototype,也不需要回傳this。

2). 由用戶代碼維護的原型(prototype)鏈
------
接下來我們更深入的討論原型鏈與構造過程的問題。這就是:
  - 原型鏈是用戶代碼創建的,new()關鍵字並不協助維護原型鏈

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

這時,Delphi的編譯器會通過編譯技術來維護一個繼承關係鏈表。我們可以通
過類似以下的代碼來查詢這個鏈表:
//---------------------------------------------------------
// delphi中使用繼關係鏈表的關鍵代碼
//---------------------------------------------------------
function isAnimal(obj: TObject): boolean;
begin
  Result := obj is TAnimal;
end;

var
  dog := TDog;

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

可以看到,在Delphi的用戶代碼中,不需要直接繼護繼承關係的鏈表。這是因
爲Delphi是強類型語言,在處理用class()關鍵字聲明類型時,delphi的編譯器
已經爲用戶構造了這個繼承關係鏈。——注意,這個過程是聲明,而不是執行
代碼。

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

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

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

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

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

可以看到,在JS的用戶代碼中,“原型鏈表”的構建方法是一行代碼:
  "當前類的構造器函數".prototype = "直接父類的實例"

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

那麼,“是執行而非聲明”到底有什麼意義呢?

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

如下例:
//---------------------------------------------------------
// 函數聲明與執行語句的關係(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環境中執行的結果是:
------------------------------------
  1234
  undefined
  Exception: 'obj2' 未定義
  [object Object]
  hi, obj
------------------------------------
問題是,testFoo()是在它被聲明之前被執行的;而同樣用“直接聲明”的
形式定義的object變量,卻不能在聲明之前引用。——例子中,第二、三
個輸入是不正確的。

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

另外我們也可以發現,使用"var"來聲明的時候,編譯器會先確認有該變量
存在,但變量的值會是“undefined”。——因此“testFoo(obj1)”不會發
生異常。但是,只有等到關於obj1的賦值語句被執行過,纔會有正常的輸出。
請對照第二、三與第四、五行輸出的差異。

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

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

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

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

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

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

3). 原型實例是如何被構造過程使用的
------
仍以Delphi爲例。構造過程中,delphi中會首先創建一個指定實例大小的
“空的對象”,然後逐一給屬性賦值,以及調用構造過程中的方法、觸發事
件等。

JavaScript中的new()關鍵字中隱含的構造過程,與Delphi的構造過程並不完全一致。但
在構造器函數中發生的行爲卻與上述的類似:
//---------------------------------------------------------
// JS中的構造過程(形式代碼)
//---------------------------------------------------------
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();

如果以單個類爲參考對象的,這個構造過程中JavaScript可以擁有與Delphi
一樣豐富的行爲。然而,由於Delphi中的構造過程是“動態的”,因此事實上
Delphi還會調用父類(MyObject)的構造過程,以及觸發父類的OnCreate()事件。

JavaScript沒有這樣的特性。父類的構造過程僅僅發生在爲原型(prototype
屬性)賦值的那一行代碼上。其後,無論有多少個new MyObject2()發生,
MyObject()這個構造器都不會被使用。——這也意味着:
  - 構造過程中,原型對象是一次性生成的;新對象只持有這個原型實例的引用
    (並用“寫複製”的機制來存取其屬性),而並不再調用原型的構造器。

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

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

  // ...
}

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

八、JavaScript面向對象的支持
~~~~~~~~~~~~~~~~~~
(續)

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

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

  對於JavaScript的內置對象來說,constructor屬性指向內置的構造器函數。如:
//---------------------------------------------------------
// 內置對象實例的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';
}

// 測試數據和相關代碼
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>');

在這個例子中,我們發現constructor屬性被實現得並不完整。對於DOM對象、ActiveX對象
來說這個屬性都沒有正確的返回。

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

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

我們也發現實例obj的constructor指向function MyObject()。這說明JavaScript維護了對
象的constructor屬性。——這與一些人想象的不一樣。

然而再接下來,我們發現MyObject2()的實例obj2的constructor仍然指向function MyObject()。
儘管這很說不通,然而現實的確如此。——這到底是爲什麼呢?

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

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

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

作爲一種補充的解決問題的手段,JavaScript開發規範中說“need to remember to reset
the constructor property',要求用戶自行設定該屬性。

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

obj2 = new MyObject2();

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

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

obj2 = new MyObject2();

5). 析構問題
------
JavaScript中沒有析構函數,但卻有“對象析構”的問題。也就是說,儘管我們不
知道一個對象什麼時候會被析構,也不能截獲它的析構過程並處理一些事務。然而,
在一些不多見的時候,我們會遇到“要求一個對象立即析構”的問題。

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

可能還是有人不明白我們在說什麼,那麼我就舉一個例子:如果創建一個Excel對象,
打開文件A,然後我們save它,然後關閉這個實例。然後我們再創建Excel對象並打開
同一文件。——注意這時JavaScript可能還沒有來得及析構前一個對象。——這時我們
再想Save這個文件,就發現失敗了。下面的代碼示例這種情況:
//---------------------------------------------------------
// JavaScript中的析構問題(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 οnclick="createXLS()">創建</button>
  <button οnclick="writeXLS()">重寫</button>
</body>

在這個例子中,在本地文件操作時並不會出現異常。——最多隻是有一些內存垃
圾而已。然而,如果strSaveLocation是一個遠程的URL,這時本地將會保存一個
文件存取權限的憑證,而且同時只能一個(遠程的)實例來開啓該excel文檔並存
儲。於是如果反覆點擊"重寫"按鈕,就會出現異常。

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

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

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

例如:
//---------------------------------------------------------
// 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”在函數testObject()中構造了_obj1,但是在函數退出時,
    它就已經離開了函數的上下文環境,因此_obj1失效了;
  - “示例2”中,testObject2()中也構造了一個對象_obj2並傳出,因
    此對象有了“函數外”的上下文環境(和生存週期),然而由於函數
    的返回值沒有被其它變量“持有”,因此_obj2也立即失效了;
  - “示例3”中,testObject2()構造的_obj2被外部的變量obj3持用了,
    這時,直到“obj3=null”這行代碼生效時,_obj2纔會因爲引用關係
    消失而失效。
  - 與示例3相同的原因,“示例4”中的_obj2會在“arr=[]”這行代碼
    之後纔會失效。

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

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

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

Microsoft公開了解決這種問題的策略:主動調用內存回收過程。

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

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

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

第一行代碼調用excel.Quit()方法來使得excel進程中止並退出,這時由於JavaScript
環境執有excel對象實例,因此excel進程並不實際中止。

第二行代碼使excel爲null,以清除對象引用,從而使對象“失效”。然而由於
對象仍舊在函數上下文環境中,因此如果直接調用GC過程,對象仍然不會被清理。

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

GC過程的使用,在使用了ActiveX Object的JS環境中很有效。一些潛在的ActiveX
Object包括XML、VML、OWC(Office Web Componet)、flash,甚至包括在JS中的VBArray。
從這一點來看,ajax架構由於採用了XMLHTTP,並且同時要滿足“不切換頁面”的
特性,因此在適當的時候主動調用GC過程,會得到更好的效率用UI體驗。

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

  excel.Quit();
  excel = null;

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

最後之最後,關於GC的一個補充說明:在IE窗體被最小化時,IE將會主動調用一次
CollectGarbage()函數。這使得IE窗口在最小化之後,內存佔用會有明顯改善。

八、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封裝性
//--------------
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章