当前位置: 代码迷 >> JavaScript >> DOJO中的面向对象_第一章 JS中的对象模型
  详细解决方案

DOJO中的面向对象_第一章 JS中的对象模型

热度:168   发布时间:2012-10-11 10:16:10.0
DOJO中的面向对象__第一章 JS中的对象模型

DOJO中的面向对象

?

  在JS中并没有Java等面向对象语言中常见的类(class)的概念。也就是说,JS中的对象并非基于类的。它仅仅为开发者提供了一些原类型和基本的内置对象。从写法上来看,它更加的类似于面向过程式的语言,函数仿佛成了JS中的顶级实体。事实上,JS是一门函数式编程的语言。所以当我们需要以面向对象的方式来构建大型web应用时,原生态的JS并不能很好的满足这一点。而DOJO的出现完美的解决了这个问题,它使得程序员能够以传统的面向对象的思维来进行开发,进而使JS用起来更加得心应手。

?

第一章 JS中的对象模型

(一) 构造器(伪类)

  在JS中创建一个对象很容易,JS提供了语法糖允许我们以字面量的方式来构建对象。例如:

var foo={	'name':'Jack' }

?

  但是在JS中构建一个类却显得稍微复杂。由于JS并没有从语言层面上支持任何自定义类型,所以我们只能通过模拟的方式来构建出一个类。这得益于JS中的强大的函数以及原型机制。先来看一个简单的例子:

function Foo(){
	this.name='Jack';
}
var foo=new Foo();
console.log(foo.name) // 输出jack

?

  在这个例子中,Foo已经不仅仅是一个简单的函数了,我们将Foo看成一个‘伪类’(在javascript中称为构造器)。因此可以用new运算符来生成该类型的对象。通常JS中的类型构造都采用该方法。新生成的对象将自动拥有‘伪类’中定义的字段,所以此例中生成的foo将拥有name属性。

?

  注意Foo中的this.name='Jack'。由于JS中的某些不良设计,一般的函数调用会将该函数中的this绑定到全局对象上,这使得this的使用容易造成混乱。通常而言,如果并不涉及到面向对象编程,可以不必使用this。只有存在了某个对象,this的使用才会有意义。


  如果对构造器进行new运算,构造器中的this会被绑定到生成的新的对象。换句话说,上例中new Foo()时,Foo中的this会被绑定到新生成的实例foo。可以猜测,对一个Foo调用new运算符的时候,会发生类似于下面的过程:?

var obj=new Object();	//obj是一个新生成的对象
Foo.apply(obj);			//将Foo中的this绑定到obj
var foo=obj;			//最后将obj引用返回给foo
?

(二) prototype是什么

  JS中的继承有点特殊,在JS中并不存常见的基于类的继承。JS中的继承是对象与对象之间的行为。也就是说,一个对象可以直接继承另一个对象里的属性。而这一切,都是依靠prototype来完成的。示例如下:

var foo={
	'name':'Jack'
}
function Bar(age){
	this.age=age;
}
Bar.prototype=foo;
var bar=new Bar(22);

?

  这个例子中,我们首先创建了一个对象foo,它包含一个name属性。然后我们创建了一个构造器Bar,由于将Bar当做类来使用,所以将其首字母大写。随后我们将Bar的原型指向foo对象。接着,我们以new的方式来创建了一个Bar的实例bar。很显然,对象bar中包含了两个属性,name属性值为Jack,还有age属性,值为22。值得考究的是Bar. prototype=foo这一句。该语句将Bar的原型设定成一个对象foo。这一句的运行结果是通过Bar创建的所有对象都将继承foo对象的属性。于是,接下来bar便从foo中继承了name属性。

?

  推广开来说, JS中的每个构造器都有一个prototype属性。JS里的构造器,除了包括了我们上面自己定义的‘伪类’,还包括了内置的Object、Function、String 、Array、Number、Date、RegExp等等一系列函数。prototype本身也是一个对象,也就是我们所说的原型对象,如果我们用构造器创建了一个新的对象,该对象便与原型对象发生了继承关系。注意,JS中的继承是发生在两个对象之间的关系,而JAVA之中的继承是两个类之间的关系。


  JS中的继承有两个突出的特点,一是动态性,二是对修改封闭。下面的例子阐述了这两点,例一:

var foo={ 'name':'Jack' }			
function Bar(age){
	this.age=age;
}			
Bar.prototype=foo;			
var bar=new Bar(22);
console.log(bar.name)    //Jack
foo.name='Mike';
console.log(bar.name)    //Mike

?

  当我们修改了foo的属性时,通过bar来访问这些属性也会收到影响。也就是说,我们可以将一些需要的特性动态添加到JS的对象中。这是一种非常强大的编程技术。比如JS中的String对象缺少trim方法。通过

String.prototype.trim=function(){//dojo中的实现
  return this.replace(/^\s\s*/,'').
  replace(/\s\s*$/,'');
} 

?

语句,可以为所有的string加上trim方法。

例二:

var foo={ 'name':'Jack' }
function Bar(age){
	this.age=age;
}
Bar.prototype=foo;
var bar=new Bar(22);
bar.name='Mike';
console.log(bar.name)    // Mike
console.log(foo.name)    // Jack

?

  从上例中可以清楚的看出,如果我们试图通过修改bar来影响foo,这样是行不通的。通过bar可以访问foo的属性,但是却无法改变这些属性的值。当我们修改bar.name='Mike'之后,foo的name值依然是Jack。

?

(三) 原型链(prototype chain)

  事实上,在bar对象中,有一个看不见的_proto属性。该属性指向了Bar.prototype,也就是foo。在Ecma-262 3rd Edition中有如下描述:

写道
Each constructor has a Prototype property that is used to implement prototype-based inheritance and shared properties.
写道
Every constructor has an associated prototype, and every object created by that constructor has an implicit reference to the prototype associated with its constructor.

?

  这段话的意思是JS中的构造器都拥有一个prototype属性。每个由构造器创建出来的object都含有一个指向该prototype的隐式引用。

function Foo(){
	this.name='Jack';
}
var foo=new Foo();
function Bar(age){
	this.age=age;
}
Bar.prototype=foo;
var bar=new Bar(22);

?

因此,上例可以表示成:

?

  注意绿色虚线框内的部分。通过_proto,可以将许多对象串起来,形成一个链条,也就是我们经常说的原型链(prototype chain)。当我们试图访问对象中的一个属性,首先会在对象本体中寻找该属性。如果没有找到,JS会自动在该对象的原型对象中查询该属性,这个过程是一种上溯。如果还是没有找到,会继续上溯到原型对象的原型对象中。


  但是这个步骤不是无止尽的,这个上溯的过程直到Object.prototype._proto为止。以上面的图为例,从foo可以找到的原型对象是Foo.prototype。Foo.prototype本身是一个对象,也是Object的一个实例,因此有:Foo.prototype._proto=Object.prototype 。


  所以在向上追溯的过程中,会追溯到Object.prototype这个对象。如果依然没有我们要找的属性,那还会继续向上追溯么?从Ecma-262 3rd Edition15.2.4节中可以得知:

写道
The value of the internal [[Prototype]] property of the Object prototype object is null and the value of the internal [[Class]] property is "Object".

?

  也就是说Object.prototype._proto=null.至此,可以清楚的弄明白,整个原型链的最顶端的对象是Object.prototype,再往上就是null了。所以原型链可以认为是众多对象利用_proto串成的引用链,有点类似单链表,引用链的最后一个节点是Object.prototype。

?

(四) 维护constructor

  只有当我们创建一个函数(JS中的函数也是对象)时,会自动为这个函数附上prototype对象, prototype中的所有属性会被遗传到该函数创建的对象上。在prototype的属性中,比较特殊的是constructor,constructor的值就是这个函数本身。赋上prototype对象的这个过程类似于:

Foo.prototype={
	constructor:Foo
}

?

如果我们执行:

Foo.prototype.constructor===Foo

?

会输出true。同样如果在Bar.prototype = foo语句之前执行:

Bar.prototype.constructor===Bar

?

也会输出true。由于在prototype对象中的属性会被继承,因此foo和bar中都能访问到constructor属性,分别指向Foo和Bar。

?

  可以看出,JS中的constructor好比JAVA中的Class,在JAVA中一个对象可以通过getClass方法来获取自己的Class,那么JS中的对象可以直接访问constructor来获取自己的构造器。在多层次的继承问题上,我们可能需要维护正确的继承结构。由于无法访问_proto属性,因此继承链的维护只能依靠constructor属性。

function Foo(){}
var foo=new Foo();
function Bar(){}
Bar.prototype=foo;
var bar=new Bar();
console.log(bar.constructor) //Foo

?

  运行上面这个例子,最后的输出结果为Foo。原因是foo中的constructor指向了Foo,而bar又从foo对象继承了该属性。这个时候需要进行一些修改,使得bar的constructor属性能够正确指向Bar。一般有两种方式来处理这个问题。一是在构造器里面修改constructor的值,第二种是在构造完成之后进行修改。

function Foo(){}
var foo=new Foo();
function Bar(){
	this.constructor=Bar;
}
Bar.prototype=foo;
var bar=new Bar();
bar.constructor //Bar 
function Foo(){}
var foo=new Foo();
function Bar(){}
Bar.prototype=foo;
var bar=new Bar();
bar.constructor= Bar;

?

  如果使用下边一种方式,每次实例化一个Bar的对象,都要进行修改。推荐使用上边的方式来修改constructor属性,此修改会对所有Bar的实例生效。

?