??xml version="1.0" encoding="utf-8" standalone="yes"?>
Qomolangma OpenProject v0.9
cd QRich Web Client
关键?QJS OOPQJS Framwork, Rich Web ClientQRIAQWeb ComponentQ?br />DOMQDTHMLQCSSQJavaScriptQJScript
目发vQaimingoo (aim@263.net)
目团队Qaimingoo, leon(pfzhou@gmail.com)
有A献者:JingYu(zjy@cnpack.org)
================================================================================
八、JavaScript面向对象的支?br />~~~~~~~~~~~~~~~~~~
很少有h对JavaScript的面向对象特性进行系l的分析。我希望接下来的文字让你了解到这
个语a最ؓ人知的一面?/p>
1. JavaScript中的cd
--------
虽然JavaScript是一个基于对象的语言Q但对象(Object)在JavaScript中不是第一型的。JS
是以函数(Function)为第一型的语言。这栯Q不但是因ؓJS中的函数h高语言中的?br />数的各种Ҏ,而且也因为在JS中,Object也是由函数来实现的。——关于这一点,可以?br />后文中“构造与析构”部分看到更q一步的说明?/p>
JS中是q型的Q他的内|类型简单而且清晰Q?br />---------------------------------------------------------
undefined : 未定?br />number : 数字
boolean : 布尔?br />string : 字符?br />function : 函数
object : 对象
1). undefinedcd
========================
在IE5及以下版本中Q除了直接赋值和typeof()之外Q其它Q何对undefined的操作都导?br />异常。如果需要知道一个变量是否是undefinedQ只能采用typeof()的方法:
<script>
var v;
if (typeof(v) == 'undefined') {
// ...
}
</script>
但是在IE5.5及以上版本中Qundefined是一个已实现的系l保留字。因此可以用undefined?br />比较和运。检一个值是否是undefined的更单方法可以是Q?br /><script>
var v;
if (v === undefined) {
// ...
}
</script>
因此Z使得核心代码?部分?兼容IE5及早期版本,Romo核心单元中有一行代码用?br />“声明”一个undefined|
//---------------------------------------------------------
// code from Qomolangma, in JSEnhance.js
//---------------------------------------------------------
var undefined = void null;
q一行代码还有一Ҏ需要说明的Q就是void语句的应用。void表明“执行其后的语句Q且
忽略q回值”。因此在void之后可以出现能被执行的Q何“单个”语句。而执行的l果是
undefined。当Ӟ如果你愿意,你也可以用下面的代码之一“定义undefined”?br />//---------------------------------------------------------
// 1. 较复杂的ҎQ利用一个匿名的I函数执行的q回
//---------------------------------------------------------
var undefined = function(){}();
//---------------------------------------------------------
// 2. 代码更简z,但不易懂的方?br />//---------------------------------------------------------
var undefined = void 0;
void也能像函C样用,因此void(0)也是合法的。有些时候,一些复杂的语句可能不能
使用void的关键字形式Q而必要使用void的函数Ş式。例如:
//---------------------------------------------------------
// 必须使用void()形式的复杂表辑ּ
//---------------------------------------------------------
void(i=1); // 或如下语?
void(i=1, i++);
2). numbercd
========================
JavaScript中L处理点敎ͼ因此它没有象Delphi中的MaxIntq样的常量,反而是有这
样两个常值定义:
Number.MAX_VALUE : q回 JScript 能表辄最大的数。约{于 1.79E+308?br />Number.MIN_VALUE : q回 JScript 最接近0的数。约{于 2.22E-308?/p>
因ؓ没有整型的缘故,因此在一些关于CSS和DOM属性的q算中,如果你期望取gؓ整数2Q?br />你可能会得到字符东y?.0”——或者类g此的一些情c这U情况下Q你可能需要用
到全局对象(Gobal)的parseInt()Ҏ?/p>
全局对象(Gobal)中还有两个属性与numbercd的运有养I
NaN : 术表达式的q算l果不是数字Q则q回NaN倹{?br />Infinity : 比MAX_VALUE更大的数?/p>
如果一个值是NaNQ那么他可以通过全局对象(Gobal)的isNaN()Ҏ来检。然而两个NaN
g间不是互{的。如下例Q?br />//---------------------------------------------------------
// NaN的运与?br />//---------------------------------------------------------
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中,
它在数学q算时的价g正无Ih一L。——在一些实用技巧中Q它也可以用来做一
个数l序列的边界?/p>
Infinity在Number对象中被定义为POSITIVE_INFINITY。此外,负无I也在Number中被?br />义:
Number.POSITIVE_INFINITY : 比最大正敎ͼNumber.MAX_VALUEQ更大的倹{正无穷?br />Number.NEGATIVE_INFINITY : 比最负敎ͼ-Number.MAX_VALUEQ更的倹{负无穷?/p>
与NaN不同的是Q两个Infinity(?Infinity)之间是互{的。如下例Q?br />//---------------------------------------------------------
// Infinity的运与?br />//---------------------------------------------------------
var
v1 = Number.MAX_VALUE * 2;
v2 = Number.MAX_VALUE * 3;
document.writeln(v1);
document.writeln(v2);
document.writeln(v1 == v2);
在Global中其它与numbercd相关的方法有Q?br />isFinite() : 如果值是NaN/正无I?负无IPq回falseQ否则返回true?br />parseFloat() : 从字W串(的前~部分)取一个QҎ。不成功则返回NaN?/p>
3). booleancd
========================
(?
4). stringcd
========================
JavaScript中的Stringcd原本没有什么特D的Q但是JavaScriptZ适应
“浏览器实现的超文本环境”,因此它具有一些奇怪的Ҏ。例如:
link() : 把一个有HREF属性的链接标{?lt;A>攑֜String对象中的文本两端?br />big() : 把一?lt;big>标签攑֜String对象中的文本两端?br />以下Ҏ与此cdQ?br />anchor()
blink()
bold()
fixed()
fontcolor()
fontsize()
italics()
small()
strike()
sub()
sup()
除此之外Qstring的主要复杂性来自于在JavaScript中无所不在的toString()
Ҏ。这也是JavaScript为浏览器环境而提供的一个很重要的方法。例如我?br />声明一个对象,但是要用document.writeln()来输出它Q在IE中会昄什么呢Q?/p>
下例说明q个问题Q?br />//---------------------------------------------------------
// toString()的应?br />//---------------------------------------------------------
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);
在这个例子中Q我们看刎ͼ当一个对象没有重新声?覆盖)自己toString()?br />法的时候,那么它作为字W串型态用时(例如被writeln)Q就会调用Java Script
环境~省的toString()。反q来Q你也可以重新定义JavaScript理解q个对象
的方法?/p>
很多JavaScript框架Q在实现“模李쀝机制的时候,利用了q个Ҏ。例?br />他们用这样定义一个FontElement对象Q?br />//---------------------------------------------------------
// 利用toString()实现模板机制的简单原?br />//---------------------------------------------------------
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('q是一个测试?);
// 留意下面q行代码的写?br />document.writeln(obj);
5). functioncd
========================
javascript函数h很多Ҏ,除了面向对象的部分之?q在后面讲述)Q它?br />已的一些独特特性应用也很广泛?/p>
首先javascript中的每个函数Q在调用q程中可以执有一个arguments对象。这?br />对象是由脚本解释环境创徏的,你没有别的方法来自己创徏一个arguments对象?/p>
arguments可以看成一个数l:它有length属性,q可以通过arguments[n]的方?br />来访问每一个参数。然而它最重要的,却是可以通过 callee 属性来得到正在执行
的函数对象的引用?/p>
接下的问题变得很有趣QFunction对象有一?caller 属性,指向正在调用当前
函数的父函数对象的引用?/p>
——我们已l看刎ͼ我们可以在JavaScript里面Q通过callee/caller来遍历执?br />期的调用栈。由于arguments事实上也是Function的一个属性,因此我们事实上也
能遍历执行期调用栈上的每一个函数的参数。下面的代码是一个简单的CZQ?/p>
//---------------------------------------------------------
// 调用栈的遍历
//---------------------------------------------------------
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('调用参数Q?lt;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>');
// 上一U?br />foo = foo.caller;
}
}
// q行试
foo1(1, 2);
2. JavaScript面向对象的支?br />--------
在前面的例子中其实已l讲Cobjectcd的“类型声明”与“实例创建”?br />在JavaScript中,我们需要通过一个函数来声明自己的objectcdQ?br />//---------------------------------------------------------
// JavaScript中对象的cd声明的Ş式代?br />// (以后的文档中Q“对象名”通常用MyObject来替?
//---------------------------------------------------------
function 对象?参数? {
this.属?= 初始?
this.Ҏ = function(Ҏ参数? {
// Ҏ实现代码
}
}
然后Q我们可以通过q样的代码来创徏q个对象cd的一个实例:
//---------------------------------------------------------
// 创徏实例的Ş式代?br />// (以后的文档中Q“实例变量名”通常用obj来替?
//---------------------------------------------------------
var 实例变量?= new 对象?参数?;
接下来我们来看“对象”在JavaScript中的一些具体实现和奇怪特性?/p>
1). 函数在JavaScript的面向对象机制中的五重n?br />------
“对象名”——如MyObject()——这个函数充当了以下语言角色Q?br />(1) 普通函?br />(2) cd声明
(3) cd的实?br />(4) cd?br />(5) 对象的构造函?/p>
一些程序员(例如DelphiE序?习惯于类型声明与实现分开。例如在delphi
中,Interface节用于声明类型或者变量,而implementation节用于书写类?br />的实C码,或者一些用于执行的函数、代码流E?/p>
但在JavaScript中,cd的声明与实现是在一L。一个对象的cd(c?
通过函数来声明,this.xxxx表明了该对象可具有的属性或者方法?/p>
q个函数的同时也是“类引用”。在JavaScriptQ如果你需要识别一个对?br />的具体型别,你需要执有一个“类引用”。——当Ӟ也就是这个函数的?br />字。instanceof q算W就用于识别实例的类型,我们来看一下它的应用:
//---------------------------------------------------------
// JavaScript中对象的cd识别
// 语法: 对象实例 instanceof cd?br />//---------------------------------------------------------
function MyObject() {
this.data = 'test data';
}
// q里MyObject()作ؓ构造函C?br />var obj = new MyObject();
var arr = new Array();
// q里MyObject作ؓcd用?br />document.writeln(obj instanceof MyObject);
document.writeln(arr instanceof MyObject);
================
(未完待箋)
================
接下来的内容Q?/p>
2. JavaScript面向对象的支?br />--------
2). 反射机制在JavaScript中的实现
3). this与with关键字的使用
4). 使用in关键字的q算
5). 使用instanceof关键字的q算
6). 其它与面向对象相关的关键?/p>
3. 构造与析构
4. 实例和实例引?/p>
5. 原型问题
6. 函数的上下文环境
7. 对象的类型检查问?/p>
2). 反射机制在JavaScript中的实现
------
JavaScript中通过for..in语法来实C反射机制。但是JavaScript中ƈ?br />明确区分“属性”与“方法”,以及“事件”。因此,对属性的cd考查在JS
中是个问题。下面的代码单示例for..in的用与属性识别:
//---------------------------------------------------------
// JavaScript中for..in的用和属性识?br />//---------------------------------------------------------
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本n是没有事?Event)pȝ的。?br />常我们在JavaScript用到的onclick{事Ӟ其实是IE的DOM模型提供的。从更内?br />的角度上ԌIE通过COM的接口属性公布了一l事件接口给DOM?/p>
有两个原因,使得在JS中不能很好的识别“一个属性是不是事g”:
- COM接口中本w只有方法,属性与事gQ都是通过一lget/setҎ来公布的?br />- JavaScript中,本nq没有独立的“事件”机制?/p>
因此我们看到event的识别方法,是检属性名是否是以'on'字符串开??On'开
头的是Qomo的约?。接下来Q由于DOM对象中的事g是可以不指定处理函数的,q?br />U情况下事g句柄为null?Qomo采用相同的约?Q在另外的一些情况下Q用户可
能象obj2q样Q定义一个gؓ undefined的事件。因此“事件”的判定条g被处?br />成一个复杂的表达式:
("属性以on/On开? && ("gؓnull/undefined" || "cd为function"))
另外Q从上面的这D代码的q行l果来看。对DOM对象使用for..inQ是不能列D?br />对象Ҏ来的?/p>
最后说明一炏V事实上Q在很多语言的实CQ“事件”都不是“面向对象”的?br />aҎ,而是由具体的~程模型来提供的。例如Delphi中的事g驱动机制Q是由Win32
操作pȝ中的H口消息机制来提供,或者由用户代码在Component/Class中主动调?br />事g处理函数来实现?/p>
“事件”是一个“如何驱动编E模型”的机制Q问题,而不是语a本n的问题。然
而以PME(property/method/event)为框架的OOP概念Q已l深入h心,所以当~程?br />a或系l表现出q些Ҏ来的时候,已l没人关心“eventI竟是谁实现”的了?/p>
3). this与with关键字的使用
------
在JavaScript的对象系l中Qthis关键字用在两U地方:
- 在构造器函数中,指代新创建的对象实例
- 在对象的Ҏ被调用时Q指代调用该Ҏ的对象实?/p>
如果一个函数被作ؓ普通函?而不是对象方?调用Q那么在函数中的this关键?br />指向window对象。与此相同的Q如果this关键字不在Q何函CQ那么他也指?br />window对象?/p>
׃在JavaScript中不明确区分函数与方法。因此有些代码看h很奇怪:
//---------------------------------------------------------
// 函数的几U可能调用Ş?br />//---------------------------------------------------------
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: 作ؓ对象Ҏ的调?br />obj1.foo();
obj2.foo();
// 试3: 函C为“指定对象的”方法调?br />foo.call(obj1);
foo.apply(obj2);
在上面的代码里,obj1/obj2对foo()的调用是很普通的调用Ҏ。——也?br />是在构造器上,一个函数指定ؓ对象的方法?/p>
而测?中的call()与apply()比较特D?/p>
在这个测试中Qfoo()仍然作ؓ普通函数来调用Q只是JavaScript的语aҎ?br />允许在call()/apply()Ӟ传入一个对象实例来指定foo()的上下文环境中所
出现的this关键字的引用。——需要注意的是,此时的foo()仍旧是一个普?br />函数调用Q而不是对象方法调用?/p>
与this“指C用该Ҏ的对象实例”有些类同的Qwith()语法也用于限?br />“在一D代码片D中默认使用对象实例”。——如果不使用with()语法Q那
么这D代码将受到更外层with()语句的媄响;如果没有更外层的with()Q那
么这D代码的“默认用的对象实例”将是window?/p>
然而需要注意的是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>');
你不能指望这L代码在调用结束后Q会使obj2.value属性置gؓ8。这几行
代码的结果是Qwindow对象多了一个value属性,q且gؓ8?/p>
with(obj){...}q个语法Q只能限定对obj的既有属性的dQ而不能主动的
声明它。一旦with()里的对象没有指定的属性,或者with()限定了一个不是对
象的数据Q那么结果会产生一个异常?/p>
4). 使用in关键字的q算
------
除了用for..in来反对象的成员信息之外QJavaScript中也允许直接用in
关键字去对象是否有指定名字的属性?/p>
in关键字经常被提及的原因ƈ不是它检属性是否存在的能力Q因此在早期
的代码中Q很多可喜欢用“if (!obj.propName) {}?q样的方式来propName
是否是有效的属性。——很多时候,有效性比“是否存有该属性”更
有实用性。因此这U情况下Qin只是一个可选的、官方的Ҏ?/p>
in关键字的重要应用是高速字W串索。尤其是在只需要判定“字W串是否
存在”的情况下。例?0万个字符Ԍ如果存储在数l中Q那么检索效率将?br />极差?br />//---------------------------------------------------------
// 使用对象来检?br />//---------------------------------------------------------
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;
}
q种使用关键字in的方法,也存在一些限制。例如只能查扑֭W串Q而数
l元素可以是L倹{另外,arrayToObject()也存在一些开销Q这使得?br />不适合于频J变动的查找集。最后,(我想你可能已l注意到?使用对象
来查扄时候ƈ不能准确定位到查找数据,而数l中可以指向l果的下标?/p>
八、JavaScript面向对象的支?br />~~~~~~~~~~~~~~~~~~
(l?
2. JavaScript面向对象的支?br />--------
(l?
5). 使用instanceof关键字的q算
------
在JavaScript中提供了instanceof关键字来实例的cd。这在前面讨
论它的“五重n份”时已经讲过。但instanceof的问题是Q它L列D整个
原型链以类?关于原型l承的原理在“构造与析构”小节讲q?Q如Q?br />//---------------------------------------------------------
// 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>');
我们看到Qobj1与obj2都是MyObject的实例,但他们是不同的构造函C?br />的。——注意,q在面向对象理论中正的Q因为obj2是MyObject的子cd
例,因此它具有与obj1相同的特性。在应用中这是obj2的多态性的体现之一?/p>
但是Q即便如此,我们也必面临这L问题Q如何知道obj2与obj1是否?br />相同cd的实例呢Q——也是_q构造器都相同?
instanceof关键字不提供q样的机制。一个提供实现这U检的能力的,?br />Object.constructor属性。——但请先CQ它的用远比你惌的要难?/p>
好的Q问题先到这里。constructor属性已l涉及到“构造与析构”的问题Q?br />q个我们后面再讲。“原型扎쀝、“构造与析构”是JavaScript的OOP?br />的主要问题、核心问题,以及“致命问题”?/p>
6). null与undefined
------
在JavaScript中,null与undefined曾一度我迷惑。下面的文字Q有利于
你更清晰的认知它(或者让你更qh)Q?br />- null是关键字Qundefined是Global对象的一个属性?br />- null是对?I对? 没有M属性和Ҏ)Qundefined是undefinedc?br />型的倹{试试下面的代码Q?br />document.writeln(typeof null);
document.writeln(typeof undefined);
- 对象模型中,所有的对象都是Object或其子类的实例,但null对象例外Q?br />document.writeln(null instanceof Object);
- null“等?==)”于undefinedQ但不“全{?===)”于undefinedQ?br />document.writeln(null == undefined);
document.writeln(null == undefined);
- q算时null与undefined都可以被cd转换为falseQ但不等gfalseQ?br />document.writeln(!null, !undefined);
document.writeln(null==false);
document.writeln(undefined==false);
八、JavaScript面向对象的支?br />~~~~~~~~~~~~~~~~~~
(l?
3. 构造、析构与原型问题
--------
我们已经知道一个对象是需要通过构造器函数来生的。我们先C几点Q?br />- 构造器是一个普通的函数
- 原型是一个对象实?br />- 构造器有原型属性,对象实例没有
- (如果正常地实现承模型,)对象实例的constructor属性指向构造器
- 从三、四条推出:obj.constructor.prototype指向该对象的原型
好,我们接下来分析一个例子,来说明JavaScript的“承原型”声明,?br />及构造过E?br />//---------------------------------------------------------
// 理解原型、构造、承的CZ
//---------------------------------------------------------
function MyObject() {
this.v1 = 'abc';
}
function MyObject2() {
this.v2 = 'def';
}
MyObject2.prototype = new MyObject();
var obj1 = new MyObject();
var obj2 = new MyObject2();
1). new()关键字的形式化代?br />------
我们先来看“obj1 = new MyObject()”这行代码中的这个new关键字?/p>
new关键字用于生一个新的实例(说到q里补充一下,我习惯于把保留字叫关?br />字。另外,在JavaScript中new关键字同时也是一个运符Q,q个实例的缺省属?br />中,(臛_)会执有构造器函数的原型属?prototype)的一个引?在ECMA Javascript
规范中,对象的这个属性名定义为__proto__)?/p>
每一个函敎ͼ无论它是否用作构造器Q都会有一个独一无二的原型对?prototype)?br />对于JavaScript“内|对象的构造器”来_它指向内部的一个原型。缺省时JavaScript
构造出一个“空的初始对象实?不是null)”ƈ使原型引用指向它。然而如果你l函
数的q个prototype赋一个新的对象,那么新的对象实例执有它的一个引用?/p>
接下来,构造过E将调用MyObject()来完成初始化。——注意,q里只是“初?br />化”?/p>
Z清楚地解释这个过E,我用代码形式化地描述一下这个过E:
//---------------------------------------------------------
// new()关键字的形式化代?br />//---------------------------------------------------------
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);
// q回对象
return _this;
}
所以我们看C下两点:
- 构造函?aFunction)本n只是对传入的this实例做“初始化”处理,?br />不是构造一个对象实例?br />- 构造的q程实际发生在new()关键?q算W的内部?/p>
而且Q构造函?aFunction)本nq不需要操作prototypeQ也不需要回传this?/p>
2). q户代码维护的原型(prototype)?br />------
接下来我们更深入的讨论原型链与构造过E的问题。这是Q?br />- 原型链是用户代码创徏的,new()关键字ƈ不协助维护原型链
以Delphi代码ZQ我们在声明l承关系的时候,可以用这L代码Q?br />//---------------------------------------------------------
// delphi中用的“类”类型声?br />//---------------------------------------------------------
type
TAnimal = class(TObject); // 动物
TMammal = class(TAnimal); // Z^动物
TCanine = class(TMammal); // 犬科的哺乛_?br />TDog = class(TCanine); // ?/p>
q时QDelphi的编译器会通过~译技术来l护一个承关p链表。我们可以?br />q类g下的代码来查询这个链表:
//---------------------------------------------------------
// delphi中用关系链表的关键代?br />//---------------------------------------------------------
function isAnimal(obj: TObject): boolean;
begin
Result := obj is TAnimal;
end;
var
dog := TDog;
// ...
dog := TDog.Create();
writeln(isAnimal(dog));
可以看到Q在Delphi的用户代码中Q不需要直接护承关pȝ链表。这是因
为Delphi是强cd语言Q在处理用class()关键字声明类型时Qdelphi的编译器
已经为用h造了q个l承关系链。——注意,q个q程是声明,而不是执?br />代码?/p>
而在JavaScript中,如果需要获知对象“是否是某个基类的子cd象”,那么
你需要手工的来维?与delphiq个例子cM?一个链表。当Ӟq个链表?br />叫类型承树Q而叫?对象?原型链表”。——在JS中,没有“类”类型?/p>
参考前面的JS和Delphi代码Q一个类同的例子是这P
//---------------------------------------------------------
// JS中“原型链表”的关键代码
//---------------------------------------------------------
// 1. 构造器
function Animal() {};
function Mammal() {};
function Canine() {};
function Dog() {};
// 2. 原型链表
Mammal.prototype = new Animal();
Canine.prototype = new Mammal();
Dog.prototype = new Canine();
// 3. CZ函数
function isAnimal(obj) {
return obj instanceof Animal;
}
var
dog = new Dog();
document.writeln(isAnimal(dog));
可以看到Q在JS的用户代码中Q“原型链表”的构徏Ҏ是一行代码:
"当前cȝ构造器函数".prototype = "直接父类的实?
q与Delphi一cȝ语言不同Q维护原型链的实质是在执行代码,而非声明?/p>
那么Q“是执行而非声明”到底有什么意义呢Q?/p>
JavaScript是会有编译过E的。这个过E主要处理的是“语法检错”、“语
法声明”和“条件编译指令”。而这里的“语法声明”,主要处理的就是函
数声明。——这也是我说“函数是W一cȝQ而对象不是”的一个原因?/p>
如下例:
//---------------------------------------------------------
// 函数声明与执行语句的关系(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);
q个CZ代码在JS环境中执行的l果是:
------------------------------------
1234
undefined
Exception: 'obj2' 未定?br />[object Object]
hi, obj
------------------------------------
问题是,testFoo()是在它被声明之前被执行的Q而同L“直接声明”的
形式定义的object变量Q却不能在声明之前引用。——例子中Q第二、三
个输入是不正的?/p>
函数可以在声明之前引用,而其它类型的数值必d声明之后才能被用?br />q说明“声明”与“执行期引用”在JavaScript中是两个q程?/p>
另外我们也可以发玎ͼ使用"var"来声明的时候,~译器会先确认有该变?br />存在Q但变量的g是“undefined”。——因此“testFoo(obj1)”不会发
生异常。但是,只有{到关于obj1的赋D句被执行q,才会有正常的输出?br />请对照第二、三与第四、五行输出的差异?/p>
׃JavaScript对原型链的维护是“执行”而不是“声明”,q说明“原?br />链是q户代码来l护的,而不是编译器l护的?/p>
p个推论,我们来看下面q个例子Q?br />//---------------------------------------------------------
// CZQ错误的原型?br />//---------------------------------------------------------
// 1. 构造器
function Animal() {}; // 动物
function Mammal() {}; // Z^动物
function Canine() {}; // 犬科的哺乛_?/p>
// 2. 构造原型链
var instance = new Mammal();
Mammal.prototype = new Animal();
Canine.prototype = instance;
// 3. 试输出
var obj = new Canine();
document.writeln(obj instanceof Animal);
q个输出l果Q我们看到一个错误的原型铑֯致的l果“犬U的Z^?br />物‘不是’一U动物”?/p>
Ҏ在于?. 构造原型链”下面的几行代码是解释执行的Q而不是象var?br />function那样是“声明”ƈ在编译期被理解的。解决问题的Ҏ是修攚w?br />行代码,使得它的“执行过E”符合逻辑Q?br />//---------------------------------------------------------
// 上例的修正代?部分)
//---------------------------------------------------------
// 2. 构造原型链
Mammal.prototype = new Animal();
var instance = new Mammal();
Canine.prototype = instance;
3). 原型实例是如何被构造过E用的
------
仍以DelphiZ。构造过E中Qdelphi中会首先创徏一个指定实例大的
“空的对象”,然后逐一l属性赋|以及调用构造过E中的方法、触发事
件等?/p>
JavaScript中的new()关键字中隐含的构造过E,与Delphi的构造过Eƈ不完全一致。但
在构造器函数中发生的行ؓ却与上述的类|
//---------------------------------------------------------
// JS中的构造过E?形式代码)
//---------------------------------------------------------
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();
如果以单个类为参考对象的Q这个构造过E中JavaScript可以拥有与Delphi
一样丰富的行ؓ。然而,׃Delphi中的构造过E是“动态的”,因此事实?br />Delphiq会调用父类(MyObject)的构造过E,以及触发父类的OnCreate()事g?/p>
JavaScript没有q样的特性。父cȝ构造过E仅仅发生在为原?prototype
属?赋值的那一行代码上。其后,无论有多个new MyObject2()发生Q?br />MyObject()q个构造器都不会被使用。——这也意味着Q?br />- 构造过E中Q原型对象是一ơ性生成的Q新对象只持有这个原型实例的引用
(q用“写复制”的机制来存取其属?Q而ƈ不再调用原型的构造器?/p>
׃不再调用父类的构造器Q因此Delphi中的一些特性无法在JavaScript中实现?br />q主要媄响到构造阶D늚一些事件和行ؓ。——无法把一些“对象构造过E中?br />的代码写到父cȝ构造器中。因为无论子cL造多次Q这ơ对象的构造过E根
本不会激zȝcL造器中的代码?/p>
JavaScript中属性的存取是动态的Q因为对象存取父cd性依赖于原型链表Q构?br />q程却是静态的Qƈ不访问父cȝ构造器Q而在Delphi{一些编译型语言中,(不
用读写器?属性的存取是静态的Q而对象的构造过E则动态地调用父类的构造函数?br />所以再一ơ请大家看清楚new()关键字的形式代码中的q一行:
//---------------------------------------------------------
// new()关键字的形式化代?br />//---------------------------------------------------------
function new(aFunction) {
// 原型引用
var _proto= aFunction.prototype;
// ...
}
q个q程中,JavaScript做的是“get a prototype_Ref”,而Delphi{其它语a?br />的是“Inherited Create()”?/p>
八、JavaScript面向对象的支?br />~~~~~~~~~~~~~~~~~~
(l?
4). 需要用L护的另一个属性:constructor
------
回顾前面的内容,我们提到q:
- (如果正常地实现承模型,)对象实例的constructor属性指向构造器
- obj.constructor.prototype指向该对象的原型
- 通过Object.constructor属性,可以obj2与obj1是否是相同类型的实例
与原型链要通过用户代码来维护prototype属性一P实例的构造器属性constructor
也需要用户代码维护?/p>
对于JavaScript的内|对象来_constructor属性指向内|的构造器函数。如Q?br />//---------------------------------------------------------
// 内置对象实例的constructor属?br />//---------------------------------------------------------
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';
}
// 试数据和相关代?br />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>');
在这个例子中Q我们发现constructor属性被实现得ƈ不完整。对于DOM对象、ActiveX对象
来说q个属性都没有正确的返回?/p>
切的说QDOMQ包括Image)对象与ActiveX对象都不是标准JavaScript的对象体pM的,
因此它们也可能会h自己的constructor属性,q有着与JavaScript不同的解释。因此,
JavaScript中不l护它们的constructor属性,是具有一定的合理性的?/p>
另外的一些单体对?而非构造器)Q也不具有constructor属性,例如“Math”和“Debug”?br />“Global”和“RegExp对象”。他们是JavaScript内部构造的Q不应该公开构造的l节?/p>
我们也发现实例obj的constructor指向function MyObject()。这说明JavaScriptl护了对
象的constructor属性。——这与一些h惌的不一栗?/p>
然而再接下来,我们发现MyObject2()的实例obj2的constructor仍然指向function MyObject()?br />管q很说不通,然而现实的如此。——这到底是ؓ什么呢Q?/p>
事实上,仅下面的代码Q?br />--------
function MyObject2() {
}
obj2 = new MyObject2();
document.writeln(MyObject2.prototype.constructor === MyObject2);
--------
构造的obj2.constructor正的指向function MyObject2()。事实上Q我们也会注意到q?br />U情况下QMyObject2的原型属性的constructor也正的指向该函数。然而,׃JavaScript
要求指定prototype对象来构造原型链Q?br />--------
function MyObject2() {
}
MyObject2.prototype = new MyObject();
obj2 = new MyObject2();
--------
q时Q再讉Kobj2Q将会得到新的原?也就是MyObject2.prototype)的constructor属性?br />因此Q一切很明了Q原型的属性媄响到构造过E对对象的constructor的初始设定?/p>
作ؓ一U补充的解决问题的手D,JavaScript开发规范中说“need to remember to reset
the constructor property'Q要求用戯行设定该属性?/p>
所以你会看到更规范的JavaScript代码要求q样书写Q?br />//---------------------------------------------------------
// l护constructor属性的规范代码
//---------------------------------------------------------
function MyObject2() {
}
MyObject2.prototype = new MyObject();
MyObject2.prototype.constructor = MyObject2;
obj2 = new MyObject2();
更外一U解决问题的ҎQ是在function MyObject()中去重置该倹{当Ӟq样会
得执行效率稍低一点点Q?br />//---------------------------------------------------------
// l护constructor属性的W二U方?br />//---------------------------------------------------------
function MyObject2() {
this.constructor = arguments.callee;
// or, this.constructor = MyObject2;
// ...
}
MyObject2.prototype = new MyObject();
obj2 = new MyObject2();
5). 析构问题
------
JavaScript中没有析构函敎ͼ但却有“对象析构”的问题。也是_管我们?br />知道一个对象什么时候会被析构,也不能截获它的析构过Eƈ处理一些事务。然而,
在一些不多见的时候,我们会遇到“要求一个对象立x构”的问题?/p>
问题大多数的时候出现在对ActiveX Object的处理上。因为我们可能在JavaScript
里创Z一个ActiveX ObjectQ在做完一些处理之后,我们又需要再创徏一个。?br />如果原来的对象供应?Server)不允许创建多个实例,那么我们需要在JavaScript
中确保先前的实例是已l被释放q了。接下来Q即使Server允许创徏多个实例Q?br />在多个实例间允许׃n数据(例如OS的授权,或者资源、文件的?Q那么我们在?br />实例中的操作可能会出问题?/p>
可能q是有h不明白我们在说什么,那么我就举一个例子:如果创徏一个Excel对象Q?br />打开文gQ,然后我们save它,然后关闭q个实例。然后我们再创徏Excel对象q打开
同一文g。——注意这时JavaScript可能q没有来得及析构前一个对象。——这时我?br />再想Saveq个文gQ就发现p|了。下面的代码CZq种情况Q?br />//---------------------------------------------------------
// JavaScript中的析构问题(ActiveX ObjectCZ)
//---------------------------------------------------------
<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()">创徏</button>
<button onclick="writeXLS()">重写</button>
</body>
在这个例子中Q在本地文g操作时ƈ不会出现异常。——最多只是有一些内存垃
圾而已。然而,如果strSaveLocation是一个远E的URLQ这时本地将会保存一?br />文g存取权限的凭证,而且同时只能一?q程?实例来开启该excel文档q存
储。于是如果反复点?重写"按钮Q就会出现异常?/p>
——注意,q是在SPS中操作共享文件时的一个实例的化代码。因此,它ƈ?br />“学术的”无聊讨论,而且工程中的实际问题?/p>
解决q个问题的方法很复杂。它涉及C个问题:
- 本地凭证的释?br />- ActiveX Object实例的释?/p>
下面我们先从JavaScript中对象的“失效”问题说赗简单的_
- 一个对象在其生存的上下文环境之外,即会失效?br />- 一个全局的对象在没有被执?引用)的情况下Q即会失效?/p>
例如Q?br />//---------------------------------------------------------
// JavaScript对象何时失效
//---------------------------------------------------------
function testObject() {
var _obj1 = new Object();
}
function testObject2() {
var _obj2 = new Object();
return _obj2;
}
// CZ1
testObject();
// CZ2
testObject2()
// CZ3
var obj3 = testObject2();
obj3 = null;
// CZ4
var obj4 = testObject2();
var arr = [obj4];
obj3 = null;
arr = [];
在这四个CZ中:
- “示?”在函数testObject()中构造了_obj1Q但是在函数退出时Q?br />它就已经d了函数的上下文环境,因此_obj1失效了;
- “示?”中QtestObject2()中也构造了一个对象_obj2q传出,?br />此对象有了“函数外”的上下文环?和生存周?Q然而由于函?br />的返回值没有被其它变量“持有”,因此_obj2也立卛_效了Q?br />- “示?”中QtestObject2()构造的_obj2被外部的变量obj3持用了,
q时Q直到“obj3=null”这行代码生效时Q_obj2才会因ؓ引用关系
消失而失效?br />- 与示?相同的原因,“示?”中的_obj2会在“arr=[]”这行代?br />之后才会失效?/p>
但是Q对象的“失效”ƈ不等会“释䏀。在JavaScriptq行环境的内部,?br />有Q何方式来切地告诉用户“对象什么时候会释放”。这依赖于JavaScript
的内存回收机制。——这U策略与.NET中的回收机制是类同的?/p>
在前面的Excel操作CZ代码中,对象的所有者,也就?EXCEL.EXE"q个q程
只能在“ActiveX Object实例的释䏀之后才会发生。而文件的锁,以及操作
pȝ的权限凭证是与进E相关的。因此如果对象仅是“失效”而不是“释䏀,
那么其它q程处理文g和引用操作系l的权限凭据时就会出问题?/p>
——有些h说这是JavaScript或者COM机制的BUG。其实不是,q是QS、テQ?br />和JavaScript之间的一U复杂关pLD的,而非独立的问题?/p>
Microsoft公开了解册U问题的{略Q主动调用内存回收过E?/p>
?微Y?JScript中提供了一个CollectGarbage()q程(通常UGCq程)Q?br />GCq程用于清理当前IE中的“失效的对象׃”,也就是调用对象的析构q程?/p>
在上例中调用GCq程的代码是Q?br />//---------------------------------------------------------
// 处理ActiveX ObjectӞGCq程的标准调用方?br />//---------------------------------------------------------
function writeXLS() {
//(?..)
excel.Quit();
excel = null;
setTimeout(CollectGarbage, 1);
}
W一行代码调用excel.Quit()Ҏ来得excelq程中止q出,q时׃JavaScript
环境执有excel对象实例Q因此excelq程q不实际中止?/p>
W二行代码excel为nullQ以清除对象引用Q从而对象“失效”。然而由?br />对象仍旧在函C下文环境中,因此如果直接调用GCq程Q对象仍然不会被清理?/p>
W三行代码用setTimeout()来调用CollectGarbage函数Q时间间隔设?1'Q只
是得GCq程发生在writeXLS()函数执行完之后。这样excel对象满了“能?br />GC清理”的两个条gQ没有引用和d上下文环境?/p>
GCq程的用,在用了ActiveX Object的JS环境中很有效。一些潜在的ActiveX
Object包括XML、VML、OWC(Office Web Componet)、flashQ甚臛_括在JS中的VBArray?br />从这一Ҏ看,ajax架构׃采用了XMLHTTPQƈ且同时要满“不切换面”的
Ҏ,因此在适当的时候主动调用GCq程Q会得到更好的效率用UI体验?/p>
事实上,即使用GCq程Q前面提到的excel问题仍然不会被完全解冟뀂因为IEq?br />~存了权限凭据。ə늚权限凭据被更新的唯一ҎQ只能是“切换到新的面”,
因此事实上在前面提到的那个SPS目中,我采用的Ҏq不是GCQ而是下面q一
D代码:
//---------------------------------------------------------
// 处理ActiveX Object旉用的面切换代码
//---------------------------------------------------------
function writeXLS() {
//(?..)
excel.Quit();
excel = null;
// 下面代码用于解决IE call Excel的一个BUG, MSDN中提供的Ҏ:
// setTimeout(CollectGarbage, 1);
// ׃不能清除(或同?|页的受信Q状? 所以将DSaveAs(){方法在
// 下次调用时无?
location.reload();
}
最后之最后,关于GC的一个补充说明:在IEH体被最化ӞIE会d调用一?br />CollectGarbage()函数。这使得IEH口在最化之后Q内存占用会有明显改善?/p>
八、JavaScript面向对象的支?br />~~~~~~~~~~~~~~~~~~
(l?
4. 实例和实例引?br />--------
?NET Framework对CTS(Common Type System)U定“一切都是对象”,q分为“值类型”和“引用类型”两U。其中“值类型”的对象在{换成“引用类型”数据的q程中,需要进行一个“装”和“拆”的q程?/p>
在JavaScript也有同样的问题。我们看到的typeof关键字,q回以下六种数据cdQ?br />"number"?string"?boolean"?object"?function" ?"undefined"?/p>
我们也发现JavaScript的对象系l中Q有String、Number、Function、Booleanq四U对象构造器。那么,我们的问题是Q如果有一个数字AQtypeof(A)的结果,到底会是'number'呢,q是一个构造器指向function Number()的对象呢Q?/p>
//---------------------------------------------------------
// 关于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属性来识别对象Q?有时)比typeof更加有效。因为“值类型数据”A1作ؓ一个对象来看待Ӟ与A2有完全相同的Ҏ?/p>
——除了与实例引用有关的问题?/p>
参考JScript手册Q我们对其它基础cd和构造器做相同考察Q可以发玎ͼ
- 基础cd中的undefined、number、boolean和stringQ是“值类型”变?br />- 基础cd中的array、function和objectQ是“引用类型”变?br />- 使用new()Ҏ构造出对象Q是“引用类型”变?/p>
下面的代码说明“值类型”与“引用类型”之间的区别Q?br />//---------------------------------------------------------
// 关于JavaScriptcdpȝ中的?引用问题
//---------------------------------------------------------
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
-----------
我们看到Q无论是{D?==)Q还是全{运?===)Q对“对象”和“值”的理解都是不一L?/p>
更进一步的理解q种现象Q我们知道:
- q算l果为值类型,或变量ؓ值类型时Q等?或全{?比较可以得到预想l果
- (即包含相同的数据,)不同的对象实例之间是不等?或全{??br />- 同一个对象的不同引用之间Q是{?==)且全{?===)?/p>
但对于StringcdQ有一点补充:ҎJScript的描qͼ两个字符串比较时Q只要有一个是值类型,则按值比较。这意味着在上面的例子中,代码“str1==obj1”会得到l果true。而全{?===)q算需要检变量类型的一致性,因此“str1===obj1”的l果q回false?/p>
JavaScript中的函数参数L传入值参Q引用类?的实?是作为指针g入的。因此函数可以随意重写入口变量,而不用担心外部变量被修改。但是,需要留意传入的引用cd的变量,因ؓ对它Ҏ调用和属性读写可能会影响到实例本w。——但Q也可以通过引用cd的参数来传出数据?/p>
最后补充说明一下,值类型比较会逐字节检对象实例中的数据,效率低但准确性高Q而引用类型只实例指针和数据cdQ因此效率高而准性低。如果你需要检两个引用类型是否真的包含相同的数据Q可能你需要尝试把它{换成“字W串值”再来比较?/p>
6. 函数的上下文环境
--------
只要写过代码Q你应该知道变量是有“全局变量”和“局部变量”之分的。绝大多数的
JavaScriptE序员也知道下面q些概念Q?br />//---------------------------------------------------------
// JavaScript中的全局变量与局部变?br />//---------------------------------------------------------
var v1 = '全局变量-1';
v2 = '全局变量-2';
function foo() {
v3 = '全局变量-3';
var v4 = '只有在函数内部ƈ使用var定义的,才是局部变?;
}
按照通常对语a的理解来_不同的代码调用函敎ͼ都会拥有一套独立的局部变量?br />因此下面q段代码很容易理解:
//---------------------------------------------------------
// JavaScript的局部变?br />//---------------------------------------------------------
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());
l果昄falseQ表明不?实例的方?调用q回的局部变量“obj1/obj2”是不相同?/p>
变量的局部、全局Ҏ与OOP的封装性中的“私?private)”、“公开(public)”具有类同性。因此绝大多数资料L以下面的方式来说明JavaScript的面向对象系l中的“封装权限别”问题:
//---------------------------------------------------------
// JavaScript中OOP装?br />//---------------------------------------------------------
function MyObject() {
// 1. U有成员和方?br />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. 公开成员和方?br />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)”是Ҏ一U存取“私有域”的“公开(public)”方法。“公开(public)”表明在(构?函数外可以调用和存取?/p>
除了上述的封装权限之外,一些文档还介绍了其它两U相关的概念Q?br />- 原型属性:Classname.prototype.propertyName = someValue
- (c?静态属性:Classname.propertyName = someValue
然而,从面向对象的角度上来Ԍ上面q些概念都很难自圆其_JavaScriptI竟是ؓ何、以及如何划分出q些装权限和概忉|的呢Q?/p>
——因为我们必L意到下面q个例子所带来的问题:
//---------------------------------------------------------
// JavaScript中的局部变?br />//---------------------------------------------------------
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>');
// 试?br />MyFoo.setValue.call(obj2, 'obj2');
document.writeln(MyFoo.getValue.call(obj2));
document.writeln(MyFoo.getValue.call(obj1));
document.writeln(MyFoo.getValue());
在这个测试代码中Qobj1/obj2都是Object()实例。我们用function.call()的方式来调用setValue/getValueQ得在MyFoo()调用的过E中替换this为obj1/obj2实例?/p>
然而我们发现“测试二”完成之后,obj2、obj1以及function MyFoo()所持有的局部变量都q回了“obj2”。——这表明三个函数使用了同一个局部变量?/p>
由此可见QJavaScript在处理局部变量时Q对“普通函数”与“构造器”是分别对待的。这U处理策略在一些JavaScript相关的资料中被解释作“面向对象中的私有域”问题。而事实上Q我更愿意从源代码一U来告诉你真相:q是对象的上下文环境的问题。——只不过从表面看去,“上下文环境”的问题被{嫁到对象的封装性问题上了?/p>
(在阅M面的文字之前Q?先做一个概忉|的说明Q?br />- 在普通函CQ上下文环境被window对象所持有
- 在“构造器和对象方法”中Q上下文环境被对象实例所持有
在JavaScript的实C码中Q每ơ创Z个对象,解释器将为对象创Z个上下文环境链,用于存放对象在进入“构造器和对象方法”时对function()内部数据的一个备份。JavaScript保证q个对象在以后再q入“构造器和对象方法”内部时QL持有该上下文环境Q和一个与之相关的this对象。由于对象可能有多个ҎQ且每个Ҏ可能又存在多层嵌套函敎ͼ因此q事实上构成了一个上下文环境的树型链表结构。而在构造器和对象方法之外,JavaScript不提供Q何访?该构造器和对象方法的)上下文环境的Ҏ?/p>
而言之:
- 上下文环境与对象实例调用“构造器和对象方法”时相关Q而与(普?函数无关
- 上下文环境记录一个对象在“构造函数和对象Ҏ”内部的U有数据
- 上下文环境采用链式结构,以记录多层的嵌套函数中的上下?/p>
׃上下文环境只与构造函数及其内部的嵌套函数有关Q重新阅d面的代码Q?br />//---------------------------------------------------------
// JavaScript中的局部变?br />//---------------------------------------------------------
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()的确可以讉KC于MyFoo()函数内部的“局部变量i”,但是׃setValue()Ҏ的执有者是MyFoo对象(C函数也是对象)Q因此MyFoo对象拥有MyFoo()函数的唯一一份“上下文环境”?/p>
接下来MyFoo.setValue.call()调用虽然为setValue()传入了新的this对象Q但实际上拥有“上下文环境”的仍旧是MyFoo对象。因此我们看到无论创建多个obj1/obj2Q最l操作的都是同一个私有变量i?/p>
全局函数/变量的“上下文环境”持有者ؓwindowQ因此下面的代码说明了“ؓ什么全局变量能被L的对象和函数讉K”:
//---------------------------------------------------------
// 全局函数的上下文
//---------------------------------------------------------
/*
function Window() {
*/
var global_i = 0;
var global_j = 1;
function foo_0() {
}
function foo_1() {
}
/*
}
window = new Window();
*/
因此我们可以看到foo_0()与foo_1()能同时访问global_i和global_j。接下来的推论是Q上下文环境军_了变量的“全局”与“私有”。而不是反q来通过变量的私有与全局来讨Z下文环境问题?/p>
更进一步的推论是:JavaScript中的全局变量与函敎ͼ本质上是window对象的私有变量与Ҏ。而这个上下文环境块,位于所?window对象内部?对象实例的上下文环境链表的顶端,因此都可能访问到?/p>
用“上下文环境”的理论Q你可以利地解释在本小节中Q有兛_量的“全局Q局部”作用域的问题,以及有关对象Ҏ的封装权限问题。事实上Q在实现JavaScript的C源代码中Q这个“上下文环境”被叫做“JSContext”,q作为函敎ͼҎ的第一个参C入。——如果你有兴,你可以从源代码中证实本小节所q的理论?/p>
另外Q《JavaScript权威指南》这本书中第4.7节也讲述了这个问题,但被叫做“变量的作用域”。然而重要的是,q本书把问题讲反了。——作者试囄“全局、局部的作用域”,来解释生这U现象的“上下文环境”的问题。因此这个小节显得凌p且难以自圆其说?/p>
不过?.6.3节Q作者也提到了执行环?execution context)的问题,q就与我们这里说的“上下文环境”是一致的了。然而更ȝ的是Q作者又读者引错了ҎQ试囄函数的上下文环境去解释DOM和ScriptEngine中的问题?/p>
但这本书在“上下文环境链表”的查询方式上的讲述Q是正确的而合理的。只是把q个叫成“作用域”有点不对,或者不妥?