论坛首页 Web前端技术论坛

JavaScript 对象性的构造分析 - 从现象到本质

浏览 25320 次
该帖已经被评为精华帖
作者 正文
   发表时间:2008-06-19  
- 写在前面,这篇文章的目的是讨论细节的JavaScript对象性的实现而不是概念性的东西,由此而延伸开的基于面向对象的编程思想。如果嫌篇推导过程太复杂的读者,可以跳过去先看结果
- 如果FF不能正常显示图片,请用IE浏览
- 知识储备:
  1. Web编程经验 (HTML, CSS, B/S交互形式)
  2. JavaScript 开发经验
  3. Object Oriented 的基本思想
- 大纲:
  1. Object Construct讨论JavaScript对象构造分析
  2. Class 讨论JavaScript的类性质
  3. Encapsulation 讨论JavaScript的封装性
  4. Object Oriented Implementation: Class Initialize, Extends 讨论JavaScript的面向对象的实现细节 - 构造类,继承
  5. Package & Namespace Management - Expose to Client 讨论JavaScript的包管理面向Client的应用
  6. Package & Namespace Management - Implementation 讨论JavaScript的包管理实现细节

-所有讨论基于JavaScript 1.5
Sample 1: Object Construct
					/*****
					***** SAMPLE1
					*****/
					function sample1 (){
						var f1 = {};
						f1.attr = "f1attr";
						
						var f2 = new Object();
						f2.attr = "f2attr";
						
						var f3 = function() {this.markAsConstructor = "It is o3's constructor"};
						f3.attr = "f3attr";
						
						var f4 = new Function();
						f4.attr = "f4attr";
						
						(function objTest() {
							alert("**Object Test**\n f1.constructor : " + f1.constructor
							+ "\nf2.constructor : " + f2.constructor
							+ "\nf3.constructor : " + f3.constructor
							+ "\nf4.constructor : " + f4.constructor
							+ "\nf1.constructor() : " + f1.constructor()
							+ "\nf2.constructor() : " + f2.constructor()
							+ "\nf3.constructor() : " + f3.constructor()
							+ "\nf4.constructor() : " + f4.constructor()
							+ "\nf1.attr : " + f1.attr + "\nf2.attr : " + f2.attr + "\nf3.attr : " + f3.attr);
						}());
						
						(function classTest() {
							var msg = "**Class Test**\n";							
							
							try {
								var o1 = new f1();
							}
							catch(e) {
								msg += e;
							}
							try {
								var o2 = new f2();
							}
							catch(e) {
								msg += "\n" + e;
							}
							var o3 = new f3();
							var o4 = new f4();
							
							msg += "\n o3.constructor : " + o3.constructor;							
							msg += "\n o4.constructor : " + o4.constructor;
							msg += "\n o3.constructor() : " + o3.constructor();
							msg += "\n o4.constructor() : " + o4.constructor();
							msg += "\n o3.attr : " + o3.attr;
							msg += "\n o4.attr : " + o4.attr;
							
							try {
								var oo3 = new o3();
							}
							catch(e) {
								msg += "\noo3 exception" + e;
							}
							alert(msg);
						}());
						
						(function constructorTest () {
							var msg = "** Constructor Test **\n";
							msg += "{}.constructor : " + {}.constructor;
							msg += "\nObject.constructor : " + Object.constructor;
							msg += "\nFunction.constructor : " + Function.constructor;
							msg += "\n{}.constructor() : " + {}.constructor();
							msg += "\nObject.constructor() : " + Object.constructor();
							msg += "\nFunction.constructor() : " + Function.constructor();
							alert(msg);							
							
						}());
						
						(function classTest_enhanced() {
							var msg = "**Class Test enhanced**\n";							
							var f3 = function() {return function(){this.constructorMark = "It is oo3's constructor"}};						
							var f4 = new Function("return function(){this.constructorMark =\"It is oo4's constructor\"}");

							var o3 = new f3();
							var o4 = new f4();
							
							msg += "\n o3.constructor : " + o3.constructor;							
							msg += "\n o4.constructor : " + o4.constructor;
							msg += "\n o3.constructor() : " + o3.constructor();
							msg += "\n o4.constructor() : " + o4.constructor();
							

							var oo3 = new o3();
							var oo4 = new o4();				
							msg += "\n---------------------- 分割线 ----------------------------";
							msg += "\n oo3.constructor : " + oo3.constructor;							
							msg += "\n oo4.constructor : " + oo4.constructor;
							msg += "\n oo3.constructor() : " + oo3.constructor();
							msg += "\n oo4.constructor() : " + oo4.constructor();
							
							alert(msg);
						}());
					}

在Sample1中,我们测试了JavaScript的4种对象定义方式:
var f1 = {},
var f2 = new Object(),
var f3 = function() {},
var f4 = new Function()
并且为每个对象增加了属性"attr", 在接下来的测试实例中,函数体objTest测试了4个对象的构造器和构造器运行结果,以及他们的属性值,结果为:

图1-1
里面发生了什么,而f1,f2,f3,f4又真正是什么呢?
图中[native code]部分是被编译的本地代码,我们无法得知内容,但是我们不需要关心内部是什么,不管他的定义是什么,运行结果才是我们真正需要的
在进行以下分析之前,我们需要以下前提讨论:
前提一
1. "."运算符能够访问对象的成员
2. "()"运算符能够使他前面一个函数体中的代码运行,前提是这个函数已经被正确定义并且分配空间

f1,f2 用.constructor的运算结果是function Object(){[native code]} 而
f3,f4 用.constructor的运算结果是function Function(){[native code]}
这样我们就知道,在变量f1,f2,f3,f4中有名为constructor的成员指向一个函数句柄,事实上,我们从字面意思可以猜测construtor是什么,而且这是被Js语言级所封装的成员,但是Js中的constructor到底是像Java的类中的构造函数,用于构造实例变量,还是他自身被构造的由上层环境所调用的呢?
接下来用"()"运算符运算他们,得到结果为:
f1,f2 的constructor()运行结果为[object Object]
f3,f4 的constructor()运行结果为function anonymous(){}
按照变量的定义,我们不难发现,f1,f2,f3,f4真正被赋值是"constructor()"的执行结果,而"constructor"是定义了构造该变量的方法。

接下来看测试函数classTest的执行结果:



图1-2
我们可以发现,使用var o3 = new f3()的时候,
o3 的 constructor 为 function() {this.markAsConstructor = "It is o3's constructor"}
而这恰好是我们定义的var f3 = function() {this.markAsConstructor = "It is o3's constructor"}
由此可见,对于某个变量的成员"constructor",指的是他自身被构造时,上层环境所调用的构造方法,也就是说它自身的构造函数,而construtor()则是构造方法返回的最终变量引用(该变量), 在code-level看,如果var x = new y(),那么x.constructor 就指向y.

既然我们清楚了这点,就让我们暂时跳过 图1-2 所显示(稍候会讨论这个测试)的classTest的结果,先看第3个函数体
constructorTest 的执行结果:


图1-3
在这里,我们可以发现
虽然{}, Object, Function, function都是JavaScript的保留字,但是在使用他们做变量定义的时候,功能仍然不同
{}, Object, Functoin事实上都是使用 "function" 关键字来定义并构造的类型,事实上,他们也是属于JavaScript的的对象,都具有构造器,能由上层环境所构建,只不过这个对象由JavaScript内部而产生,进一步说,我们使用function.constructor 则会报语法错误(有兴趣的读者可以自行测试),说明了真正的关键字就是"function",类似于"new" 或者 "="等运算符一样,JavaScript所有的对象性,或者说类型,都围绕这个"function"关键字而构建。
观察这几个类型的构造器运行的结果,可以发现
{} 的 构造结果是[object Object]
而Object和Function的构造结果则是functon anonymous(){},这种形式和我们直接写
var x = function (){}是一样的。x最终的构造结果也是function anonymous(){}
结合测试1,测试2,测试3
由此我们在基于面向对象的基础知识和对Js基本特性的理解上可以推测他们的行为:

推测一:
1. "function"关键字可以用来定义构造器
2. function anonymous(){}指定了一个匿名函数(构造器),但是内部实现细节和返回类型被封装起来不得而知(我们也无法从函数形式得知返回类型,因为JavaScript是弱类型语言)
3. 定义有"constructor"成员的对象x,则x可以由"new"关键字所创建(不是唯一创建途径,直接用"="换引用也可以,不过在oo性中暂且不讨论),它指向该构造器 x.constructor() 的运行结果
4. 变量x的constructor指定了它的自身构造器,如果 "x.constructor()" 的运行结果是以"function"定义,则x可以被"new"运算,否则不行。
5. "new" 可能是隐式的(由上层容器封装并提供保留字).

基于推测一,我们可以得出规则:
规则一
1. "function"关键字可以定义形式如"function(){}"的类型,该类型可以被"new"所运算,运算结果为返回function(){}体内的数据
2. [object Object]类型无法被"new"运算
3. 形如var x = new y(), 则x.constructor指向y

注:上述定义是基于Firefox的测试结果,IE和其他浏览器可能略有不同,所以我们暂时给出[object Object]是一个通用于所有浏览器的类型定义

接下来我们按照 Q&A 的形式分析下测试中的例子:

Q1: 使用var x = {}的时候发生了什么?
A1: 由图1-3可知,"{}" 具有构造器,所以事实上"var x = {}",的时候是运行了"{}"的自构造器"{}.constructor" 产生了一个"{}"的实例,对于这些JavaScript的保留字,可以看作是"new"运算隐式被封装在了语言的内部,因为我们可以从 "{}.constructor" 的运行结果看出事实上 "{}" 的构造器内部也使用"function"来定义。"{}.constructor()" 的运行结果为 [object Object],也就是说,"{}"实例 由上层环境所构建,返回类型为[object Object],然后将新的[object Object]类型实例引用赋值给 "x",  并且var x = new {}是不成立的,因为根据规则一,该类型是无法被"new"所运算,强制运行会抛出 "({}) is not a constructor" 异常,注意,这里没有抛语法错误,这也从一个侧面证明了要使用"new"关键字必须要用"function"关键字定义构造器

Q2: 使用 var x = new Object() 的时候发生了什么?
A2: 结合Q1,由图1-3可知,类似于"{}"的构造,"var x = new Object()" 中的 "Object" 关键字 也是由 Object.constructor 自构建而产生的一个实例, Object.constructor()的运行结果为 function anonymous(){}, 既然结果为 "function(){}"类型,根据规则一,那一定可以使用"new"进行运算,所以 "new Object()" 其实是 "Object" 的实例再次"new"运算,如果我们将前面隐式的 "new" 也看作一次 "new" 运算的话,事实上在语句 "var x = new Object()" 中进行了2次 "new" 运算。 至于 function anonymous(){} 的内部实现细节,我们不得而知,但是结合图1-1,我们可以从var f2 = new Object()的 f2.constructor() 运行结果来看,Object实例指向的的"function anonymous(){}"返回的是[object Object]类型,这也意味着,我们不能再次对f2进行"new"关键字的运算,结合图1-2,得到了证实,当强制使用var o2 = new f2()的时候,会抛出"f2 is not a constructor"异常. 从这个例子中也可以推断,var x = Object 一样能运行成功,返回为 "Object" 类型的实例。而 var x = Object, var y = new x();的效果和var y = new Object()是一样的。有兴趣的读者可以当作例子去练习一下。

Q3: 使用var x = new Function() 的时候发生了什么?
A3: 由图1-3,结合Q1,Q2,不难得知,"Function" 关键字的构造过程和"Object" 完全一样,唯一不同的地方是 Function.constructor()构造器的运行结果的function anonymous(){}, 这个匿名函数返回的不是[object Object]而是又一个function anonymous(){}, 所以在 "var x = new Function()" 语句运行的时候, 运行过程为:

1. "Function" 关键字 产生了一个 "Function"的实例,构造器为Function.constructor,返回类型为 function anonymous(){}
2. "new Function()"运行了 "Function"的实例指向的 function anonymous(){},返回另一个function anonymous(){}, 并把此function anonymous(){}的引用赋值给x,和 Object 不同,Object在此返回了一个[object Object]类型
3. x最终指向一个function anonymous(){}

如果理解了前面2个问题的话,那自然不难理解这个问题 - 结果为我们可以使用"new" 再次对 x 进行运算,结合图1-2,我们可以发现,o4 = new f4()不会抛出异常。并且,Prototype.js的作者很明显是注意到这点的,所以在写Class定义的时候也是利用这个原理,有关这部分我们可以在后面讨论。

从上述3个问题中,我们还可以得出一个结论:
结论一
1. "function" 是关键字
2. "{}", "Object", "Function"是类型
3. "{}", "Object", "Function" 在 Runtime的时候,并不是一个作为一个类型存在,而是在出现的地方创建了它们各自的实例

-------------------------------------- 休息一下 ------------------------------------

利用休息的时间我们看一个很有趣的地方,看下图:


图1-4

注意图1-4中,我们定义了
var f3 = function() {this.markAsConstructor = "It is o3's constructor"};
var o3 = new f3();
在运行测试函数classTest的时候 (以红色区域线标注)
o3.constructor()运行居然是"undefined"! 可能有人看到这个结果可能会说:Oh, My God! 你刚刚不是说在 "new" 的时候 "=" 运算符左边的变量最终得到的结果是它自身的constructor()执行结果?这里怎么会是"undefined"的呢?那是不是说变量o3也是undefined呢?
这里出现了2个问题:
q1. constructor()为什么会返回undefined?
q2. o3到底是什么?

我们观察o3.constructor,可以发现,o3.constructor()函数体内并没有 "return" 语句来返回任何信息,这样的结果必然是 "undefined",但是由于o3 = new f3(), 因此具有constructor成员,一个 "undefined"的变量具有 "constructor" 成员,这显然是不可能的,为了避免出现抛出 "undefined"的异常,JS非常聪明的使用了 [object Object] 类型来避免了这样的情况,成功的将o3定义为[object Object]类型。我们可以使用alert(o3)直接观察o3是什么,结果显示o3为[object Object]类型

同样的,当我们定义
var f3 = function() {this.markAsConstructor = "It is o3's constructor";return function(){}};
o3 这个时候为 "function(){}" 类型

-------------------------------------- 休息结束 ------------------------------------

接下来我们看最后的测试函数classTest_enhanced()的测试结果,在这个测试中,我们为构造函数加入了返回值,返回依然是function(){}



图1-5

首先看变量定义
var f4 = new Function("return function(){this.constructorMark =\"It is oo4's constructor\"}");

在这里我们将字符串 "return function(){this.constructorMark =\"It is oo4's constructor\"}" 作为参数传入到 new Function()中,根据我们前面的分析,"Function" 在这里(Runtime的时候)首先创建了一个 "Function" 的实例,Function 最终指向的是一个 function anonymous(){}
同时根据规则一,我们可以知道,f4 的 constructor 指向为 Function 的实例,我们可以猜测f4.constructor 的形式大致为:
伪代码:
f4.constructor = function Function() {
  ...                         - native code 1
  return function anonymous() {
    ...                       - native code 2
    return function anonymous(){
      this.constructorMark ="It is oo4's constructor"
    }
  }
}


其中... 部分是native code, 被编译过的本地代码(先不讨论是不是2进制代码或者是中间代码),所以我们使用 alert()直接看类型的时候,由于这部分代码为编译代码无法直接显示,所以JS只会显示[native code]标注,稍候我们会来讨论这个 "native code" 到底是什么,但是由此我们可先得出一个推测和一个疑问:

推测二:(基于Firefox)
1. 在使用 "alert" 测试变量类型和内部代码时,如果代码是被编译过的,则只会以[native code]标注显示
2. 基于1,如果强制同时显示未编译的code(plain text模式)和已编译的code,则为保证一致性,则只会以[native code]标注显示

疑问一:
1. [native code]到底是什么?

基于以上推测,我们先看图1-5的测试结果:
f4.constructor = function Function() {
  [native code]
}

这个结果证实了我们的猜想,同时也证明了f4.constructor确实是指向 Function 的一个实例
那么,我们得知f4.constructor的运行结果f4.constructor() 的形式大致应该为:
伪代码:
f4.constructor() = function anonymous() {
    ...                       - native code 2
    return function anonymous(){
      this.constructorMark ="It is oo4's constructor"
    }
}


我们看图1-5的测试结果,结合推测1-2可以发现这个结果显然是正确的
那么如下变量定义:
var o4 = new f4();

o4 的形式又是什么呢?根据o4.constructor指向f4,而f4指向f4.constructor()的运行结果我们不难发现,o4的形式大致如:
伪代码:
o4.constructor = f4.constructor() = function anonymous() {
    ...                       - native code 2
    return function anonymous(){
      this.constructorMark ="It is oo4's constructor"
    }
}



伪代码:
o4 = o4.consturctor() = function anonymous() {
    this.constructorMark ="It is oo4's constructor"
}


所以
o4 = function anonymous() {
 this.constructorMark ="It is oo4's constructor"
}

图1-5的测试结果证实了我们的猜想

接下来我们再用变量定义
var oo4 = new o4();

不难推断出oo4的constructor和oo4的最终形式:
伪代码:
oo4.constructor = function () {
  this.constructorMark ="It is oo4's constructor"
}

因为 oo4.constructor() 返回为undefined,不难得知 oo4 的最终形式为[object Object]类型(参阅 休息一下)
细心的读者可能会发现这里的问题:
既然oo4.constructor 指向 o4,为什么在以上推测中
o4 = function anonymous() {
this.constructorMark ="It is oo4's constructor"
}

oo4.constructor = function () {
  this.constructorMark ="It is oo4's constructor"
}

这里给出一个疑问:
疑问二:
1. x.constructor为什么会发生变化?
2. 形如var x = new y()形式,到底x.constructor是不是指向y呢?
3. 运行结果为什么是正确的?

有关这些疑问,我们稍候会做解答
我们会发现,在这个例子中,我们用的例子都是 "构造返回",就是说,在一个构造器中返回另一个构造器,这样使得利用构造器产生的新类型,可以再次的被 "new" 运算符所运算。事实上,现在大多数流行的Ajax的Framework,都直接的或者间接的使用了这个特性

在继续进行下述分析前,我们先要明确下我们在开篇所定义的前提一-2
前提一-2: "()"运算符能够使他前面一个函数体中的代码运行,前提是这个函数已经被正确定义并且分配空间

事实上,JS不论在任何情况下,都是按照operator的优先级进行计算,而"()"的优先级非常之高,下面列出的一张表说明了JS运算符的优先顺序:

引用自 http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide:Operators

从上表可以发现 "()" 运算符的优先级甚至高于 "new" 所以在变量定义
var o4 = new f4();

的时候,事实上,JS会先运行f4(),由于
伪代码:
f4.constructor() = function anonymous() {
    ...                       - native code 2
    return ***function anonymous(){
      this.constructorMark ="It is oo4's constructor"
    }***
}


在运行完成后,直接将以"***"标注部分返回给了o4, 按照这个推断
我们将定义从
var o4 = new f4();

改变成
var o4 = f4();

应该不会影响运行结果
我们将代码中的var o4 = new f4() 替换成var o4 = f4(); 重新看测试结果,惊奇的发现,测试结果和图1-5完全一样(这部分替换有兴趣的读者可以自行测试)



图1-6

甚至于
我们将最顶层的类型
var f4 = new Function("return function(){this.constructorMark =\"It is oo4's constructor\"}");

替换成(去掉"new"关键字)
var f4 = Function("return function(){this.constructorMark =\"It is oo4's constructor\"}");


运行结果也是一样的。

这个结果说明了什么?是不是"new" 运算符完全没用呢?聪明的读者会想到,假设JavaScript的开发者定义了一个完全无用的运算符,那是不是很囧。。。。
在这个时候我们会从面向对象的设计角度上来想 和 "new" 最有关系的当然是 "this","this" 通常在程序里指向某个实例,那么,当我们不用"new"来运行时,"this"是否一样能运行成功呢?

我们来看下列的2个测试(由于篇幅时间关系,我简略的将测试结果写下来而不是用截图的形式给读者一个明确的证据,这方面有经验的读者可以自行测试)
测试一:
						var f = function(b) {
							this.a = b;
							return null;
						}
						
						/**
							测试1 - 不改变constructor()的参数值
						**/
						var ff = new f("b");						
						
						alert("** Test 1 **\n"
						+ "ff: " + ff + "\n"
						+ "ff.a : " + ff.a
						+ "\nff.constructor() : " + ff.constructor("b"));
						
						/**
							测试2 - 改变constructor()的参数值
						**/
						
						ff.constructor("c");
						alert("** Test 2 **\n"
						+"ff: " + ff + "\n"
						+ "ff.a : " + ff.a
						+ "\nff.constructor() : " + ff.constructor("c"));						


测试结果为:
**Test 1 **
ff: [object Object]
ff.a : b
ff.constructor() : null

** Test 2 **
ff: [object Object]
ff.a : c
ff.constructor() : null

我们发现,虽然ff.cosntructor()的返回结果为null,但是ff是个[object Object]类型,同时,ff.constructor()确实能改变ff实例成员的值。

这个结果说明了,如果x.constructor()返回的并不是function() {},则x并不指向x.constructor()的执行结果
这样,我们就进一步解释了在 "休息一下" 中提出的问题:为什么x.constructor()返回是 "undefined" 而x确是一个[object Object]类型呢

我们看另一个测试:
						var f = function(b) {
							this.a = b;
							var ff = function(cc) {
								this.c = cc;
							}
							ff.e  = b;
							return ff;
						}
						
						/**
							测试1 - 不改变constructor()的参数值
						**/
						var ff = new f("b");						
						
						alert("** Test 1 **\n"
						+ "ff: " + ff + "\n"
						+ "ff.a : " + ff.a
						+ "\nff.e : " + ff.e
						+ "\nff.constructor() : " + ff.constructor("b"));
						
						/**
							测试2 - 改变constructor()的参数值
						**/
						
						ff.constructor("c");
						alert("** Test 2 **\n"
						+"ff: " + ff			
						+ "\nff.a : " + ff.a
						+ "\nff.e : " + ff.e
						+ "\nff.constructor() : " + ff.constructor("c"));						


运行结果为:
** Test 1 **
ff: function (cc) {
    this.c = cc;
}
ff.a : undefined
ff.e : b
ff.constructor() : function anonymous() {
    b;
}

** Test 2 **
ff: function (cc) {
    this.c = cc;
}
ff.a : undefined
ff.e : b
ff.constructor() : function anonymous() {
    c;
}

我们可以发现几个特点:
1. "ff.a" 丢失了
2. ff.e 没有改变
3. ff.constructor()发生了变化,并且ff.constructor()不再控制ff成员的值

我们可以从上述现象中得出以下结论:
1. ff的指向发生了变化
2. ff没有实例产生,如果有,则this.a的定义必然会有值
3. ff.constructor 在"new"运算的时候和指针发生变化时,是不一样的,如果有,则必然会修改ff.e的值

与此同时,我们利用"prototype"来测试
在进行"prototype"测试前,我们要先清楚:
prototype 不是一个JavaScript 的保留字,也就是说,可以定义
var prototype = "hello world"这样,有兴趣的读者可以测试
则prototype + "." 运算符,指向了一个成员

观察 ff.prototype.constructor,会发现
ff.prototype.constructor  = function (cc) {
    this.c = cc;
}

这个结果和ff的直接指向是一样的

我们引入一个重要的概念: "类型变量" 和 "实例变量",用过Java 范型的人应该都了解,在class-level上面,类型变量也可以作为参数传递,然后在application-level上面用实例传递

所以我们会发现,其实在我们定义某个构造器
var f = function() {
   this.a = "b"
   return function() {
   }
}
var ff = new f()


f其实作为类型变量而产生,从而忽略自身的 "this",因为类型没有 "this", 同时,f.constructor 作为更high-level的类型产生器,在产生类型后,并不对已有的类型做进一步的控制

我们来看代码
						var f = function(b) {
							this.a = b;
							var ff = function(cc) {
								this.c = cc;
							}
							ff.e  = b;
							return ff;
						}

究竟发生了什么?
事实上,在var ff = new f()的时候,还是先用f()来判断 return 到底是什么,当了解了最终return 是个类型的时候,在运行"new" 的时候,使用完全不同的机制来产生"ff";
而且在声明var f = function() {
  ...
}
的时候,
f 的实例构造函数事实上被存放在 f.prototype.constructor 中。

1. 由于ff是类型,所以忽略构造函数中全部的"this"变量
2. 使用类型构造器构造并分配空间,该构造返回类型function anonymous(){}为一个通用类型,声明了"ff"是一个类型,由于声明为类型,因此ff具有prototype成员,在这个时候,由于 ff 由更上层的类型构造器产生,则 ff.constructor 和 ff.constructor() 发生了变化。
3. 使用f的构造函数 f.prototype.constructor 来初始化ff(因为语法上ff是f的实例):
  a. 将ff 指向 f.prototype.constructor()的执行结果
  b. 将f.prototype.constructor()的执行结果赋值给ff.prototype.constructor

由此我们可以得出结论,如果某个变量作为一个类型,则该变量仅仅关注.prototype 成员用来做实例的构造,如果类型指向某个function(){},则类型的成员.prototype.constructor也必然指向该function(){}

我们继续看定义某个构造器
var f = function() {
   this.a = "b"
   return null;
}
var f = new f()


的时候发生了什么
事实上,在var ff = new f()的时候,由于f()的 return 值不是一个类型(function anonymous() {}), 则在使用"new" 运算的时候,会显示完全不同的结果:
1. "new" 为了新的实例分配了新的空间,并且使该变量的类型为"[object Object]",由于变量是实例而不是类型,所以该变量没有.prototype成员
2. 由于ff可以直接由类型实例化,不需要更上层的类型构造器构造,所以ff.constructor指向它的类型的构造函数 f.prototype.constructor,同时ff由ff.constructor构造初始化,同时,也可以由ff.constructor控制赋值
3. 全部的"this"指针在新实例内都指向该实例。
4. 忽略构造函数的"return"值,因为构造函数是为控制该实例而产生,不可能去修改实例的引用

由于ff.constructor就是类型ff.prototype.constructor,所以我们可以使用consturctor来修改实例的成员。
我们发现类型的构造过程和实例的构造过程是完全不一样的。

按照以上定义,我们不难发现,当定义var f = function() {
   return function() {
   }
}
var ff = new f();
构造器的返回为一个类型时
的时候,var ff = f();和var ff = new f();其实是完全等价的,这点我们在前面的例子中已经证实过了。

类型具有"prototype"成员,所以能够被"new"运算,而[object Object]类型由于没有"prototype"成员,无法被"new"运算。这点我们在规则一中已经能够被证实。

指出JS构造的时候有所不同,希望对大家有帮助,毕竟,JS过于灵活的特性给我们带来了相当大的不便。

有时间的话,我会讨论下后面几个话题:
  2. Class 讨论JavaScript的类性质
  3. Encapsulation 讨论JavaScript的封装性
  4. Object Oriented Implementation: Class Initialize, Extends 讨论JavaScript的面向对象的实现细节 - 构造类,继承
  5. Package & Namespace Management - Expose to Client 讨论JavaScript的包管理面向Client的应用
  6. Package & Namespace Management - Implementation 讨论JavaScript的包管理实现细节








  • JavaScript_sample.zip (35.7 KB)
  • 描述: 例子中的测试源代码包括一个简单的JS实现的包管理工具,测试代码仅在Firefox运行成功,包管理工具支持IE6.0,7.0,Firefox2.0+,Safari3.0+(Macintosh & window)
  • 下载次数: 142
   发表时间:2008-06-20  
IE下面也看不到图片,可能是你放置图片的网址禁止站外引用
你可以使用JavaEye博客提供的相册功能上传图片,然后在文章中使用这些图片
0 请登录后投票
   发表时间:2008-06-20  
我使用的是Google的Picasa发布的图片,我会尽快的解决这个问题
0 请登录后投票
   发表时间:2008-06-20  
我觉得楼主的钻研精神是好的 不过自己猜测终究不是办法
为什么不去看看ECMA标准文档呢

楼主的认识有些确实没错 但有些确实偏离了JS的本意 即使他们看上去可以得到证实
就像楼主讲的new f()和f()那个地方 其实有很多错误的(这么说希望楼主不要介意)
0 请登录后投票
   发表时间:2008-06-20  
做的分析基于大量的测试之上,确实只是一种推测,推测自然不会100%正确
做了大量的测试想得出一个合适的通用的解释是不容易的
要使得推论即合适已有的测试结果,又能符合未来的测试结果
就好像现在所有对于宇宙起源的推测,没有人能证实一样,除了你直接去问造物主。。
直接看ECMA的标准文档确实是最权威直接的证据,不过要是没有注意到JavaScript这里稀奇古怪的构造现象,可能还没人有兴趣去看那种枯燥的文档。。
欢迎大家提出不同的建议,而且大家也可以利用我已有的测试结果,发表自己的推论
至于f()和new f(),按照目前的测试结果来看,JS确实会根据返回类型的不同而构造不同的对象
谢谢评论
0 请登录后投票
   发表时间:2008-06-20  
alucardggg 写道
做的分析基于大量的测试之上,确实只是一种推测,推测自然不会100%正确
做了大量的测试想得出一个合适的通用的解释是不容易的
要使得推论即合适已有的测试结果,又能符合未来的测试结果
就好像现在所有对于宇宙起源的推测,没有人能证实一样,除了你直接去问造物主。。
直接看ECMA的标准文档确实是最权威直接的证据,不过要是没有注意到JavaScript这里稀奇古怪的构造现象,可能还没人有兴趣去看那种枯燥的文档。。
欢迎大家提出不同的建议,而且大家也可以利用我已有的测试结果,发表自己的推论
至于f()和new f(),按照目前的测试结果来看,JS确实会根据返回类型的不同而构造不同的对象
谢谢评论

问题时JS的造物主是可以直接问的......BE大人还在世 而且ECMA也说得很清楚......
不需要这样推测......

而且正因为你的推测有错误 我才提醒你的 比如
f()和new f()的区别 我可以大概说一下
new f()一定构造了一个Object作为this的值 这个Object的原型是f.prototype 它将作为f的this值
如果f返回值为object 那么表达式new f()返回值是和f()的返回值是相同的
否则new f()会返回this

还有很多地方 你的说法不能算错 但是其实思路是错的
0 请登录后投票
   发表时间:2008-06-20  
很遗憾,本来想精读全文的,
在第一次通读时,遇到这句话:
引用
var ff = f();和var ff = new f();其实是完全等价的

已经让我放弃了当初精读的想法
0 请登录后投票
   发表时间:2008-06-20  
可能没表达正确,意思是
在定义
var f = function() {
  return function() {
  }
}
的时候,以运行结果来看
var ff = new f()和var ff = f() 是等价的

但是如果定义
var f = function() {
  return null
}
这样来说,当然不是,为什么会这样,前面已经给出例子而且分析过了

事实上这点也很奇怪,说明JS确实是有"类型变量"存在的
0 请登录后投票
   发表时间:2008-06-20  
而且分析var f = new f()
从运算符优先顺序来看,确实要先执行()运算,
但是var f = new (f())这样的运行结果又不等同于 var f = new f()
更奇怪的是,JS 会根据f()的运行结果,来为f生成不同的类型
0 请登录后投票
   发表时间:2008-06-20  
alucardggg 写道
而且分析var f = new f()
从运算符优先顺序来看,确实要先执行()运算,
但是var f = new (f())这样的运行结果又不等同于 var f = new f()
更奇怪的是,JS 会根据f()的运行结果,来为f生成不同的类型

所以说要你看标准文档  人家说的很清楚
第一点 new f()是一个表达式 这里面()并非函数调用 且()是可以省略的参数列表
第二点 就
function f(){
    return function(){};
}
而言 new f()和f()也不是等价的 new f()构造了一个额外的对象作为this

第三点 不只是
function f(){
    return function(){};
}
只要返回值是object(null不算 null只是typeof是object) new f()的返回值就和f()一样
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics