很少有人对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

因为没有整型的缘故,因此在一些关于CSSDOM属性的运算中,如果你期望取值为整数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中,
它在数学运算时的价值与正无穷是一样的。——在一些实用技巧中,它也可以用来做一
个数组序列的边界检测。

InfinityNumber对象中被定义为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. 对象的类型检查问题

 

 

 

类别    Rich Web Client
关键词  JS OOPJS Framwork, Rich Web ClientRIAWeb Component

          DOM
DTHMLCSSJavaScriptJScript

项目发起:aimingoo (aim@263.net)
项目团队:aimingoo, leon(pfzhou@gmail.com
)
有贡献者:JingYu(zjy@cnpack.org
)
================================================================================

 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等事件,其实是IEDOM模型提供的。从更内核
的角度上讲: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/obj2foo()的调用是很普通的调用方法。——也就
是在构造器上,将一个函数指定为对象的方法。

而测试3中的call()apply()就比较特殊。

在这个测试中,foo()仍然作为普通函数来调用,只是JavaScript的语言特性
允许在call()/apply()时,传入一个对象实例来指定foo()的上下文环境中所
出现的this关键字的引用。——需要注意的是,此时的foo()仍旧是一个普通
函数调用,而不是对象方法调用。

this“指示调用该方法的对象实例有些类同的,with()语法也用于限定
在一段代码片段中默认使用对象实例——如果不使用with()语法,那
么这段代码将受到更外层with()语句的影响;如果没有更外层的with(),那
么这段代码的默认使用的对象实例将是window

然而需要注意的是thiswith关键字不是互为影响的。如下面的代码:
//---------------------------------------------------------
//
测试: thiswith关键字不是互为影响的

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

我们看到,obj1obj2都是MyObject的实例,但他们是不同的构造函数产生
的。——注意,这在面向对象理论中正确的:因为obj2MyObject的子类实
例,因此它具有与obj1相同的特性。在应用中这是obj2的多态性的体现之一。

但是,即便如此,我们也必须面临这样的问题:如何知道obj2obj1是否是
相同类型的实例呢?——也就是说,连构造器都相同?

instanceof关键字不提供这样的机制。一个提供实现这种检测的能力的,是
Object.constructor
属性。——但请先记住,它的使用远比你想象的要难。

好的,问题先到这里。constructor属性已经涉及到构造与析构的问题,
这个我们后面再讲。原型继承构造与析构JavaScriptOOP
的主要问题、核心问题,以及致命问题


 6). null
undefined
 ------
 
JavaScript中,nullundefined曾一度使我迷惑。下面的文字,有利于

你更清晰的认知它(或者让你更迷惑)
   - null
是关键字;undefinedGlobal对象的一个属性。
   - null
是对象(空对象, 没有任何属性和方法)undefinedundefined
    
型的值。试试下面的代码:
       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);
   -
运算时nullundefined都可以被类型转换为false,但不等值于false
       document.writeln(!null, !undefined);
       document.writeln(null==false);
       document.writeln(undefined==false);

 

类别    Rich Web Client
关键词  JS OOPJS Framwork, Rich Web ClientRIAWeb Component

          DOM
DTHMLCSSJavaScriptJScript

项目发起:aimingoo (aim@263.net)
项目团队:aimingoo, leon(pfzhou@gmail.com
)
有贡献者:JingYu(zjy@cnpack.org
)
================================================================================

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

4. 实例和实例引用
--------
.NET FrameworkCTS(Common Type System)约定一切都是对象,并分为
类型引用类型两种。其中值类型的对象在转换成引用类型数据的
过程中,需要进行一个装箱拆箱的过程。

JavaScript也有同样的问题。我们看到的typeof关键字,返回以下六种数据类型:
"number"
"string""boolean""object""function" "undefined"

我们也发现JavaScript的对象系统中,有StringNumberFunctionBoolean这四
种对象构造器。那么,我们的问题是:如果有一个数字Atypeof(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
-----------

我们注意到,A1A2的构造器都指向Number。这意味着通过constructor属性来识别
对象,(有时)typeof更加有效。因为值类型数据”A1作为一个对象来看待时,
A2有完全相同的特性。

——除了与实例引用有关的问题。

参考JScript手册,我们对其它基础类型和构造器做相同考察,可以发现:
  -
基础类型中的undefinednumberbooleanstring,是值类型变量
  -
基础类型中的arrayfunctionobject,是引用类型变量
  -
使用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()调用的过程中替换thisobj1/obj2实例。

然而我们发现测试二完成之后,obj2obj1以及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_iglobal_j。接下来的推论是,
上下文环境决定了变量的全局私有。而不是反过来通过变量的私有与全局来
讨论上下文环境问题。

更进一步的推论是:JavaScript中的全局变量与函数,本质上是window对象的私有变量
与方法。而这个上下文环境块,位于所有(window对象内部的)对象实例的上下文环境链
表的顶端,因此都可能访问到。

上下文环境的理论,你可以顺利地解释在本小节中,有关变量的全局/局部
作用域的问题,以及有关对象方法的封装权限问题。事实上,在实现JavaScriptC

代码中,这个上下文环境被叫做“JSContext”,并作为函数/方法的第一个参数
传入。——如果你有兴趣,你可以从源代码中证实本小节所述的理论。

另外,《JavaScript权威指南》这本书中第4.7节也讲述了这个问题,但被叫做变量
的作用域。然而重要的是,这本书把问题讲反了。——作者试图用全局、局部的作
用域,来解释产生这种现象的上下文环境的问题。因此这个小节显得凌乱而且难
以自圆其说。

不过在4.6.3小节,作者也提到了执行环境(execution context)的问题,这就与我们这
里说的上下文环境是一致的了。然而更麻烦的是,作者又将读者引错了方法,试图
用函数的上下文环境去解释DOMScriptEngine中的问题。

但这本书在上下文环境链表的查询方式上的讲述,是正确的而合理的。只是把这个
叫成作用域有点不对,或者不妥。

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

7. JavaScript面向对象的支持的补充内容
--------
 1).
类型系统
========================
我们前面已经完整地描述过JavaScript的两种类型系统。包括:
  -
基础类型系统:由typeof()返回值的六种基础类型
  -
对象类型系统:由new()返回值的、构造器和原型继承组织起来的类型系统

JavaScript是弱类型语言,因此类型自动转换是它语言特性的一个重要组成部分。但
对于一个指定的变量而言,(在某一时刻,)它总是有确定的数据类型的。运算
导致类型转换的方法(但不是根源),因此运算结果的类型的确定就非常重要。关
于这一部分的内容,推荐大家阅读一份资料:
 
http://jibbering.com/faq/faq_notes/type_convert.html

类型系统中还有一个特殊的组成部分,就是直接量声明。下面的代码简述各种直
接量声明的方法,但不再详述具体细节:
//---------------------------------------------------------
//
各种直接量声明(一些错误格式或特例请查看JScript手册
)
//---------------------------------------------------------
// 1. Number
var n1 = 11;      //
普通十进制数

var n2 = 013;     //
八进制数
var n3 = 0xB;     //
十六进制数
var n4 = 1.2;     //
浮点值
var n5 =  .2;     //
浮点值
var n6 = 1.0e-4;  // (
1e-4)浮点值

// 2. String
var s1 = 'test';  // (
"test")字符串

var s2 = "test\n";//
带转义符的字符串(转义符规则参见手册)
var s3 = "'test'";//
""''以在字符串中使用引号

var s4 = "\xD";   //
用转义符来声明不可键入的字符

// 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() {}, //
利用匿名函数来直接声明对象方法
  foo2   : foo2           //
使方法指向已声明过的函数
}

// 6. RegExp
var r1 = /^[O|o]n/;     //
使用一对"/../"表达的即是正则表达式

var r2 = /^./gim;       // (
注意,) gim为正则表达式的三个参数

// 7. Array
var arr1 = [1,,,1];     //
直接声明, 包括一些"未定义(undefined)"

var arr2 = [1,[1,'a']]; //
异质(非单一类型)的数组声明
var arr3 = [[1],[2]];   //
多维数组(其实是从上一个概念衍生下来的

// 8. undefined
var u1 = undefined;     //
可以直接声明, 这里的undefinedGlobal的属性

有些时候,我们可以即声明即使用一个直接量,下面的代码演示这一特性:
//---------------------------------------------------------
//
直接量的即声明即使用
//---------------------------------------------------------
var obj = function () {   // 1.
声明了一个匿名函数

  return {                // 2.
函数执行的结果是返回一个直接声明的"对象"
    value: 'test',
    method: function(){}
  }
}();                      // 3.
使匿名函数执行并返回结果,以完成obj变量的声明

在这个例子中,很多处用到了直接量的声明。这其中函数直接声明(并可以立即执行)的特
性很有价值,例如在一个.js文件中试图执行一些代码,但不希望这些代码中的变量声明对
全局代码导致影响,因此可以在外层包装一个匿名函数并使之执行,例如:
//---------------------------------------------------------
//
匿名函数的执行

// (
注:void用于使后面的函数会被执行, 否则解释器会认为仅是声明函数)
//---------------------------------------------------------
void function() {
  if (isIE()) {
    // do something...
  }
}();


 2).
对象系统
========================
对象系统中一个未被提及的重要内容是delete运算。它用于删除数组元素、对象属性和已
声明的变量。

由于delete运算不能删除用var来声明的变量,也就意味着它只能删除在函数内/外声明
的全局变量。——这个说法有点别扭,但事实上的确如此。那么我们可以更深层地透视一
个真想:delete运算删除变量的实质,是删除用户在window对象的上下文环境中声明的属
性。

回到前面有关上下文环境的讨论,我们注意到(在函数外)声明全局变量的三种形式:
----------
var global_1 = '
全局变量
1';
global_2 = '
全局变量2';

function foo() {
  global_3 = '
全局变量
3';
}
----------

全局变量23都是不用var声明的变量,这其实是在window对象的上下文环境中的
属性声明。也就是说可以用window.global_2window.global_3来存取它们。这三种声
window对象的属性的方法,与直接指定“window.global_value = <>”这种方法的
唯一区别,是在“for .. in”运算时,这三种方法声明的属性/方法都会被隐藏。如下
例所示:
//---------------------------------------------------------
//
全局变量上下文环境的一些特点:属性名隐藏

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

我们注意到在返回的结果中不会出现全局变量1/2/3的属性名。但使用window.xxxx这种方
式仍可以存取到它们。

window上下文环境中,global_1实质是该上下文中的私有变量,我们在其它代码中能存
取到它,只是因为其它(所有的)代码都在该上下文之内。global_2/3则被(隐含地)声明成
window
的属性,而global_4则显式地声明为window的属性。

因此我们回到前面的结论:
  -
删除(不用var声明的)变量的实质,是删除window对象的属性。

此外,我们也得到另外三条推论(最重要的是第一条)
  - delete
能删除数组元素,实质上是因为数组下标也是数组对象的隐含属性。
  -
在复杂的系统中,为减少变量名冲突,应尽量避免全局变量(和声明)的使用,或采用
    delete
运算来清理window对象的属性。
  - window
对象是唯一可以让用户声明隐含的属性的对象。——注意这只是表面的现
   
象,因为事实上这只是JavaScript规范带来的一个附加效果:)

delete清除window对象、系统对象、用户对象等的用户声明属性,但不能清除如prototype
constructor
这样的系统属性。此外,delete也可以清除数组中的元素(但不会因为清除元
素而使数组长度发生变化)。例如:
//---------------------------------------------------------
// delete
运算的一些示例

//---------------------------------------------------------
var arr = [1, 2, 3];
var obj = {v1:1, v2:2};
global_variant = 3;

delete arr[2];
document.writeln('1' in arr, '<BR>');  //
数组下标事实上也是数组对象的隐含属性

document.writeln(arr.length, '<BR>');  //
数组长度不会因delete而改变

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

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

// 以下的代码不能正常执行,这是IE的一个bug
if ('global_variant' in window) {
 document.writeln('bug test
', global_variant, '<BR>');
}

最后这行代码错误的根源,在于IE错误地检测了'global_variant'window的对象属性
中是否仍然存在。因为在同样的位置,“('global_variant' in window)”表达式的返
回结果居然为true——firefox中没有这个bug

delete清除掉属性或数组元素,并不表明脚本引擎会对于该属性/元素执行析构。对象
的析构操作是不确定的,关于这一点请查看更前面的内容。

 

posted on 2009-01-13 14:09  账号难注册  阅读(1356)  评论(1编辑  收藏  举报