面向对象基础
"面向对象的JavaScript"这一说法多少有些冗余,因为JavaScript语言本就是完全面向对象的,不可能有另外的用法。但是,初学编程者(包括JavaScript编程者)共有的一个缺点就是,功能性地编写代码而不使用任何上下文或分组。要完全理解怎么编写优化的JavaScript代码,你必须理解JavaScript的对象是怎样工作的,它们与其它语言有怎样的不同,以及怎样让它们为你所用。 本章的剩余部分我们将讨论用JavaScript编写面向对象代码的基础,在后面的几章中,我们将看到以这种方式编写代码的实例。
对象
对象是JavaScript的基础。实际上JavaScript语言中的一切都是对象,JavaScript的多数能力也正起源于此。在其最根本的层面上,对象作为属性的集合存在,差不多类似于你在其它语言中看到的哈希的概念。程序2-19展示了创建两个带有一组属性的对象的基本示例。
程序2-19. 创建简单对象并设置其属性的两个例子
//创建一个新对象并将其存放在obj里 var obj = new Object();
//将该对象的一些属性设置成不同的值 obj.val = 5; obj.click = function(){ alert( "hello" ); };
//下面是等效的代码,使用了{...}式缩写, //和定义对象属性的"名称-值"对 var obj = {
//用名称-值对设置对象属性 val: 5, click: function(){ alert( "hello" ); } };
实际上对象就这么回事了。然而,事情变得麻烦的地方,在于新对象(尤其是那些继承其它对象属性的对象)的创建。
对象创建
不像大多数其它面向对象的语言,JavaScript实际上并没有类的概念。在大多数其它的面向对象语言中,你可以初始化一个特定的类的实例,但是在JavaScript中的情况这是这样。在JavaScript中,对象能够创建新的对象,对象可以从继承自其它对象。整个概念被称为"prototypal inheritance"(原型标本继承),将在"公有方法"一节中有更多论述。 然而,重要的是,不论JavaScript采用哪种对象方案,总归要有一个方式来创建新的对象。JavaScript的做法是,任何一个函数也都能作为一个对象被实例化。实际上,事情听起来远比它本身更令人困惑。好比有一块生面团(相当于原始的对象),用小甜饼切割器(相当于对象构造器,使用对象的原型prototype)为其成形。 让我们看看程序2-20中这一机制的工作的实例
程序2-20. 创建并使用一个简单的对象
//一个简单的函数,接受一个参数name, //并将其保存于当前上下文中 function User( name ) { this.name = name; }
//用指定的name创建上述函数的新实例 var me = new User( "My Name" );
//我们可以看到name已经被成为对象本身的属性 alert( me.name == "My Name" );
//而且它确实是User对象的一个新实例 alert( me.constructor == User );
//那么,既然User()只是一个函数, //当我们这么处理它的时候,发生了什么? User( "Test" );
//因为this上下文没有被设置,它缺省地指向全局的window对象, //这意味着window.name将等于我们提供给它的那个name alert( window.name == "Test" );
程序2-20说明了constructor属性的使用。这个存在于每一个对象中的属性将总是指向创建该对象的那个函数。于是,你可以方便的复制该对象,创建一个新的有共同基类和不同属性的对象。示例见程序2-21.
程序2-21. 使用constructor属性一例
//创建一个新的、简单的User对象(函数) function User() {}
//创建一个新的User对象 var me = new User();
//也是创建一个新的User对象(使用上前一个对象的constructor) var you = new me.constructor();
//我们可以看到,实际上它们的constructor是同一个 alert( me.constructor == you.constructor );
公有方法
公有方法可以完全地被对象的上下文中的最终使用者访问。为了实现这些对于特定对象的所有实例都可用的公共方法,你需要学习一个名为"prototype"的属性。prototype简单地包含一个对象,为一个父对象的所有新副本充当对基类的引用。本质上,prototype的任何属性对该对象的所每一个实例都是可用的。创建/引用的过程给了我们一个廉价版的继承,这一点我将在第三章论及。 由于对象的prototype也是一个对象,就跟其它任何对象一样,你可以给它附加新的属性。附加给prototype的新的属性将成为从原来的prototype对象实例化的每个对象的一部分,有效地使得该属性成为公有的(且可为全部实例所访问)。程序2-22展示一个此类例子: 程序2-22. 带有通过prototype附加的方法的对象的例子
//创建一个新的User的构造器 function User( name, age ){ this.name = name; this.age = age; }
//为prototype对象添加一个新方法 User.prototype.getName = function(){ return this.name; };
//为prototype对象添加另一个方法 //注意此方法的上下文将是被实例化的对象 User.prototype.getAge = function(){ return this.age; };
//实例化一个新的User对象 var user = new User( "Bob", 44 );
//我们可以看到两个方法被附加到了对象上,有着正确的上下文 alert( user.getName() == "Bob" ); alert( user.getAge() == 44 );
私有方法
私有方法和变量只能被其它的私有方法、私有变量的特权方法(下一节将会论述)访问。这是一种定义只能在内象内部访问的代码的方式。这一技术得益于Douglas Crockford的工作。他的网站提供了大量的详述面向对象的JavaScript的工作机制和使用方法的文档: JavaScript文章列表:http://javascript.crockford.com/ 文章"JavaScript中的私有成员:http://javascript.crockford.com/private.html
我们来看一个私有方法可以怎样应用中的例子,如程序2-23所示.
程序2-23. 私有方法只能被构造函数使用的示例:
//一个表示教室的对象构造器 function Classroom( students, teacher ) { //用来显示教室中的所有学生的私有方法 function disp() { alert( this.names.join(", ") ); } //课程的数据存储在公有的对象属性里 this.students = students; this.teacher = teacher; //调用私有方法显示错误 disp(); }
//创建一新的教室对象 var class = new Classroom( [ "John", "Bob" ], "Mr. Smith" );
//失败,因为disp不是该对象的公有方法 class.disp();
尽管很简单,私有方法却是非常重要的,它可以在保持你的代码免于冲突同时允许对你的用户可见和可用的施以更强大的控制。接下来,我们来研究特权方法。它是你的对象中可以使用的私有方法和共有方法的联合。
特权方法
"特权方法"一语是Douglas Crockford创造的,用来称呼那种能够观察和维护私有变量而又可以作为一种公有方法被用户访问的方法。程序2-24展示了使用特权方法的一个例子。 程序2-24 使用特权方法一例
//创建一个新的User对象构造器 function User( name, age ) { //计算用户的出生年份 var year = (new Date()).getFullYear() – age;
//创建一个新特权方法,对变量year有访问权, //但又是公共可访问的 this.getYearBorn = function(){ return year; }; }
//创建一个User对象的新实例 var user = new User( "Bob", 44 );
//验证返回的出生年份是否正确 alert( user.getYearBorn() == 1962 );
//并注意我们不能访问对象的私有属性year alert( user.year == null );
本质上,特权方法是动态生成的方法,因为它们是在运行时而不是代码初次编译时添加给对象的。这种技术在计算量上要比绑定一个简单的方法到对象的prototype上来得昂贵,但同时也的强大和灵活得多。程序2-25展示了使用动态生成的方法可以实现什么。
程序2-25. 新对象初始化时创建的动态方法的示例
//创建一个新的接受properties对象的对象 function User( properties ) { //遍历对象属性,确保它作用域正确(如前所述) for ( var i in properties ) { (function(){ //为属性创建获取器 this[ "get" + i ] = function() { return properties[i]; };
//为属性创建设置器 this[ "set" + i ] = function(val) { properties[i] = val; }; })(); } }
//创建一个新user对象实例,传入一个包含属性的对象作为种子 var user = new User({ name: "Bob", age: 44 });
//请注意name属性并不存在,因为它在properties对象中,是私有的 alert( user.name == null );
//然而,我们能够使用用动态生成的方法getname来访问它 alert( user.getname() == "Bob" );
//最后,我们能看到,通过新生成的动态方法设置和获取age都是可以的 user.setage( 22 ); alert( user.getage() == 22 );
(译注:这段程序是错误的。那个匿名函数里的this错误地指向了匿名函数的上下文,而其中的变量i却又恰仍属User 的上下文) 动态生成的代码的力量不可低估。能够基于变量的值实时的生成代码是极其有用;这与在其它语言(如Lisp)中宏那样强大的道理是一样的,不过是放在一种现代编程语言的背景里。接下来,我们将看到一类纯粹因其组织上的优势而有用的方法。
静态方法
静态方法背后的前提其实跟其它任何方法是一样的。然而,最主要的不同在于,这些方法作为对象的静态属性而存在。作为属性,它们在该对象的实例上下文中不可访问;它们只有在与主对象本身相同的上下文是可用的。这些与传统的类继承的相似点,使得他们有点像是静态的类方法。 实际上,以这种方式编写代码的唯一好处在于,这种方法保持对象名称空间的干净,——这一概念我就在第三章中更一步论述。程序2-26展示了附加在对象上的静态方法的一个例子。
程序2-26. 静态方法的简单示例
//附加在User对象上的一个静态方法 User.cloneUser = function( user ) { //创建并返回一个新的User对象 return new User( //该对象是其它user对象的克隆 user.getName(), user.getAge() ); };
静态方法是我们遇到的第一种纯粹以组织代码为目的的方法。这是向我们将要看到的下一章的重要过渡。开发专业品质JavaScript的一个基本侧观点,就是要有能力快速、平静地与其它代码段接口,同时保持可理解地可用性。这是一个重要的奋斗目标,也是我们下一章里所期望达到的。
经典论坛讨论: http://bbs.blueidea.com/thread-2734050-1-1.html
本文链接:http://www.blueidea.com/tech/web/2007/4618.asp
出处:蓝色理想
责任编辑:moby
上一页 语言特性:上下文 下一页
◎进入论坛网页制作、网站综合版块参加讨论
|