javascript 的面向对象编程

最近想学一下ExtJs,不错以前javascript基础不好,有很多Extjs的内容虽然可以用,但是都是一知半解。所以下定决心学一下javascript,不学不知道啊。现在发现以前自己对javascript的了解太少了,现在有点爱上她了。本来想自己写一个javascript 的OOP方面的文章的,不错发现了一篇很好的文章,呵呵,转载一下好了。附件里有PDF文档下载,高清

 

第二章:面向对象的Javascript 理解本章概念的大纲的重要性是不容忽视的。本章的前半部分,让你对于JavaScript 语言怎样运作和怎样最好地它用一个良好的理解,这是完全掌握专业地使用JavaScript 的出发点。彻底地理解对象怎样运作、引用怎样处理、作用域怎样确定,将会毫无疑问 地改变你编写JavaScript 代码的方式。 有了广博的JavaScript 编码技能,编写干净的面向对象JavaScript 代码的重要性将 会变得更加明显。本章的后半部分里我论述了怎样着手编写种种面向对象的代码以适应 来自其它编程语言阵营的任何人。现代JavaScript 正是基于这些技能,给予你开发新型 的创新的应用程序时巨大的优势。 引用 JavaScript 的一个重要的方面是引用的概念。引用就是指向对象实际位置的指针。 这是一项极其强大的功能。前提是,实际的对象决不是一个引用:字符串总是一个字符 串,数组总是一个数组。然而,多个变量可以引用相同的对象。JavaScript 就是以这种 引用引用机制为基础。通过维护一系列的指向其它对象的引用,语言为你提供了更大的 弹性。 另外,对象能包括一系列的属性,这些属性简单地引用其它对象(如字符串,数字, 数组等等)。当几个变量指向相同对象时,修改底层对象类型将会在所有的指点向它的 变量上有所反映。例2-1即此一例,两个变量指向同一个对象,但是对对象内容的修改 的反映是全局的。 程序2-1. 多变量引用单个对象的示例 //设置obj 为一个空对象 var obj = new Object(); //objRef 现在引用了别的对象 var objRef = obj; //修改原始对象的属性 obj.oneProperty = true; //我们可以发现该变化在两个变量中都可以看到 //(因为他们引用了同一个对象) alert( obj.oneProperty === objRef.oneProperty ); 我从前提到过自更改的对象在JavaScript 里非常少见的。让我们看一个发生这一状 况的实例。数组对象能够用push 方法给它自己增加额外的项。因为在数组对象的核心, 值是作为对象的属性存储的,结果类似程序2-1中的情形,一个对象成为全局被改动的(导 致了多个变量的值被同时改变)。见程序2-2. 程序2-2. 自修改对象的例子 //创建一组项目的数组 var items = new Array( "one", "two", "three" ); //创建一个对项目数组的引用 var itemsRef = items; //给原始数组添加一项 items.push( "four" ); //两个数组的长度应该相同, //因为它们都指向相同的数组对象 alert( items.length == itemsRef.length ); 记住这一点是很重要的:引用总是只指向最终被引用的对象,而不会是引用本身。 例如,在Perl 语言里,很可能有一个引用指向另一个也是引用的变量。但在JavaScript 里,它会沿着引用链向下追溯直到指向核心的对象。程序2-3演示了这种情形,物理的 目标已经改变而引用仍然指向原来的对象。 程序2-3. Changing the Reference of an Object While Maintaining Integrity(见#9 oerrite 的回复) // 设置items 为一个字符串的数组(对象) var items = new Array( "one", "two", "three" ); // 设置itemsRef 为对items 的引用 var itemsRef = items; //让items 指向一个新的对象 items = new Array( "new", "array" ); // items 和itemsRef 现在指向不同的对象 // items 指向new Array( "new", "array" ) // itemsRef 则指向new Array( "one", "two", "three" ) alert( items !== itemsRef ); 最后,让我们来看一个陌生的例子,表面似乎是一个自修改的对象,却作用于一个 新的未被引用的对象。当执行字符串串联时,结果总是一个新的字符串对象,而非原字 符串更改后的版本。这在程序2-4中可以看出。 程序2-4. 对象修改作用于一个新的对象而非自修改对象的示例 //让item 等于一个新的字符串对象 var item = "test"; //itemRef 也引用相同的字符串对象 var itemRef = item; //在字符串对象上串联一个新的对象 //注意:这创建了一个新的对象,并不修改初始对象 item += "ing"; //item 和itemRef 的值并不相等,因为 //一个全新的对象被创建了 alert( item != itemRef ); 如果你刚刚接触,引用可能是个令人头大的刁钻话题。然而,理解引用是如何工作 的对于编写良好、干净的JavaScript 代码是极其重要的。接下来的几节我们将探究几种 未必新鲜和令人激动的,但是同样对编写良好、干净的代码很重要的特性。 函数重载和类型检查 其它面向对象的语言(比如Java)的一种共有的特性是“重载”函数的能力:传给它们 不同数目或类型的参数,函数将执行不同操作。虽然这种能力在JavaScript 中不是直接 可用的,一些工具的提供使得这种探求完全成为可能。 在JavaScript 的每一个函数里存在一个上下文相关的名为arguments 的变量,它的 行为类似于一个伪数组,包含了传给函数的所有参数。参数不是一真正的数组(意味着 你不能修改它,或者调用push()方法增加新的项),但是你可以以数组的形式访问它,而 且它也的确有一个length 属性。程序2-5中有两个示例。 程序2-5. JavaScript 中函数重载的两个示例 //一个简单的用来发送消息的函数 function sendMessage( msg, obj ) { //如果同时提供了一个消息和一个对象 if ( arguments.length == 2 ) //就将消息发给该对象 obj.handleMsg( msg ); //否则,刚假定只有消息被提供 else //于是显示该消息 alert( msg ); } //调用函数,带一个参数– 用警告框显示消息 sendMessage( "Hello, World!" ); //或者,我们也可以传入我们自己的对象用 //一种不同方式来显示信息 sendMessage( "How are you?", { handleMsg: function( msg ) { alert( "This is a custom message: " + msg ); } }); //一个使用任意数目参数创建一个数组的函数 function makeArray() { //临时数组 var arr = []; //遍历提交的每一个参数 for ( var i = 0; i < arguments.length; i++ ) { arr.push( arguments[i] ); } //返回结果数组 return arr; } 另外,存在另一种断定传递给一个函数的参数数目的方法。这种特殊的方法多用了 一点点技巧:我们利用了传递过来的任何参数值不可能为undefined 这一事实。程序2-6 展示一了个简单的函数用来显示一条错误消息,如果没有传给它,则提供一条缺省消息。 程序2-6: 显示错误消息和缺省消息 function displayError( msg ) { //检查确保msg 不是undefined if ( typeof msg == 'undefined' ) { //如果是,则设置缺省消息 msg = "An error occurred."; } //显示消息 alert( msg ); } typeof 语句的使用引入了类型检查。因为JavaScript(目前)是一种动态类型语言, 使得这个话题格外有用而重要的话题。有许多种方法检查变量的类型;我们将探究两种 特别有用的。 第一种检查对象类型的方式是使用显式的typeof 操作符。这种有用的方法给我们一 个字符串名称,代表变量内容的类型。这将是一种完美的方案,除非变量的类型或者数 组或自定义的对象如user(这时它总返回"ojbect",导致各种对象难以区分)。 这种方法的示例见程序2-7 程序2-7. 使用typeof 决定对象类型的示例 //检查我们的数字是否其实是一个字符串 if ( typeof num == "string" ) //如果是,则将它解析成数字 num = parseInt( num ); //检查我们的数组是否其实是一个字符串 if ( typeof arr == "string" ) //如果是,则用逗号分割该字符串,构造出一个数组 arr = arr.split(","); 检查对象类型的第二种方式是参考所有JavaScript 对象所共有的一个称为 constructor 的属性。该属性是对一个最初用来构造此对象的函数的引用。该方法的示例 见程序2-8。 程序2-8. 使用constructor 属性决定对象类型的示例 //检查我们的数字是否其实是一个字符串 if ( num.constructor == String ) //如果是,则将它解析成数字 num = parseInt( num ); //检查我们的字符串是否其实是一个数组 if ( str.constructor == Array ) //如果是,则用逗号连接该数组,得到一个字符串 str = str.join(','); 表2-1显示了对不同类型对象分别使用我所介绍的两种方法进行类型检查的结果。 表格的第一列显示了我们试图找到其类型的对象。每二列是运行typeof Variable(Variable 为第一列所示的值)。此列中的所有结果都是字符串。最后,第三列显示了对第一列包 含的对象运行Variable.constructor 所得的结果。些列中的所有结果都是对象。 表2-1. 变量类型检查 ——————————————————————————————— Variable typeof Variable Variable.constructor ——————————————————————————————— {an:"object"} object Object ["an","array"] object Array function(){} function Function "a string" string String 55 number Number true boolean Boolean new User() object User —————————————————————————————————— 使用表2-1的信息你现在可以创建一个通用的函数用来在函数内进行类型检查。可 能到现在已经明显,使用一个变量的constructor 作为对象类型的引用可能是最简单的类 型检查方式。当你想要确定精确吻合的参数数目的类型传进了你的函数时,严格的类型 检查在这种可能会大有帮助。在程序2-9中我们可以看到实际中的一例。 程序2-9. 一个可用来严格维护全部传入函数的参数的函数 //依据参数列表来严格地检查一个变量列表的类型 function strict( types, args ) { //确保参数的数目和类型核匹配 if ( types.length != args.length ) { //如果长度不匹配,则抛出异常 throw "Invalid number of arguments. Expected " + types.length + ", received " + args.length + " instead."; } //遍历每一个参数,检查基类型 for ( var i = 0; i < args.length; i++ ) { //如JavaScript 某一项类型不匹配,则抛出异常 if ( args[i].constructor != types[i] ) { throw "Invalid argument type. Expected " + types[i].name +", received " + args[i].constructor.name + " instead."; } } } //用来打印出用户列表的一个简单函数 function userList( prefix, num, users ) { //确保prefix 是一个字符串,num 是一个数字, //且user 是一个数组 strict( [ String, Number, Array ], arguments ); //循环处理num 个用户 for ( var i = 0; i < num; i++ ) { //显示一个用户的信息 print( prefix + ": " + users[i] ); 变量类型检查和参数长度校验本身是很简单的概念,但是可用来实现复杂的方法, 给开发者和你的代码的使用者提供更好的体验。接下来,我们将探讨JavaScript 中的作 用域以及怎么更好的控制它。 作用域 作用域是JavaScript 中一个较难处理的特性。所有面向对象的编程语言都有某种形 式的作用域;这要看是什么上下文约束着作用域。在JavaScript 里,作用域由函数约束, 而不由块约束(如while,if,和for 里的语句体)。最终可能使得一些代码的运行结果表面上 显得怪异(如果你来自一种块作用域语言的话)。程序2-10的例子说明了“函数作用域代 码”的含义。 代码2-10. JavaScript 中变量作用域是怎样工作的例子 //设置一个等于"test"的全局变量foo var foo = "test"; //在if 块中 if ( true ) { //设置foo 为"new test" //注意:这仍然是在全局作用域中 var foo = "new test"; } //正如我们在此处可见,foo 现在等于"new test" alert( foo == "new test" ); //创建一个修改变量foo 的函数 function test() { var foo = "old test"; } //调用时,foo 却驻留在是在函数的作用域里面 test(); //确认一下,foo 的值仍然是"new test" alert( foo == "new test" ); 在程序2-10中你会发现,变量位于在全局作用域。基于浏览器的JavaScript 有趣的 一面是,所有的全局变量实际上都是window 对象的属性。尽管一些老版本的Opera 浏 览器或Safari 浏览器不是这样,假定浏览器这样工作通常是一个很好的经验规则。程序 2-11展示了一个这种例子。 程序2-11. JavaScript 的全局变量与window 对象的例子 //全局变量,包含字符串"test" var test = "test"; //你会发现,我们的全局变量和window 的test 属性是相同的 alert( window.test == test ); 最后,让我们来看看当一个变量漏定义时会怎样。程序2-12里,变量foo 在test() 的作用域里被赋值。但是,程序2-12里实际并没有(用var foo)定义变量的作用域。当变 量foo 没有明确定义时,它将成为全局变量,即使它只在函数的上下文使用。 程序2-12. 隐式全局变量声明的示例 //一个为变量foo 赋值的函数 function test() { foo = "test"; } //调用函数为foo 赋值 test(); //我们发现foo 现在是全局变量了 alert( window.foo == "test" ); 到目前应该很明显,尽管JavaScript 的作用域不如块作用域语言的严格,它还是相 当强大和有特色的。尤其是与下节中叙述的闭包的概念结合起来时,JavaScript 语言的 强大将展露无遗。 闭包 闭包意味着内层的函数可以引用存在于包绕它的函数的变量,即使外层的函数的执 行已经终止。这一特殊的论题可能是非常强大又非常复杂的。我强烈推荐你们参考本节 后面将提及的站点,因为它有一些关于闭包这一话题的精彩的信息。 我们先来看程序2-13所示的闭包的两个简单例子。 程序2-13. 闭包改善的代码清晰性的两例 //得到id 为"main"的元素 var obj = document.getElementById("main"); //改变它的边框样式 obj.style.border = "1px solid red"; //初始化一个1秒钟以后被调用的回调函数 setTimeout(function(){ //此函数将隐藏该元素 obj.style.display = 'none'; }, 1000); //用来延迟显示消息的通用函数 function delayedAlert( msg, time ) { //初始化一个被封套的函数 setTimeout(function(){ //此函数使用了来自封套它的函数的变量msg alert( msg ); }, time ); } //调用函数delayedAlert,带两个参数 delayedAlert( "Welcome!", 2000 ); 第一个对setTimeout 的函数调用,展示了一个的JavaScript 新手遇到问题的通俗的 例子。在JavaScript 新手的程序里像这样的代码时常可以看到: setTimeout("otherFunction()", 1000); //或者甚至 setTimeout("otherFunction(" + num + "," + num2 + ")", 1000); 使用闭包的概念,完全可能的把这种混乱的代码清理掉。第一个例子很简单;有一 个回调函数在调用setTimeout 函数以后1000微秒以后被调用,而它仍引用了变量obj(定 义在全局范围,指向id 为"main"的元素)。定义的第二个函数,delayedAlert,展示了一 种解决出现的setTimeout 混乱的方案,以及函数作用域内可以有闭包的能力。 你们应该可以发现,当在代码中使用这种简单的闭包时,你所写的东西的清晰性将 会提高,免于陷入语法的迷雾之中。 我们来看一个闭包可能带来的有有趣的副作用。在某些函数化的编程语言里,有一 个叫做currying 的概念。本质上讲,currying 是就是为函数的一些参数预填入值,创建 一个更简单的新函数的方法。代码2-14里有一个简单的currying 的例子,创建了向另一 个函数预填一个参数而得的新函数。 代码2-14. 使用闭包的函数currying //生成做加法的新函数的函数 function addGenerator( num ) { //返回一个简单函数用来计算两个数的加法, //其中第一个数字从生成器中借用 return function( toAdd ) { return num + toAdd }; } //addFive 现在是接受一个参数的函数, //此函数将给参数加5,返回结果数字 var addFive = addGenerator( 5 ); //这里我们可以看到,当传给它参数4的时候 //函数addFive 的结果为9 alert( addFive( 4 ) == 9 ); 闭包还能解决另一个常见的JavaScript 编码方面的问题。JavaScript 新手趋向于在全 局作用域里放置许多变量。这一般被认为是不好的习惯,因为那些变量可能悄悄地影响 其它的库,导致令人迷惑的问题的产生。使用一个自执行的、匿名的函数,你可以从根 本上隐藏所有的通常的全局变量,使它们对其它代码不可见,如程序2-15所示。 代码2-15. 使用匿名函数从全局作用域隐藏变量的例子 //创建一个用作包装的匿名函数 (function(){ //这个变量通常情况下应该是全局的 var msg = "Thanks for visiting!"; //为全局对象绑定新的函数 window.onunload = function(){ //使用了“隐藏”的变量 alert( msg ); }; //关闭匿名函数并执行之 })(); 最后,让我们来看使用闭包时出现的一个问题。闭包允许你引用存在于父级函数中 的变量。然而,它并不是提供该变量创建时的值;它提供的是父级函数中该变量最后的 值。你会看到这个问题最通常是在一个for 循环中。有一个变量被用作迭代器(比如i), 在for 内部新的函数被创建,并使用了闭包来引用该迭代器。问题是,当新的闭包函数 被调用时,它们将会引用该iterator 最后的值(比如,一个数组的最后位置),而不是你所 期望的那个。程序2-16的例子说明,使用匿名函数激发作用域,在其中创建一个合乎期 望的闭包是可能的。 程序2-16. 使用匿名函数激发一个创建多个闭包函数所需的作用域的例子 //id 为"main"的一个元素 var obj = document.getElementById("main"); //用来绑定的items 数组 var items = [ "click", "keypress" ]; //遍历items 中的每一项 for ( var i = 0; i < items.length; i++ ) { //用自执行的匿名函数来激发作用域 (function(){ //在些作用域内存储值 var item = items[i]; //为obj 元素绑定函数 obj[ "on" + item ] = function() { //item 引用一个父级的变量, //该变量在此for 循环的上文中已被成功地scoped(?) alert( "Thanks for your " + item ); }; })(); } 闭包的概念并非轻易可以掌握的;我着实花了大量的时间和精力才彻底弄清闭包有 多么强大。幸运的是,有一个精彩的资源解释了JavaScript 中的闭包是怎么工作的:Jim Jey 的"JavaScript 闭包",网址是http://jibbering.com/faq/faq_notes/closures.html。 最后,我们将研究上下文的概念,这是许多JavaScript 的面向对象特性赖以建立的 基石。 上下文 在JavaScript 中,你的代码将总是有着某种形式的上下文(代码在其内部工作的对 象)。这也是其它面向对象语言所共有的功能,但它们都不如JavaScript 处理得这样极端。 上下文是通过变量this 工作。变量this 总是引用代码当前所在的那个对象。记住全 局对象实际上是window 对象的属性。这意味着即使是在全局上下文里,this 变量仍然 引用一个对象。上下文可以成为一个强大的工具,是面向对象代码不可或缺的一环。程 序2-17展示了一些关于上下文的简单例子。 程序2-17. 在上下文中使用函数然后将其上下文切换到另一个变量的例子 var obj = { yes: function(){ // this == obj this.val = true; }, no: function(){ this.val = false; } }; //我们看到,obj 对象没有"val"的属性 alert( obj.val == null ); //我们运行yes 函数,它将改变附着在obj 对象的val 属性 obj.yes(); alert( obj.val == true ); //然而,我们现在让window.no 指向obj.no 方法,并运行之 window.no = obj.no; window.no(); //这导致obj 对象保持不变(上下文则切换到了window 对象), alert( obj.val == true ); //而window 的val 属性被更新 alert( window.val == false ); 你可能已经注意到,在程序2-17中,当我们切换obj.no 方法的上下文到变量window 时,笨重的代码需要切换函数的上下文。幸运的是,JavaScript 提供了两种方法使这一 过程变得更加易于理解和实现。程序2-18展示了恰能些目的的两种不同方法,call 和 apply。 程序2-18. 改变函数上下文的示例 //一个简单的设置其上下文的颜色风格的函数 function changeColor( color ) { this.style.color = color; } //在window 对象上调用这个函数将会出错,因为window 没有style 对象 changeColor( "white" ); //得到一个id 为"main"的对象 var main = document.getElementById("main"); //用call 方法改变它的颜色为黑 //call 方法将第一个参数设置为上下文, //并其它所有参数传递给函数 changeColor.call( main, "black" ); //一个设置body 元素的颜色的函数 function setBodyColor() { //apply 方法设置上下文为body 元素 //第一个参数为设置的上下文, //第二个参数是一个被作为参数传递给函数的数组 // of arguments that gets passed to the function changeColor.apply( document.body, arguments ); } //设置body 元素的颜色为黑 setBodyColor( "black" ); 上下文的有用性此处可能还没有立即显现。当我们进入下一节"面向对象的 JavaScript"时,它会变得更加明显。 面向对象基础 "面向对象的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 的一个基本侧观点,就是要有能力快速、 平静地与其它代码段接口,同时保持可理解地可用性。这是一个重要的奋斗目标,也是 我们下一章里所期望达到的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值