`
alucardggg
  • 浏览: 8676 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

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

阅读更多
- 写在前面,这篇文章的目的是讨论细节的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
分享到:
评论
40 楼 wmingjian 2009-04-25  
只有一条评论,楼主精神可嘉。
1.不知道楼主是否知道obj.toString方法?
2.不知道楼主是否知道JS内建对象Boolean,Number,String,Object,Array,Function,RegExp等的字面量(literal)形式?(true,123,"abc",{a:"A"},[1,"a"],function(){},/\w+/)
3.下面这个没有楼主所说的函数调用运算符,又该如何解释这段代码?
function F(){}
var f = new F;
4.不知道楼主能够看懂下面这段模拟new运算符工作原理的代码?
function _new(clazz, args){
	var _this = clone(clazz.prototype);  //{}
	var obj = clazz.apply(_this, args);
	var type = typeof obj;
	if(type == "object" && obj !== null || type == "function")
		return obj;
	else
		return _this;
	/*  另一种写法
	if(obj === null || type == "undefined" || type == "boolean"
		|| type == "number" || type == "string"){
		return obj;
	}else{
		return _this;
	}
	*/
}

new clazz(...)
等价于
_new(clazz, ...)
注意,代码忽略了对参数细节的处理
39 楼 rmn190 2009-02-05  
谢谢楼主花这么大的精力来做如此深入的研究.

再次感谢.
38 楼 terrynoya 2008-07-10  
ham 写道
terrynoya 写道

function car(){
    this.name="toyota";
}
car.prototype.say=function(){
      alert(this.name);
}

1.car.say() 报"此对象不支持方法"
2.car().say() 报"undefined"为空
3.new car().say() 正确

1>.car.prototype.say=function....说明say()这个方法是只有car这个"类"所产生的对象所有的方法.并不是car的方法.
在car.say=function...中.say()才是car的方法.

2>.car().say()这样使用.实际上是调用了car()这个函数.而这个函数没有返回值,未返回任何东西.所以当.say()时,会报undefined为空.

3>.参照第一条,通过new car()就产生了一个car"类"的对象.自然就可以使用say()方法了.

terrynoya 写道

function car(){
	var cc=this;
	this.name="toyota";
	this.say=function(){
	   alert(this.name);
	}
	return cc;
}
var c=car();
c.say();

这样可以但用prototype就报错了,为什么呢?

这句话问的是什么意思..?我有点没搞懂哦.


感谢ham的回答,函数这种2位一体很能让我这种新手弄不清方向。。呵呵
37 楼 ham 2008-07-10  
terrynoya 写道

function car(){
    this.name="toyota";
}
car.prototype.say=function(){
      alert(this.name);
}

1.car.say() 报"此对象不支持方法"
2.car().say() 报"undefined"为空
3.new car().say() 正确

1>.car.prototype.say=function....说明say()这个方法是只有car这个"类"所产生的对象所有的方法.并不是car这个函数的方法.Ps:"函数"和"类"是同一样东东.
在car.say=function...中.say()才是car的方法.

2>.car().say()这样使用.实际上是调用了car()这个函数.而这个函数没有返回值,未返回任何东西.所以当.say()时,会报undefined为空.

3>.参照第一条,通过new car()就产生了一个car"类"的对象.自然就可以使用say()方法了.

terrynoya 写道

function car(){
	var cc=this;
	this.name="toyota";
	this.say=function(){
	   alert(this.name);
	}
	return cc;
}
var c=car();
c.say();

这样可以但用prototype就报错了,为什么呢?

这句话问的是什么意思..?我有点没搞懂哦.
36 楼 terrynoya 2008-07-10  
刚刚看到这个问题就看到这个帖子,f() 和 new f()

我作的小实验
function car(){
    this.name="toyota";
}
car.prototype.say=function(){
      alert(this.name);
}
1.car.say() 报"此对象不支持方法"
2.car().say() 报"undefined"为空
3.new car().say() 正确

我想请教1,2为什么错,为什么错的不一样?

谢谢!

还有
function car(){
var cc=this;
this.name="toyota";
this.say=function(){
   alert(this.name);
}
return cc;
}
var c=car();
c.say();
这样可以但用prototype就报错了,为什么呢?
35 楼 bbs 2008-07-04  
hax 写道
csf177 写道
- -!这样也可以靠上 你明明连词法关系都没搞对...... 人家new跟函数调用是两种表达式 只不过是长得很像 都带括号而已
算了不说了 能看懂ECMA262的人太少了 明明就那么几行英文 真无奈

设计一个貌似可爱的脚本能让一大半的使用者都看不懂标准文档 该说Brendan Eich水平高还是说他悲哀? 或者,二者兼有......


ECMA262规范不是BE大人写出来的,而是委员会产物。他只不过review过。如果要说BE写出来的,那当然是JavaScript的Referenece和Guide。

当然规范写成这样诘屈聱牙,BE同志自然不能完全脱离干系。但是其中内情,怕是只有委员会的当事人知道。

BE大人设计的比较精妙 所以语言比较难懂 跟规范写的如何没有关系
规范写的没什么问题 哪个语言标准都这样 该看不懂全都看不懂
34 楼 achun 2008-07-04  
<p>确实楼主的研究方法有问题.</p>
<p>你在IE和FF下分别试试这样的写法</p>
<pre name='code' class='js'>function f(){return 'f';}
var vf=function() {return 'vf';}

try{
var f1=new Function(f);
if(f1) alert(f1.toString());
}catch(e){
alert('can not new Function(f)');
}

try{
var f2=new Function(vf);
if(f2)
alert(f2.toString());
}catch(e){
alert('can not new Function(f)');
}
</pre>
<p> 至于标准怎么说我没有仔细看过,反正浏览器实现是不同的.</p>
33 楼 hax 2008-07-04  
BTW,楼主精神可嘉,希望你好好读Spec,天天向上。
32 楼 hax 2008-07-04  
csf177 写道
- -!这样也可以靠上 你明明连词法关系都没搞对...... 人家new跟函数调用是两种表达式 只不过是长得很像 都带括号而已
算了不说了 能看懂ECMA262的人太少了 明明就那么几行英文 真无奈

设计一个貌似可爱的脚本能让一大半的使用者都看不懂标准文档 该说Brendan Eich水平高还是说他悲哀? 或者,二者兼有......


ECMA262规范不是BE大人写出来的,而是委员会产物。他只不过review过。如果要说BE写出来的,那当然是JavaScript的Referenece和Guide。

当然规范写成这样诘屈聱牙,BE同志自然不能完全脱离干系。但是其中内情,怕是只有委员会的当事人知道。
31 楼 soni 2008-06-25  
本质这个东西,语言的创造者最有话语权。
lz的探索精神值得称赞,但结论还是参考标准吧。
30 楼 csf177 2008-06-25  
我滴神啊 你以为函数返回值相同就叫等价?
你说的实在太远了我才把ECMA262搬出来解释解释免得误导新人
你要非说没错我也没办法

反正我这人别人说点什么总插一杠子说不对确实挺烦人(我自己也这么觉得 哈哈) 不过有我搅和一下 可能有些人再看这东西的时候会稍微自己思考一下吧 毕竟这帖子挂着个精华的招牌呢 我挺怕看一片叫好的帖子的 有些东西我要不说 说不定真就没人说了

那么 目的已经达到了 我也不会再参与这个讨论了

最后感慨一句 JS这个貌似可爱的脚本咬起人来其实挺疼的
29 楼 alucardggg 2008-06-24  
csf177 写道
- -!这样也可以靠上 你明明连词法关系都没搞对...... 人家new跟函数调用是两种表达式 只不过是长得很像 都带括号而已
算了不说了 能看懂ECMA262的人太少了 明明就那么几行英文 真无奈

设计一个貌似可爱的脚本能让一大半的使用者都看不懂标准文档 该说Brendan Eich水平高还是说他悲哀? 或者,二者兼有......


我已经重复了好几遍了,文中提到概念都是现象到本质的推理,既然现在搬出了ECMA262的文档,就能圆满的解释我全部测试中提出的假设了。
并且文档中提到的和我推理的并无出入的地方,只不过一个概念化了,一个出于假设阶段而已
希望您认真读了我的文章,并且自己将全部例子跑一遍之后再进行评论,而不要断章取义。
我上述所阐述的并无不对,只不过没有必要咬文嚼字
28 楼 alucardggg 2008-06-24  
引用

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

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

还有很多地方 你的说法不能算错 但是其实思路是错的


您提到的文档引用和您的观点刚好相矛盾
27 楼 csf177 2008-06-24  
- -!这样也可以靠上 你明明连词法关系都没搞对...... 人家new跟函数调用是两种表达式 只不过是长得很像 都带括号而已
算了不说了 能看懂ECMA262的人太少了 明明就那么几行英文 真无奈

设计一个貌似可爱的脚本能让一大半的使用者都看不懂标准文档 该说Brendan Eich水平高还是说他悲哀? 或者,二者兼有......
26 楼 alucardggg 2008-06-24  
csf177 写道
When the [[Construct]] property for a Function object F is called, the following steps are taken:
1. Create a new native ECMAScript object.
2. Set the [[Class]] property of Result(1) to "Object".
3. Get the value of the prototype property of the F.
4. If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
5. If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object
prototype object as described in 15.2.3.1.
6. Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument
list passed into [[Construct]] as the argument values.
7. If Type(Result(6)) is Object then return Result(6).
8. Return Result(1).

人家说的很清楚 如果函数call返回的是Object 那就返回call的返回值
否则返回新建的object


我在文中进行测试分析的时候猜测JS的机制就是这样,JS会根据call返回值得不同而产生不同的类型,现在看来是有了依据
所以
var f = function() {
this.a = b;
return function() {}
}

var f = function() {
this.a = b;
return null;
}
为什么前面的 "this" 会消失,这样就不难解释了
25 楼 csf177 2008-06-24  
When the [[Construct]] property for a Function object F is called, the following steps are taken:
1. Create a new native ECMAScript object.
2. Set the [[Class]] property of Result(1) to "Object".
3. Get the value of the prototype property of the F.
4. If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
5. If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object
prototype object as described in 15.2.3.1.
6. Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument
list passed into [[Construct]] as the argument values.
7. If Type(Result(6)) is Object then return Result(6).
8. Return Result(1).

人家说的很清楚 如果函数call返回的是Object 那就返回call的返回值
否则返回新建的object
24 楼 csf177 2008-06-24  
alucardggg 写道
我给出
var f = new F();
和f = F();
2者是等价的这个结论,是基于我做了大量的测试的基础上,至于为什么得到这个结果我当时也很迷惑
还好,在ECMA的文档中找到了证据,大家可以研究下文档中的概念,结合我的测试结果,不难理解JS整个对象机制

晕 哪能这么读文档呢
既然要看 有些地方就要看全吧,哪能带着主观的想法去看 找支撑自己观点东西啊。

这段说的是内置的Function函数
内置的Function Array RegExp直接调用跟new完全等价是显然的
人家可没说你自己写的
function F(){
    return function(){}
}
也能做到
var f = new F();
和f = F(); 2者是等价的

去看看11.2和13.2是怎么说的吧
23 楼 alucardggg 2008-06-24  
我给出
var f = new F();
和f = F();
2者是等价的这个结论,是基于我做了大量的测试的基础上,至于为什么得到这个结果我当时也很迷惑
还好,在ECMA的文档中找到了证据,大家可以研究下文档中的概念,结合我的测试结果,不难理解JS整个对象机制
22 楼 alucardggg 2008-06-24  
我抽空会研究ECMA的文档的,希望能给出权威的证据论证我的测试结果
希望大家多多挑毛病
21 楼 alucardggg 2008-06-24  
好吧,我承认这里我偷懒了,的确我是过于注重推理的过程而忽略了给出一个权威而有说服的证据,我只是想从表面现象推断出一些东西。
那让我们看看“上帝“是怎么说的。。
我在网上下了Ecma的specification,Ecma-262,参考如下链接:
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

用PDF Reader 打开时, Page的页码并不对,为了方便起见,我这里的Page 为左边Navigator的页码数

前提:
Page 85:
NOTE:
A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.

这段说明证实了我在文中所提到的假设:
当一个变量以 "function" 定义时,property "prototype" 会自动的创建,当一个变量具有"prototype"时,才能做为一个constructor,这点我在文章的开头已经提出。

文中提到了直接 call Function(Function()), 2者事实上是相等的
Page 97, 15.3 Function Objects:
15.3.1 The Function Constructor Called as a Function
When Function is called as a function rather than as a constructor, it creates and initialises a new Function object. Thus the function call Function(…) is equivalent to the object creation expression new Function(…) with the same arguments.

而且在Ecma Specification 中, 紧接着他又对 直接 "call" Function 作出了解释:
15.3.1.1 Function (p1, p2, … , pn, body)
When the Function function is called with some arguments p1, p2, … , pn, body (where n might be 0, that is, there are no “p” arguments, and where body might also not be provided), the following steps
are taken:
1. Create and return a new Function object as if the function constructor had been called with the same arguments (15.3.2.1).

在直接"call"的时候,事实上也是返回一个 "new Function object", 就好像把Function当作一个constructor "call"一样(new Function()),接下去的处理步骤和15.3.2.1一样,我们看一下15.3.2 节
15.3.2 The Function Constructor
When Function is called as part of a new expression, it is a constructor: it initialises the newly created object.
15.3.2.1 new Function (p1, p2, … , pn, body)
The last argument specifies the body (executable code) of a function; any preceding arguments
specify formal parameters.
When the Function constructor is called with some arguments p1, p2, … , pn, body (where n might
be 0, that is, there are no “p” arguments, and where body might also not be provided), the following
steps are taken:
1. Let P be the empty string.
2. If no arguments were given, let body be the empty string and go to step 13.
3. If one argument was given, let body be that argument and go to step 13.
4. Let Result(4) be the first argument.
5. Let P be ToString(Result(4)).
6. Let k be 2.
7. If k equals the number of arguments, let body be the k’th argument and go to step 13.
8. Let Result(8) be the k’th argument.
9. Call ToString(Result(8)).
10. Let P be the result of concatenating the previous value of P, the string "," (a comma), and
Result(9).
11. Increase k by 1.
12. Go to step 7.
13. Call ToString(body).
14. If P is not parsable as a FormalParameterListopt then throw a SyntaxError exception.
15. If body is not parsable as FunctionBody then throw a SyntaxError exception.
16. Create a new Function object as specified in 13.2 with parameters specified by parsing P as a
FormalParameterListopt and body specified by parsing body as a FunctionBody. Pass in a scope
chain consisting of the global object as the Scope parameter.
17. Return Result(16).


我们发现2者确实是等价的
至于返回的变量为什么return function(){
}和return Object 会产生不同的"this"呢,
我目前还没有在文中找到直接的证据,但是从
Page 100, 15.3.5.3
15.3.5.3 [[HasInstance]] (V)
Assume F is a Function object.
When the [[HasInstance]] method of F is called with value V, the following steps are taken:
1. If V is not an object, return false.
2. Call the [[Get]] method of F with property name "prototype".
3. Let O be Result(2).
4. If O is not an object, throw a TypeError exception.
5. Let V be the value of the [[Prototype]] property of V.
6. If V is null, return false.
7. If O and V refer to the same object or if they refer to objects joined to each other (13.1.2), return
true.
8. Go to step 5.

和F的执行结果不无关系.

相关推荐

Global site tag (gtag.js) - Google Analytics