`
driftcloudy
  • 浏览: 130768 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

    博客分类:
  • Dojo
阅读更多

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的实例生效。

 

3
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics