`
alucardggg
  • 浏览: 8926 次
  • 性别: 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
分享到:
评论
20 楼 csf178 2008-06-23  
笨笨狗 写道
楼上的这种写法是有必要的,在某些情况下来说,呵呵。推荐看看周爱民的《javascript语言精粹与编程实践》,里面有讲

嘿嘿 有错字

《javascript语言精与编程实践》
19 楼 笨笨狗 2008-06-23  
楼上的这种写法是有必要的,在某些情况下来说,呵呵。推荐看看周爱民的《javascript语言精粹与编程实践》,里面有讲
18 楼 BaSaRa 2008-06-21  
一直觉得这种写法对JS是一种负担

constructor
new Function...

特别是这种代码最好不要写
var f4 = new Function("return function(){this.constructorMark =\"It is oo4's constructor\"}");  


完全没必要这样写。。。

JS可以写得很简单、很重用、很有结构性
这样的代码也不方便维护,调试起来太麻烦
17 楼 hurd 2008-06-21  
var x = function (){}是一样的。x最终的构造结果也是function anonymous(){}

晕, 另外 instanceof 干嘛用的?
16 楼 narky 2008-06-21  
KKFC 写道
charles_zuo 写道
说白了,javascript的对象是伪对象,实际上是关联数组,所以可以用. 或者[]的方式来访问属性

不是!!你说hash我还没那么气!


没错,prototype的文档里面说过,JavaScript所有的对象实际上都是Hash;但是Hash增加很多功能,让你可以枚举键和值,在键/值对上迭代,将两个哈希合并,将哈希编码成一个查询字符串表达式,等等。
15 楼 KKFC 2008-06-20  
charles_zuo 写道
说白了,javascript的对象是伪对象,实际上是关联数组,所以可以用. 或者[]的方式来访问属性

不是!!你说hash我还没那么气!
14 楼 charles_zuo 2008-06-20  
说白了,javascript的对象是伪对象,实际上是关联数组,所以可以用. 或者[]的方式来访问属性
13 楼 csf178 2008-06-20  
KKFC 写道
csf178说得没错,请楼主严谨一些,
不可思议的是JE编辑,即使是JE编辑我也要说, 这贴怎么能是精华帖啊?吓走观众啊!

我想是觉得楼主精神可嘉 这篇东西应该是花了很大精力的
12 楼 KKFC 2008-06-20  
csf178说得没错,请楼主严谨一些,
不可思议的是JE编辑,即使是JE编辑我也要说, 这贴怎么能是精华帖啊?吓走观众啊!
11 楼 csf178 2008-06-20  
引用
虽然{}, Object, Function, function都是JavaScript的保留字

其实只有function是JavaScript的保留字
{}是对象literal语法
Object和Function是内部函数名
10 楼 csf178 2008-06-20  
再随便给你挑几个地方

引用
在Sample1中,我们测试了JavaScript的4种对象定义方式:
var f1 = {},
var f2 = new Object(),
var f3 = function() {},
var f4 = new Function()


JS里面并没有什么对象定义的说法 Object RegExp Array对象都有literal 分别是
{ },/ /g,[ ]
而Function对象则是有类似literal的Function表达式
function(){}
构造对象要么直接用上面的语法 要么就用new

你这样把这4个拿出来 虽然不能算错 但是应该说理解方向有偏差
9 楼 csf178 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()一样
8 楼 alucardggg 2008-06-20  
而且分析var f = new f()
从运算符优先顺序来看,确实要先执行()运算,
但是var f = new (f())这样的运行结果又不等同于 var f = new f()
更奇怪的是,JS 会根据f()的运行结果,来为f生成不同的类型
7 楼 alucardggg 2008-06-20  
可能没表达正确,意思是
在定义
var f = function() {
  return function() {
  }
}
的时候,以运行结果来看
var ff = new f()和var ff = f() 是等价的

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

事实上这点也很奇怪,说明JS确实是有"类型变量"存在的
6 楼 KKFC 2008-06-20  
很遗憾,本来想精读全文的,
在第一次通读时,遇到这句话:
引用
var ff = f();和var ff = new f();其实是完全等价的

已经让我放弃了当初精读的想法
5 楼 csf178 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

还有很多地方 你的说法不能算错 但是其实思路是错的
4 楼 alucardggg 2008-06-20  
做的分析基于大量的测试之上,确实只是一种推测,推测自然不会100%正确
做了大量的测试想得出一个合适的通用的解释是不容易的
要使得推论即合适已有的测试结果,又能符合未来的测试结果
就好像现在所有对于宇宙起源的推测,没有人能证实一样,除了你直接去问造物主。。
直接看ECMA的标准文档确实是最权威直接的证据,不过要是没有注意到JavaScript这里稀奇古怪的构造现象,可能还没人有兴趣去看那种枯燥的文档。。
欢迎大家提出不同的建议,而且大家也可以利用我已有的测试结果,发表自己的推论
至于f()和new f(),按照目前的测试结果来看,JS确实会根据返回类型的不同而构造不同的对象
谢谢评论
3 楼 csf178 2008-06-20  
我觉得楼主的钻研精神是好的 不过自己猜测终究不是办法
为什么不去看看ECMA标准文档呢

楼主的认识有些确实没错 但有些确实偏离了JS的本意 即使他们看上去可以得到证实
就像楼主讲的new f()和f()那个地方 其实有很多错误的(这么说希望楼主不要介意)
2 楼 alucardggg 2008-06-20  
我使用的是Google的Picasa发布的图片,我会尽快的解决这个问题
1 楼 QuakeWang 2008-06-20  
IE下面也看不到图片,可能是你放置图片的网址禁止站外引用
你可以使用JavaEye博客提供的相册功能上传图片,然后在文章中使用这些图片

相关推荐

    javascript基础-->中级-->高级-->面向对象

    总结,JavaScript从基础到高级,再到面向对象的深入学习,对于任何参与Web建设的IT人员来说都是至关重要的。理解并熟练运用这些知识点,能够帮助开发者编写出更高效、更健壮的代码,以应对复杂的Web开发需求。

    面向对象JavaScript精要(英文原版pdf)

    ### 面向对象JavaScript精要 #### 一、书籍概览 本书《面向对象JavaScript...通过学习本书,读者可以全面掌握面向对象编程的基本概念,并学会如何将这些概念应用到JavaScript中,从而编写出更加健壮、可维护的代码。

    javascript对象参考手册

    这份手册旨在帮助开发者深入理解JavaScript对象的本质,提高他们在实际项目中的应用能力。通过链接到的博主Shuanran的博客文章,我们可以期待获取更多实用的见解和技巧。 在JavaScript中,对象是其动态特性和灵活性...

    JavaScript对象定义

    首先,我们要了解JavaScript对象的本质。对象是由键值对(key-value pairs)构成的集合,这些键可以是字符串或符号,而值可以是任意数据类型,包括其他对象。在JavaScript中,有两种主要的创建对象的方法:字面量...

    Javascript面向对象编程

    尽管JavaScript本质上是一种基于原型的语言,但它也支持面向对象编程的一些特性,使得开发者能够编写出结构清晰、易于维护的代码。本文将详细介绍JavaScript中的面向对象编程概念和技术,包括类定义、对象创建、继承...

    JavaScript面向对象编程指南

    本指南将深入探讨JavaScript中的面向对象特性,包括类、对象、继承、封装和多态性。 1. **对象和对象字面量** JavaScript中的对象是键值对的集合,可以通过对象字面量{}来创建。例如: ```javascript let person...

    JAVASCRIPT-使用面向对象的技术创建高级 Web 应用程序

    我们介绍了JavaScript对象作为词典的概念、函数的重要性、构造函数与原型、闭包以及如何模拟私有属性和命名空间。这些概念和技术对于任何希望使用现代Web开发技术构建复杂应用的开发者来说都是必不可少的。随着Web...

    Object-oriented-javascript

    6. **JavaScript的面向对象特性发展**:可能涵盖了从早期JavaScript版本到ES6中类语法的引入等语言特性的演进。 7. **构建可重用的代码库**:教授如何设计和编写可重用的函数、模块、对象和类,以减少重复代码,并...

    javascript面向对象编程

    面向对象的JavaScript在外观和内部运作上与其他具有对象能力的语言有着本质的不同。 JavaScript中的面向对象编程原则与常见语言不同,主要体现在构造器和原型方法的应用上。通过使用构造函数(constructor)和原型...

    javascript 面向对象编程.pdf javascript 设计模式与开发实践.pdf

    通过学习这两本书,读者不仅可以掌握JavaScript的面向对象编程基础,还能了解到如何在实际项目中应用设计模式,从而提升开发水平和团队协作效率。无论是初学者还是有经验的开发者,都能从中受益匪浅,为构建高质量的...

    javascript面向对象之对象的深入理解.docx

    构造函数本质上就是一个普通的函数,但它被用来创建特定类型的对象。例如: ```javascript function Cat() { this.name = "无名"; this.age = 0; this.color = "灰色"; } // 创建对象实例 var cat1 = new Cat();...

    理解Javascript Function与Object

    这也表明Function具有自举性(自已构造自己的能力)。这也间接决定了Function的[[call]]和[[constructor]]逻辑相同。 例如,下面的代码: ``` function Foo() {}; var foo = new Foo(); alert(foo instanceof Foo);...

    JavaScript面向对象编程

    总结,JavaScript的面向对象编程涉及到对象、构造函数、原型、继承、封装、多态以及模块化等多个方面。理解并熟练掌握这些概念,能帮助开发者编写出更高效、更易于维护的代码。通过实践和不断学习,你可以编写出功能...

    javascript对象

    JavaScript对象是JS编程中的核心概念,它是一种数据结构,用于存储和组织数据。在JavaScript中,一切都是对象,包括基本类型如字符串、数字和布尔值,它们都有对应的包装对象。理解JavaScript对象模型对于深入学习和...

    javascript面向对象技术基础

    JavaScript对象都有一个内置的`__proto__`属性,指向其构造函数的原型。原型是一个对象,可以包含共享的方法和属性。当我们尝试访问对象的一个属性时,如果该属性不存在,JavaScript会查找`__proto__`链,直到找到该...

    javascript 面向对象编程 万物皆对象

    你可以通过这些对象访问它们的属性和方法,如`getFullYear()`、`getMonth()`、`length`等,这展示了JavaScript对象的强大之处。 其次,方法在JavaScript中也是对象。你可以将函数赋值给变量,就像处理其他任何对象...

Global site tag (gtag.js) - Google Analytics