欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
JavaScript面向對象的支持
~~~~~~~~~~~~~~~~~~
很少有人對JavaScript的面向對象特性進(jìn)行系統的分析。我希望接下來(lái)的文字讓你了解到這
個(gè)語(yǔ)言最少為人知的一面。


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

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

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

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

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

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

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

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


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

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

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

如果一個(gè)值是NaN,那么他可以通過(guò)全局對象(Gobal)的isNaN()方法來(lái)檢測。然而兩個(gè)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中,
它在數學(xué)運算時(shí)的價(jià)值與正無(wú)窮是一樣的?!谝恍?shí)用技巧中,它也可以用來(lái)做一
個(gè)數組序列的邊界檢測。

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

與NaN不同的是,兩個(gè)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類(lèi)型相關(guān)的方法有:
 isFinite()   : 如果值是NaN/正無(wú)窮/負無(wú)窮,返回false,否則返回true。
 parseFloat() : 從字符串(的前綴部分)取一個(gè)浮點(diǎn)數。不成功則返回NaN。


 3). boolean類(lèi)型
========================
 (略)

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

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

下例說(shuō)明這個(gè)問(wèn)題:
//---------------------------------------------------------
// 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);

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

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

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

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

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


 5). function類(lèi)型
========================
javascript函數具有很多特性,除了面向對象的部分之外(這在后面講述),它自
已的一些獨特特性應用也很廣泛。

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

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

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

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

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

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


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


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

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

一些程序員(例如Delphi程序員)習慣于類(lèi)型聲明與實(shí)現分開(kāi)。例如在delphi
中,Interface節用于聲明類(lèi)型或者變量,而implementation節用于書(shū)寫(xiě)類(lèi)型
的實(shí)現代碼,或者一些用于執行的函數、代碼流程。

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


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

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

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

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

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

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

3. 構造與析構

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

5. 原型問(wèn)題

6. 函數的上下文環(huán)境
 
2). 反射機制在JavaScript中的實(shí)現
 ------
  JavaScript中通過(guò)for..in語(yǔ)法來(lái)實(shí)現了反射機制。但是JavaScript中并不
明確區分“屬性”與“方法”,以及“事件”。因此,對屬性的類(lèi)型考查在JS
中是個(gè)問(wèn)題。下面的代碼簡(jiǎn)單示例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>‘);
}

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

與this“指示調用該方法的對象實(shí)例”有些類(lèi)同的,with()語(yǔ)法也用于限定
“在一段代碼片段中默認使用對象實(shí)例”?!绻皇褂脀ith()語(yǔ)法,那
么這段代碼將受到更外層with()語(yǔ)句的影響;如果沒(méi)有更外層的with(),那
么這段代碼的“默認使用的對象實(shí)例”將是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>‘);

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

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


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

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

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

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

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

  return false;
}

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

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


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

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

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

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

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

我們看到,obj1與obj2都是MyObject的實(shí)例,但他們是不同的構造函數產(chǎn)生
的?!⒁?,這在面向對象理論中正確的:因為obj2是MyObject的子類(lèi)實(shí)
例,因此它具有與obj1相同的特性。在應用中這是obj2的多態(tài)性的體現之一。

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

instanceof關(guān)鍵字不提供這樣的機制。一個(gè)提供實(shí)現這種檢測的能力的,是
Object.constructor屬性?!埾扔涀?,它的使用遠比你想象的要難。

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


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

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

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

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

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

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

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

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

接下來(lái),構造過(guò)程將調用MyObject()來(lái)完成初始化?!⒁?,這里只是“初始
化”。

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

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

  // 返回對象
  return _this;
}

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

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


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

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

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

var
  dog := TDog;

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

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

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

參考前面的JS和Delphi代碼,一個(gè)類(lèi)同的例子是這樣:
//---------------------------------------------------------
// JS中“原型鏈表”的關(guān)鍵代碼
//---------------------------------------------------------
// 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的用戶(hù)代碼中,“原型鏈表”的構建方法是一行代碼:
  "當前類(lèi)的構造器函數".prototype = "直接父類(lèi)的實(shí)例"

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

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

JavaScript是會(huì )有編譯過(guò)程的。這個(gè)過(guò)程主要處理的是“語(yǔ)法檢錯”、“語(yǔ)
法聲明”和“條件編譯指令”。而這里的“語(yǔ)法聲明”,主要處理的就是函
數聲明?!@也是我說(shuō)“函數是第一類(lèi)的,而對象不是”的一個(gè)原因。

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

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

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

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

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

這個(gè)示例代碼在JS環(huán)境中執行的結果是:
------------------------------------
  1234
  undefined
  Exception: ‘obj2‘ 未定義
  [object Object]
  hi, obj
------------------------------------

問(wèn)題是,testFoo()是在它被聲明之前被執行的;而同樣用“直接聲明”的
形式定義的object變量,卻不能在聲明之前引用?!又?,第二、三
個(gè)輸入是不正確的。

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

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

由于JavaScript對原型鏈的維護是“執行”而不是“聲明”,這說(shuō)明“原型
鏈是由用戶(hù)代碼來(lái)維護的,而不是編譯器維護的。

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

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

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

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

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


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

JavaScript中的new()關(guān)鍵字中隱含的構造過(guò)程,與Delphi的構造過(guò)程并不完全一致。但
在構造器函數中發(fā)生的行為卻與上述的類(lèi)似:
//---------------------------------------------------------
// JS中的構造過(guò)程(形式代碼)
//---------------------------------------------------------
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è)類(lèi)為參考對象的,這個(gè)構造過(guò)程中JavaScript可以擁有與Delphi
一樣豐富的行為。然而,由于Delphi中的構造過(guò)程是“動(dòng)態(tài)的”,因此事實(shí)上
Delphi還會(huì )調用父類(lèi)(MyObject)的構造過(guò)程,以及觸發(fā)父類(lèi)的OnCreate()事件。

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

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

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

  // ...
}

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

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

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

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

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

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

// 測試數據和相關(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>‘);

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

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

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

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

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

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

obj2 = new MyObject2();
document.writeln(MyObject2.prototype.constructor === MyObject2);
--------

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

obj2 = new MyObject2();
--------

這時(shí),再訪(fǎng)問(wèn)obj2,將會(huì )得到新的原型(也就是MyObject2.prototype)的constructor屬性。
因此,一切很明了:原型的屬性影響到構造過(guò)程對對象的constructor的初始設定。

作為一種補充的解決問(wèn)題的手段,JavaScript開(kāi)發(fā)規范中說(shuō)“need to remember to reset
the constructor property‘,要求用戶(hù)自行設定該屬性。

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

obj2 = new MyObject2();


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

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

obj2 = new MyObject2();


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

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

可能還是有人不明白我們在說(shuō)什么,那么我就舉一個(gè)例子:如果創(chuàng )建一個(gè)Excel對象,
打開(kāi)文件A,然后我們save它,然后關(guān)閉這個(gè)實(shí)例。然后我們再創(chuàng )建Excel對象并打開(kāi)
同一文件?!⒁膺@時(shí)JavaScript可能還沒(méi)有來(lái)得及析構前一個(gè)對象?!@時(shí)我們
再想Save這個(gè)文件,就發(fā)現失敗了。下面的代碼示例這種情況:
//---------------------------------------------------------
// JavaScript中的析構問(wèn)題(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()">重寫(xiě)</button>
</body>
 

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

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

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

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

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

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

// 示例1
testObject();

// 示例2
testObject2()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

//---------------------------------------------------------
// 關(guān)于JavaScript的類(lèi)型的測試代碼
//---------------------------------------------------------
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。這意味著(zhù)通過(guò)constructor屬性來(lái)識別
對象,(有時(shí))比typeof更加有效。因為“值類(lèi)型數據”A1作為一個(gè)對象來(lái)看待時(shí),
與A2有完全相同的特性。

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

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

下面的代碼說(shuō)明“值類(lèi)型”與“引用類(lèi)型”之間的區別:
//---------------------------------------------------------
// 關(guān)于JavaScript類(lèi)型系統中的值/引用問(wèn)題
//---------------------------------------------------------
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
-----------

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

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

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

JavaScript中的函數參數總是傳入值參,引用類(lèi)型(的實(shí)例)是作為指針值傳入的。因此
函數可以隨意重寫(xiě)入口變量,而不用擔心外部變量被修改。但是,需要留意傳入的引用
類(lèi)型的變量,因為對它方法調用和屬性讀寫(xiě)可能會(huì )影響到實(shí)例本身?!?,也可以通
過(guò)引用類(lèi)型的參數來(lái)傳出數據。

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


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

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

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

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

變量的局部、全局特性與OOP的封裝性中的“私有(private)”、“公開(kāi)(public)”具
有類(lèi)同性。因此絕大多數資料總是以下面的方式來(lái)說(shuō)明JavaScript的面向對象系統中
的“封裝權限級別”問(wèn)題:
//---------------------------------------------------------
// 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. 公開(kāi)成員和方法
  this.public_prop_1 = ‘‘;
  this.public_method_1 = function () {
    // ...
  }
}

// 4. 公開(kāi)成員和方法(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)”表明只有在(構造)函數內部可訪(fǎng)問(wèn),而“特權(privileged)”
是特指一種存取“私有域”的“公開(kāi)(public)”方法。“公開(kāi)(public)”表明在(構造)函
數外可以調用和存取。

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

然而,從面向對象的角度上來(lái)講,上面這些概念都很難自圓其說(shuō):JavaScript究竟是為何、
以及如何劃分出這些封裝權限和概念來(lái)的呢?

——因為我們必須注意到下面這個(gè)例子所帶來(lái)的問(wè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());

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

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

由此可見(jiàn),JavaScript在處理局部變量時(shí),對“普通函數”與“構造器”是分別對待
的。這種處理策略在一些JavaScript相關(guān)的資料中被解釋作“面向對象中的私有域”
問(wèn)題。而事實(shí)上,我更愿意從源代碼一級來(lái)告訴你真相:這是對象的上下文環(huán)境的問(wèn)
題?!徊贿^(guò)從表面看去,“上下文環(huán)境”的問(wèn)題被轉嫁到對象的封裝性問(wèn)題上了。

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

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

簡(jiǎn)而言之:
  - 上下文環(huán)境與對象實(shí)例調用“構造器和對象方法”時(shí)相關(guān),而與(普通)函數無(wú)關(guān)
  - 上下文環(huán)境記錄一個(gè)對象在“構造函數和對象方法”內部的私有數據
  - 上下文環(huán)境采用鏈式結構,以記錄多層的嵌套函數中的上下文

由于上下文環(huán)境只與構造函數及其內部的嵌套函數有關(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ā)現setValue()的確可以訪(fǎng)問(wèn)到位于MyFoo()函數內部的“局部變量i”,但是由于
setValue()方法的執有者是MyFoo對象(記住函數也是對象),因此MyFoo對象擁有MyFoo()
函數的唯一一份“上下文環(huán)境”。

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

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

  function foo_0() {
  }

  function foo_1() {
  }
/*
}

window = new Window();
*/

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

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

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

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

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

但這本書(shū)在“上下文環(huán)境鏈表”的查詢(xún)方式上的講述,是正確的而合理的。只是把這個(gè)
叫成“作用域”有點(diǎn)不對,或者不妥。
~~~~~~~~~~~~~~~~~~
(續)

7. JavaScript面向對象的支持的補充內容
--------
 1). 類(lèi)型系統
========================
我們前面已經(jīng)完整地描述過(guò)JavaScript的兩種類(lèi)型系統。包括:
  - 基礎類(lèi)型系統:由typeof()返回值的六種基礎類(lèi)型
  - 對象類(lèi)型系統:由new()返回值的、構造器和原型繼承組織起來(lái)的類(lèi)型系統

JavaScript是弱類(lèi)型語(yǔ)言,因此類(lèi)型自動(dòng)轉換是它語(yǔ)言特性的一個(gè)重要組成部分。但
對于一個(gè)指定的變量而言,(在某一時(shí)刻,)它總是有確定的數據類(lèi)型的。“運算”是
導致類(lèi)型轉換的方法(但不是根源),因此“運算結果的類(lèi)型”的確定就非常重要。關(guān)
于這一部分的內容,推薦大家閱讀一份資料:
  http://jibbering.com/faq/faq_notes/type_convert.html

類(lèi)型系統中還有一個(gè)特殊的組成部分,就是“直接量”聲明。下面的代碼簡(jiǎn)述各種直
接量聲明的方法,但不再詳述具體細節:
//---------------------------------------------------------
// 各種直接量聲明(一些錯誤格式或特例請查看JScript手冊)
//---------------------------------------------------------
// 1. Number
var n1 = 11;      // 普通十進(jìn)制數
var n2 = 013;     // 八進(jìn)制數
var n3 = 0xB;     // 十六進(jìn)制數
var n4 = 1.2;     // 浮點(diǎn)值
var n5 =  .2;     // 浮點(diǎn)值
var n6 = 1.0e-4;  // (或1e-4)浮點(diǎn)值

// 2. String
var s1 = ‘test‘;  // (或"test")字符串
var s2 = "test\n";// 帶轉義符的字符串(轉義符規則參見(jiàn)手冊)
var s3 = "‘test‘";// 用""、‘‘以在字符串中使用引號
var s4 = "\xD";   // 用轉義符來(lái)聲明不可鍵入的字符

// 3. Boolean
var b1 = true;
var b2 = false;

// 4. Function
function foo1() {};       // 利用編譯器特性直接聲明
var foo2 = function() {}; // 聲明匿名函數

// 5. Object
// * 請留意聲明中對分隔符“,”的使用
var obj1 = null;          // 空對象是可以被直接聲明的
var obj2 = {
  value1 : ‘value‘,       // 對象屬性
  foo1   : function() {}, // 利用匿名函數來(lái)直接聲明對象方法
  foo2   : foo2           // 使方法指向已聲明過(guò)的函數
}

// 6. RegExp
var r1 = /^[O|o]n/;     // 使用一對"/../"表達的即是正則表達式
var r2 = /^./gim;       // (注意,) gim為正則表達式的三個(gè)參數

// 7. Array
var arr1 = [1,,,1];     // 直接聲明, 包括一些"未定義(undefined)"值
var arr2 = [1,[1,‘a‘]]; // 異質(zhì)(非單一類(lèi)型)的數組聲明
var arr3 = [[1],[2]];   // 多維數組(其實(shí)是從上一個(gè)概念衍生下來(lái)的

// 8. undefined
var u1 = undefined;     // 可以直接聲明, 這里的undefined是Global的屬性

有些時(shí)候,我們可以“即聲明即使用”一個(gè)直接量,下面的代碼演示這一特性:
//---------------------------------------------------------
// 直接量的“即聲明即使用”
//---------------------------------------------------------
var obj = function () {   // 1. 聲明了一個(gè)匿名函數
  return {                // 2. 函數執行的結果是返回一個(gè)直接聲明的"對象"
    value: ‘test‘,
    method: function(){}
  }
}();                      // 3. 使匿名函數執行并返回結果,以完成obj變量的聲明

在這個(gè)例子中,很多處用到了直接量的聲明。這其中函數直接聲明(并可以立即執行)的特
性很有價(jià)值,例如在一個(gè).js文件中試圖執行一些代碼,但不希望這些代碼中的變量聲明對
全局代碼導致影響,因此可以在外層包裝一個(gè)匿名函數并使之執行,例如:
//---------------------------------------------------------
// 匿名函數的執行
// (注:void用于使后面的函數會(huì )被執行, 否則解釋器會(huì )認為僅是聲明函數)
//---------------------------------------------------------
void function() {
  if (isIE()) {
    // do something...
  }
}();


 2). 對象系統
========================
對象系統中一個(gè)未被提及的重要內容是delete運算。它用于刪除數組元素、對象屬性和已
聲明的變量。

由于delete運算不能刪除用var來(lái)聲明的變量,也就意味著(zhù)它只能刪除在函數內/外聲明
的全局變量?!@個(gè)說(shuō)法有點(diǎn)別扭,但事實(shí)上的確如此。那么我們可以更深層地透視一
個(gè)真想:delete運算刪除變量的實(shí)質(zhì),是刪除用戶(hù)在window對象的上下文環(huán)境中聲明的屬
性。

回到前面有關(guān)“上下文環(huán)境”的討論,我們注意到(在函數外)聲明全局變量的三種形式:
----------
var global_1 = ‘全局變量1‘;
global_2 = ‘全局變量2‘;

function foo() {
  global_3 = ‘全局變量3‘;
}
----------

全局變量2和3都是“不用var聲明的變量”,這其實(shí)是在window對象的上下文環(huán)境中的
屬性聲明。也就是說(shuō)可以用window.global_2和window.global_3來(lái)存取它們。這三種聲
明window對象的屬性的方法,與直接指定“window.global_value = <值>”這種方法的
唯一區別,是在“for .. in”運算時(shí),這三種方法聲明的屬性/方法都會(huì )被隱藏。如下
例所示:
//---------------------------------------------------------
// 全局變量上下文環(huán)境的一些特點(diǎn):屬性名隱藏
//---------------------------------------------------------
var global_1 = ‘全局變量1‘;
global_2 = ‘全局變量2‘;

void function foo() {
  global_3 = ‘全局變量3‘;
}();

window.global_4 = ‘全局變量4‘;

for (var i in window) {
  document.writeln(i, ‘<br>‘);
}
document.writeln(‘<HR>‘);
document.writeln(window.global_1, ‘<BR>‘);
document.writeln(window.global_2, ‘<BR>‘);
document.writeln(window.global_3, ‘<BR>‘);

我們注意到在返回的結果中不會(huì )出現全局變量1/2/3的屬性名。但使用window.xxxx這種方
式仍可以存取到它們。

在window上下文環(huán)境中,global_1實(shí)質(zhì)是該上下文中的私有變量,我們在其它代碼中能存
取到它,只是因為其它(所有的)代碼都在該上下文之內。global_2/3則被(隱含地)聲明成
window的屬性,而global_4則顯式地聲明為window的屬性。

因此我們回到前面的結論:
  - 刪除(不用var聲明的)變量的實(shí)質(zhì),是刪除window對象的屬性。

此外,我們也得到另外三條推論(最重要的是第一條):
  - delete能刪除數組元素,實(shí)質(zhì)上是因為數組下標也是數組對象的隱含屬性。
  - 在復雜的系統中,為減少變量名沖突,應盡量避免全局變量(和聲明)的使用,或采用
    delete運算來(lái)清理window對象的屬性。
  - window對象是唯一可以讓用戶(hù)聲明“隱含的屬性”的對象?!⒁膺@只是表面的現
    象,因為事實(shí)上這只是JavaScript規范帶來(lái)的一個(gè)“附加效果”。:)

delete清除window對象、系統對象、用戶(hù)對象等的“用戶(hù)聲明屬性”,但不能清除如prototype、
constructor這樣的系統屬性。此外,delete也可以清除數組中的元素(但不會(huì )因為清除元
素而使數組長(cháng)度發(fā)生變化)。例如:
//---------------------------------------------------------
// delete運算的一些示例
//---------------------------------------------------------
var arr = [1, 2, 3];
var obj = {v1:1, v2:2};
global_variant = 3;

delete arr[2];
document.writeln(‘1‘ in arr, ‘<BR>‘);  // 數組下標事實(shí)上也是數組對象的隱含屬性
document.writeln(arr.length, ‘<BR>‘);  // 數組長(cháng)度不會(huì )因delete而改變

delete obj.v2;
document.writeln(‘v2‘ in obj, ‘<BR>‘);

document.writeln(‘global_variant‘ in window, ‘<BR>‘);
delete global_variant;

// 以下的代碼不能正常執行,這是IE的一個(gè)bug
if (‘global_variant‘ in window) {
 document.writeln(‘bug test:‘, global_variant, ‘<BR>‘);
}

最后這行代碼錯誤的根源,在于IE錯誤地檢測了‘global_variant‘在window的對象屬性
中是否仍然存在。因為在同樣的位置,“(‘global_variant‘ in window)”表達式的返
回結果居然為true!——firefox中沒(méi)有這個(gè)bug。

delete清除掉屬性或數組元素,并不表明腳本引擎會(huì )對于該屬性/元素執行析構。對象
的析構操作是不確定的,關(guān)于這一點(diǎn)請查看更前面的內容。
 

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

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

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

//---------------------------------------------------------
// 關(guān)于JavaScript的類(lèi)型的測試代碼
//---------------------------------------------------------
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。這意味著(zhù)通過(guò)constructor屬性來(lái)識別
對象,(有時(shí))比typeof更加有效。因為“值類(lèi)型數據”A1作為一個(gè)對象來(lái)看待時(shí),
與A2有完全相同的特性。

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

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

下面的代碼說(shuō)明“值類(lèi)型”與“引用類(lèi)型”之間的區別:
//---------------------------------------------------------
// 關(guān)于JavaScript類(lèi)型系統中的值/引用問(wèn)題
//---------------------------------------------------------
var str1 = ‘a(chǎn)bcdefgh‘, str2 = ‘a(chǎn)bcdefgh‘;
var obj1 = new String(‘a(chǎn)bcdefgh‘), obj2 = new String(‘a(chǎn)bcdefgh‘);

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

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

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

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

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

JavaScript中的函數參數總是傳入值參,引用類(lèi)型(的實(shí)例)是作為指針值傳入的。因此
函數可以隨意重寫(xiě)入口變量,而不用擔心外部變量被修改。但是,需要留意傳入的引用
類(lèi)型的變量,因為對它方法調用和屬性讀寫(xiě)可能會(huì )影響到實(shí)例本身?!?,也可以通
過(guò)引用類(lèi)型的參數來(lái)傳出數據。

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


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

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

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

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

變量的局部、全局特性與OOP的封裝性中的“私有(private)”、“公開(kāi)(public)”具
有類(lèi)同性。因此絕大多數資料總是以下面的方式來(lái)說(shuō)明JavaScript的面向對象系統中
的“封裝權限級別”問(wèn)題:
//---------------------------------------------------------
// 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. 公開(kāi)成員和方法
  this.public_prop_1 = ‘‘;
  this.public_method_1 = function () {
    // ...
  }
}

// 4. 公開(kāi)成員和方法(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)”表明只有在(構造)函數內部可訪(fǎng)問(wèn),而“特權(privileged)”
是特指一種存取“私有域”的“公開(kāi)(public)”方法。“公開(kāi)(public)”表明在(構造)函
數外可以調用和存取。

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

然而,從面向對象的角度上來(lái)講,上面這些概念都很難自圓其說(shuō):JavaScript究竟是為何、
以及如何劃分出這些封裝權限和概念來(lái)的呢?

——因為我們必須注意到下面這個(gè)例子所帶來(lái)的問(wè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());

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

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

由此可見(jiàn),JavaScript在處理局部變量時(shí),對“普通函數”與“構造器”是分別對待
的。這種處理策略在一些JavaScript相關(guān)的資料中被解釋作“面向對象中的私有域”
問(wèn)題。而事實(shí)上,我更愿意從源代碼一級來(lái)告訴你真相:這是對象的上下文環(huán)境的問(wèn)
題?!徊贿^(guò)從表面看去,“上下文環(huán)境”的問(wèn)題被轉嫁到對象的封裝性問(wèn)題上了。

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

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

簡(jiǎn)而言之:
  - 上下文環(huán)境與對象實(shí)例調用“構造器和對象方法”時(shí)相關(guān),而與(普通)函數無(wú)關(guān)
  - 上下文環(huán)境記錄一個(gè)對象在“構造函數和對象方法”內部的私有數據
  - 上下文環(huán)境采用鏈式結構,以記錄多層的嵌套函數中的上下文

由于上下文環(huán)境只與構造函數及其內部的嵌套函數有關(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ā)現setValue()的確可以訪(fǎng)問(wèn)到位于MyFoo()函數內部的“局部變量i”,但是由于
setValue()方法的執有者是MyFoo對象(記住函數也是對象),因此MyFoo對象擁有MyFoo()
函數的唯一一份“上下文環(huán)境”。

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

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

  function foo_0() {
  }

  function foo_1() {
  }
/*
}

window = new Window();
*/

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

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

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

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

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

但這本書(shū)在“上下文環(huán)境鏈表”的查詢(xún)方式上的講述,是正確的而合理的。只是把這個(gè)
叫成“作用域”有點(diǎn)不對,或者不妥。

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
在JavaScript中使用面向對象
淺談js之閉包
深入淺出 JavaScript 中的 this
學(xué)習JavaScript 的必備 (一),讓您對js的 function, javascr...
JavaScript面向對象編程(1)-- 基礎
javascript深入理解js閉包
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久