javascript 面向对象

JeffLan

贡献于2011-09-20

字数:0 关键词: JavaScript开发

javascriptjavascriptjavascriptjavascript面向对象 技术基础 ((((一)))) 看了很多介绍 javascript面向对象技术的文章 ,很晕 .为什么 ?不是因为写得不好 ,而是 因为太深奥 . javascript中的对象还没解释清楚怎么回事 ,一上来就直奔主题 ,类/继承 /原型 /私有变 量.... 结果呢 ,看了大半天 ,有了一个大概的了解 ,细细一回味 ,好像什么都没懂 ... 这篇文章是参 考 <>第7,8,9 章而写成的 ,我也 会尽量按照原书的结构来说明 javascript的面向对象技术 (对象 /数组 ->函数 -->类/构 造函数 /原型 ).对一些我自己也拿捏不准的地方 ,我会附上原文的英文语句 ,供大家参考 . 如果不做 说明 ,则文中出 现的 所有 英文 语句 (程序体除 外 )都是引自 <>. ------------------------------------------------- 对象和数组 (ObjectsandArrays) 什么是对象 ?把一些 "名字 -属性 "的组合放在一个 单元里 面 ,就组成了一个对 象 .我们可以 理解为 javascript中 的对 象 就 是 一 些 "键-值"对的 集 合 (Anobjectisacollectionofnamed values.Thesenamedvaluesareusuallyreferred toaspropertiesoftheobject.--Section3.5). "名字 "只能是 string类型 ,不能是其他类型 ,而属性的类型则是 任意的 (数字 /字符串 /其他对象 ..).可以用 newObject()来创建一个空对象 ,也可以简 单的用 "{}"来创建一个 空对象 ,这两者的作用是等同的 . Js代码 1 varemptyObject1={};//创建空对象 2 varemptyObject2=newObject();//创建空对象 3 varperson={"name":"sdcyst", 4 "age":18, 5 "sex":"male"};//创建一个包含初始值的对象 person 6 alert(person.name);//sdcyst 7 alert(person["age"]);//18 从上面的例子我们也可以看到,访问一个对象的属性,可以简单的用对象名加 "."后加属 性的名字,也 可以用 "[]"操作符来获取 ,此时在 []里面的属性名字要加 引号 ,这是因为对象中的索 引都 是字符串类型的 . javasript对象中属性的个数是可变的 ,在创建了一个对象之后可以随时对它赋予任何的 属性 . Js代码 8 varperson={}; 9 person.name="sdcyst"; 10 person["age"]=18; 11 alert(person.name+"__"+person.age);//sdcyst__18 12 13 var_person={name:"balala","age":23};//在构建一个对象时 ,属性 的名字可以不用引号来标注 (name), 14 //但是仍旧是一个字符串类型 .在访问的时候 []内仍旧需要 引号 15 alert(_person["name"]+"__"+person.age);//balala__23 16 alert(_person[name]);//undefinied 通过 "."操作符获取对象的属性 ,必须得知道属性的名字 .一般来说 "[]"操作符获取对象 属性的功能更强大一些 , 可以在 []中放入一些表达式来取属性的值, 比如可以用在循环控制语句中 ,而"."操作符则没有这种灵活性。 Js代码 17 var name = {"name1":"NAME1","name2":"NAME2","name3":"NAME3","name4":"NAME4 "}; 18 varnamestring=""; 19 for(varpropsinname){//循环 name对象中的属性名字 20 namestring+=name[props]; 21 } 22 alert(namestring);//NAME1NAME2NAME3NAME4 23 24 namestring=""; 25 for(vari=0;i<4;i++){ 26 namestring+=name["name"+(i+1)]; 27 } 28 alert(namestring);//NAME1NAME2NAME3NAME4 delete操作符可以删除对象中的某个属性 ,判断某个属性是否存在可以使用 "in"操作符 . Js代码 29 var name = {"name1":"NAME1","name2":"NAME2","name3":"NAME3","name4":"NAME4 "}; 30 varnamestring=""; 31 for(varpropsinname){//循环 name对象中的属性名字 32 namestring+=name[props]; 33 } 34 alert(namestring);//NAME1NAME2NAME3NAME4 35 36 deletename.name1;//删除 name1属性 37 deletename["name3"];//删除 name3属性 38 namestring=""; 39 for(varpropsinname){//循环 name对象中的属性名字 40 namestring+=name[props]; 41 } 42 alert(namestring);//NAME2NAME4 43 44 alert("name1"inname);//false 45 alert("name4"inname);//true 需要注意 ,对象中的属性是没有顺序的 . 对象的 constructor属性 每一个 javascript对象都有一个 constructor属性 .这个属性对应了对象初始化时的 构造函数 (函数也是对象 ). Js代码 46 vardate=newDate(); 47 alert(date.constructor);//Date 48 alert(date.constructor=="Date");//false 49 alert(date.constructor==Date);//true javascriptjavascriptjavascriptjavascript面向对象 技术基础 ((((二)))) 数组 我们已经提到过 ,对象是无序数据的集合 ,而数组则是有序数据的集合 ,数组中的数据 (元素 ) 通过索引 (从0开始 )来访问 , 数组中的数据可以是 任何的数 据类型 .数组本身仍旧是对象 ,但是由于数组的很多 特性 ,通 常情况下把数组和对象区别 开来分别对待(Throughoutthisbook,objectsandarraysareoften treatedasdistinctdatatypes. Thisisausefulandreasonablesimplification;youcantreat objectsandarraysasseparatetypes for most of your JavaScript programming.To fully understand the behavior of objects and arrays, however, you have to know the truth: an array is nothing more than an object with a thin layer of extra functionality. You can see this with the typeof operator: applied to an array value, it returns the string "object". --section7.5). 创建数组可以用 "[]"操作符 ,或者是用 Array()构造函数来 new 一个 . Js 代码 1 var array1 = [];//创建空数组 2 var array2 = new Array(); //创建空数组 3 array1 = [1,"s",[3,4],{"name1":"NAME1"}]; // 4 alert(array1[2][1]); //4 访问数组中的数组元素 5 alert(array1[3].name1); //NAME1 访问数组中的对象 6 alert(array1[8]); //undefined 7 array2 = [,,];//没有数值填入只有逗号,则对应索引处的元素为 undefined 8 alert(array2.length); //3 9 alert(array2[1]); //undefined 用new Array()来创建数组时 ,可以指定一个默认的大小 ,其中的值此时为 undefined, 以后可以再给他们赋值 .但是由于 javascript 中的数组的长度是可以任意改变的 ,同时数组中的内容也是可以任意改变的 , 因此这个初始化的长度实际上 对数组没有任何的约 束力 .对于一个数组 ,如果对超过它最大长 度的索引 赋值 ,则会改变数 组的长度 ,同时会对没有赋值 的索引处赋值 undefined,看下面的例子 . Js 代码 10 var array = new Array(10); 11 alert(array.length); //10 12 alert(array[4]); //undefined 13 array[100] = "100th"; //这个操作会改 变数 组的长 度 ,同时将 10-99索引 对应的值设为 undefined 14 alert(array.length); //101 15 alert(array[87]); //undefined 可以用 delete 操作符删除数组的元素 ,注意这个删除仅仅是将数组在该位置的元 素设为 undefined,数组的长度并没有改变 . 我们已经使用过了数组的 length 属性 ,length 属性是一个可以读 /写的属性 ,也就是说 我 们可以通过改变数组的 length 属性来 任意的 改 变 数 组的 长 度 .如果 将 length 设为小 于 数 组 长度 的 值 ,则原数 组 中 索 引大 于 length-1的值都会被删除 .如果 length 的值大于原始数组的长度 ,则在它们之间的值设为 undefined. Js代码 16 vararray=newArray("n1","n2","n3","n4","n5");//五个元素的 数组 17 varastring=""; 18 for(vari=0;i p2.age) { 22 return p1; 23 } else { 24 return p2; 25 } 26 } 27 28 var p1 = new Person("p1",18); 29 var p2 = new Person("p2",22); 30 31 alert("现在有 " + Person.counter + "个人 ");//现在有 2个人 32 var p = Person.whoIsOlder(p1,p2); 33 alert(p.name + "的年龄较大 ");//p2的年龄较大 prototype属性的应用 : 下面这个例子是根据原书改过来的 . 假设我们定义了一个 Circle 类,有一个 radius 属性和 area 方法 ,实现如下 : Js代码 34 function Circle(radius) { 35 this.radius = radius; 36 this.area = function() { 37 return 3.14 * this.radius * this.radius; 38 } 39 } 40 var c = new Circle(1); 41 alert(c.area()); //3.14 假设我们定义了 100个Circle 类的实例对象 ,那么每个实例对象都有一 个 radius 属性和 area 方法 , 实际上 ,除了 radius 属性 ,每个 Circle 类的实例对象的 area方法都是一样 ,这样的话 ,我们就可 以 把area 方法抽出来定义在 Circle 类的 prototype属性中 ,这样所有的实例对象就可以调用这 个 方法 , 从而节省空间 . Js代码 42 function Circle(radius) { 43 this.radius = radius; 44 } 45 Circle.prototype.area = function() { 46 return 3.14 * this.radius * this.radius; 47 } 48 var c = new Circle(1); 49 alert(c.area()); //3.14 现在 ,让我们用 prototype 属性来模拟一下类的继承 :首先定义一个 Circle 类作为父类 ,然后定 义子类 PositionCircle. Js代码 50 function Circle(radius) {//定义父类 Circle 51 this.radius = radius; 52 } 53 Circle.prototype.area = function() {//定义父类的方法 area 计算面积 54 return this.radius * this.radius * 3.14; 55 } 56 57 function PositionCircle(x,y,radius) {//定义类 PositionCircle 58 this.x = x; //属性横坐标 59 this.y = y; //属性纵坐标 60 Circle.call(this,radius); //调用父类的方法 ,相当于调用 this.Circle(radius),设 置PositionCircle 类的 61 //radius 属性 62 } 63 PositionCircle.prototype = new Circle(); //设置 PositionCircle 的父类为 Circle 类 64 65 var pc = new PositionCircle(1,2,1); 66 alert(pc.area()); //3.14 67 //PositionCircle 类的 area 方法继承自 Circle 类,而Circle 类的 68 //area 方法又继承自它的 prototype属性对应的 prototype对象 69 alert(pc.radius); //1 PositionCircle 类的 radius 属性继承自 Circle 类 70 71 /* 72 注意 :在前面我们设置 PositionCircle 类的 prototype属性指向了一个 Circle 对象 , 73 因此 pc 的prototype 属性 继 承 了 Circle 对象 的 prototype 属性 ,而Circle 对象 的 constructor 属 74 性(即Circle 对象对应的 prototype对象的 constructor 属性 )是指向 Circle 的,所以此 处 弹出 75 的是 Circ. 76 */ 77 alert(pc.constructor); //Circle 78 79 /*为此 ,我们在设计好了类的继承关 系后 ,还要设置子类的 constructor 属性 ,否则它会 指向父类 80 的constructor 属性 81 */ 82 PositionCircle.prototype.constructor = PositionCircle 83 alert(pc.constructor); //PositionCircle javascript javascript javascript javascript 面向对象技 术基础 ((((六)))) 作用域、闭包、模拟私有属性 先来简单说一下变量作用域,这些东西我们都很熟悉了,所以也不详细介绍。 Js代码 1 var sco = "global"; //全局变量 2 function t() { 3 var sco = "local"; //函数内部的局部变量 4 alert(sco); //local 优先调用局部变量 5 } 6 t(); //local 7 alert(sco); //global 不能使用函数内的局部变量 注意一点,在 javascript 中没有块级别的作用域,也就是说在 java 或c/c++中我们可以 用"{}"来包围一个块, 从 而在其中定义块内的局部变量, 在 "{}"块外部 ,这些变量不再起作 用, 同时 ,也可以在 for 循环等控制语句中定义局部的变量 ,但在 javascript 中没有此项特性 : Js代码 8 function f(props) { 9 for(var i=0; i<10; i++) {} 10 alert(i); //10 虽然 i定义在 for 循环的控制语句中 ,但在函数 11 //的其他位置仍旧可以访问该变量 . 12 if(props == "local") { 13 var sco = "local"; 14 alert(sco); 15 } 16 alert(sco); //同样 ,函数仍可引用 if 语句内定义的变量 17 } 18 f("local"); //10 local local 在函数内部定义局部变量时要格外小心 : Js代码 19 var sco = "global"; 20 function print1() { 21 alert(sco); //global 22 } 23 function print2() { 24 var sco = "local"; 25 alert(sco); //local 26 } 27 function print3() { 28 alert(sco); //undefined 29 var sco = "local"; 30 alert(sco); local 31 } 32 33 print1(); //global 34 print2(); //local 35 print3(); //undefined local 前面两个函数都很容易理解,关键是第三个 :第一个 alert 语句并没有把全局变量 "global"显 示出来, 而是 undefined, 这是因为在 print3函数中, 我们定义了 sco 局部变量 (不管位置在何处 ),那么 全局的 sco 属性在函数内部将不起作用,所以第一个 alert 中sco 其实是局部 sco 变量,相当于: Js代码 36 function print3() { 37 var sco; 38 alert(sco); 39 sco = "local"; 40 alert(sco); 41 } 从这个例子我们得出, 在 函数内部定义局部变量时, 最 好是在开头就把所需的变量定义好 , 以免出错。 函数的作用域在定义函数的时候已经确定了,例如: Js代码 42 var scope = "global" //定义全局变量 43 function print() { 44 alert(scope); 45 } 46 function change() { 47 var scope = "local"; //定义局部变量 48 print(); //虽然是在 change 函数的作用域内调用 print 函数, 49 //但是 print 函数执行时仍旧按照它定义时的作用域起 作用 50 } 51 change(); //golbal 闭包 闭包是拥有变量、 代 码和作用域的表达式 .在javascript 中,函数就是变量、 代 码和函数的作 用 域的组合体 ,因此所有 的函数都 是闭 包 (JavaScript functions are a combination of code to be executed and the scope in which to execute them. This combination of code and scope is known as a closure in the computer science literature. All JavaScript functions are closures).好像挺简单 .但是闭包到底有什么作用呢 ?看一个例子。 我们想写一个方法,每次都得到一个整数,这个整数是每次加 1的,没有思索,马上下笔: Js代码 52 var i = 0; 53 function getNext() { 54 i++; 55 return i; 56 } 57 alert(getNext()); //1 58 alert(getNext()); //2 59 alert(getNext()); //3 一直用 getNext 函数得到下一个整数 ,而后不小心或者故意的将全局变量 i的值设为 0,然后再 次调用 getNext, 你会发现又从 1开始了 ........这时你会想到 ,要是把 i设置成一个私有变量该多好 ,这样只有在 方 法内部才 可能改变它,在函数之外就没有办法修改了 .下面的代码就是按照这个要求来做得,后面我 们详细讨论。 为了解释方便,我们就把下面的代码称为 demo1. Js代码 60 function temp() { 61 var i = 0; 62 function b() { 63 return ++i; 64 } 65 return b; 66 } 67 var getNext = temp(); 68 alert(getNext()); //1 69 alert(getNext()); //2 70 alert(getNext()); //3 71 alert(getNext()); //4 因为我们平时所说的 javascript 绝大多数都是指的在客户端 (浏览器 )下,所以这里也不例外 。 在javascript 解释器启动时 ,会首先创建一个全局的对象 (global object),也就是 "window"所引 用 的对象 . 然后我们定义的所有全局属性和方法等都会成为这个对象的属性 . 不同的函 数 和变 量的 作 用域 是不 同 的 ,因而构成 了 一个 作用 域 链 (scope chain).很显然, 在 javascript 解释器启动时 , 这个作用域链只有一个对象 :window(Window Object,即 global object). 在demo1中,temp函数是一个全局函数,因此 temp()函数的作用域 (scopr)对应的作用域链就 是js 解释器启动时的作用域链,只有一个 window 对象。 当temp执行时,首先创建一个 call 对象 (活动对象 ),然后把这个 call 对象添加到 temp函数 对应的作用域链的最前头 ,这是, temp()函数 对应的作用域链就包含了两个对象: window 对象和 temp 函数对应的 call object(活动对象 ). 然后呢,因为我们在 temp函数里定义了变量 i, 定义了函数 b(),这些都会成为 call object 的属性。 当 然, 在 这之前会首先给 call object 对象 添 加arguments属性,保存了 temp()函数执行时 传递过来的参数。此时,整个的作用域链如下图所示 : 同理可以得出函数 b()执行时的整个作用域链 : 注意在 b()的作用域链中, b()函数对应的 call object 只有一个 arguemnts 属性, 并 没有 i属性, 这是因为在 b()的定义中,并没有用 var 关键字来 声明 i属性,只有用 var 关键字声明的属性才会添加到对应的 call object 上. 在函数执行时, 首 先查找对应的 call object 有没有需要的属性, 如 果没有, 再 往上一级查 找, 直到找到为止,如果找不到,那就是 undefined 了. 这样我们再来看 demo1的执行情况。 我们用 getNext 引用了 temp函数,而 temp函数返回 了 函数 b,这样 getNext 函数其实就是 b函数的引用。 执行一次 getNext, 就执行一次 b()函数。 因为函数 b()的作用域依赖于函数 temp, 因此 temp 函数在内存中会一直存在。函数 b执行时,首先查找 i,在b对应的 call object 中没有, 于 是往上一级找, 在 temp函数对应的 call object 中找到 了, 于是将其值加 1,然后返回这个值。 这样, 只要 getNext 函数有效, 那么 b()函数就一直有效, 同时, b()函数依赖的 temp函数 也 不会消失,变量 i也不会消失,而且这个变量在 temp函数 外部根本就访问不到,只能在 temp()函数内部访问 (b当然可以了 ). 来看一个利用闭包来模拟私有属性的例子 : Js代码 72 function Person(name, age) { 73 this.getName = function() { return name; }; 74 this.setName = function(newName) { name = newName; }; 75 this.getAge = function() { return age; }; 76 this.setAge = function(newAge) { age = newAge; }; 77 } 78 79 var p1 = new Person("sdcyst",3); 80 alert(p1.getName()); //sdcyst 81 alert(p1.name); //undefined 因为 Person('类')没有 name 属性 82 p1.name = "mypara" //显示的给 p1添加 name 属性 83 alert(p1.getName()); //sdcyst 但是并不会改变 getName 方法的返回值 84 alert(p1.name); //mypara 显示出 p1对象的 name 属性 85 p1.setName("sss"); //改变私有的 "name"属性 86 alert(p1.getName()); //sss 87 alert(p1.name); //仍旧为 mypara 定义了一个 Person 类,有两个私有属性 name,age,分别定义对应的 get/set 方法。 虽然可以显示的设置 p1的name、age 属性,但是这种显示的设置,并不会改变我们 最初设计时模拟出来的 "name/age"私有属性。 解释闭包的确不是一件容易的事, 在 网上很多人也是利用例子来说明闭包。 如 果有地方说 的 不对,还请指正。 下面是另一篇解释 javascript 闭包的文章,一块儿参考吧。 领悟 JavaScriptJavaScriptJavaScriptJavaScript 中的 面向 对象 JavaScript 是面向对象的。但是不少人对这一点理解得并不全面。 在JavaScript 中,对象分为两种。一种可以称为 “普通对象 ”,就是我们所普遍理解的那些: 数字、日期、用户自定义的对象(如: {})等等。 还有一种, 称为 “方法对象 ”, 就是我们通常定义的 function。 你可能觉得奇怪:方法就是 方 法,怎么成了对象了?但是在 JavaScript 中,方法的确是被当成对象来处理的。下 面是一 个简单的例子: Js代码 1 function func() {alert('Hello!');} 2 alert(func.toString()); 在这个例子中, func 虽然是作为一个方法定义的,但它自身却包含一个 toString 方法,说 明func 在这里是被当成一个对象来处理的。 更 准确的说, func 是一个 “方法对象 ”。下面是 例子的继续: Js代码 3 func.name = “I am func.”; 4 alert(func.name); 我们可以任意的为 func 设置属性,这更加证明了 func 就是一个对象。那么方法对象和 普 通对象的区别在哪里呢?首先方法对象当然是可以执行的, 在 它后面加上一对括号, 就 是 执 行这个方法对象了。 Js代码 5 func(); 所以,方法对象具有二重性。一方面它可以被执行,另一方面它完全可以被当成一个普通 对象来使用。这意味着什么呢?这意味着方法对象是可以完全独立于其他对象 存在的。这 一点我们可以同 Java 比较一下。在 Java 中,方法必须在某一个类中定义,而不能单独 存 在。而 JavaScript 中就不需要。 方法对象独立于其他方法,就意味着它能够被任意的引用和传递。下面是一个例子: Js代码 6 function invoke(f) { 7 f(); 8 } 9 invoke(func); 将一个方法对象 func 传递给另一个方法对象 invoke,让后者在适当的时候执行 func。这 就是所谓的 “回调 ”了。另外,方法对象的这种特殊性,也使得 this 关键字不容易把握。这 方面相关文章不少,这里不赘述了。 除了可以被执行以外, 方法对象还有一个特殊的功用, 就是它可以通过 new 关键字来创 建 普通对象。 话说每一个方法对象被创建时,都会自动的拥有一个叫 prototype 的属性。这个属性并无 什么特别之处, 它和其他的属性一样可以访问,可以赋值。 不过当我们用 new 关键字来 创 建一个对象的时候, prototype 就起作用了:它的值 (也是一 个对象 )所包含 的所有属 性, 都会被复制到新创建的那个对象上去。下面是一个例子: Js代码 10 func.prototype.name=”prototype of func”; 11 var f = new func(); 12 alert(f.name); 执行的过程中会弹出两个对话框,后一个对 话框表示 f 这个新建的对象从 func.prototype 那里拷贝了 name 属性。 而 前一个对话框则表示 func 被作为方法执行了一遍。 你 可能会 问 了, 为 什么这个时候要还把 func 执行一遍呢?其实这个时候执行 func, 就是 起 “构造函数 ” 的作用。为了形象的说明,我们重新来一遍: Js代码 13 function func() { 14 this.name=”name has been changed.” 15 } 16 func.prototype.name=”prototype of func”; 17 var f = new func(); 18 alert(f.name); 你就会发 现 f 的name 属性不 再 是 "prototype of func",而是 被 替换 成 了 "name has been changed"。 这 就是 func 这个对象方法所起到的 “构造函数 ”的作用。 所 以, 在 JavaScript 中, 用new 关键字创建对象是执行了下面三个步骤的: 19 创建一个新的普通对象; 20 将方法对象的 prototype 属性的所有属性复制到新的普通对象中去。 21 以新的普通对象作为上下文来执行方法对象。 对于 “new func()”这样的语句,可以描述为 “从func 创建一个新对象 ”。总之, prototype 这 个属性的唯一特殊之处,就是在创建新对象的时候了。 那么我们就可以利用这一点。比如有两个方法对象 A 和B,既然从 A 创建的新对象包含 了所有 A.prototype 的属性, 那么我将它赋给 B.prototype, 那么从 B 创建的新对象不也 有 同样的属性了?写成代码就是这样: Js代码 22 A.prototype.hello = function(){alert('Hello!');} 23 B.prototype = new A(); 24 new B().hello(); 这就是 JavaScript 的所谓 “继承 ”了, 其 实质就是属性的拷贝, 这 里利用了 prototype 来实 现。 如果不用 prototype,那就用循环了,效果是一样的。所谓 “多重继承 ”,自然就是到处拷贝 了。 JavaScript 中面向对象的 原理 ,就是 上面 这些了 。自 始至终 我都 没提到 “类”的概念,因为 JavaScript 本来就没有 “类”这个东西。面向对象可以没有类吗?当然可以。先有类,然后再 有对象,这本来就不合理,因为类本来是从对象中归纳出来的,先有对象再有类, 这才合 理。像下面这样的: Js代码 25 var o = {};// 我发现了一个东西。 26 o.eat = function(){return "I am eating."} // 我发现它会吃; 27 o.sleep = function(){return "ZZZzzz..."} // 我发现它会睡; 28 o.talk = function(){return "Hi!"} // 我发现它会说话; 29 o.think = function(){return "Hmmm..."} // 我发现它还会思考。 30 31 var Human = new Function(); // 我决定给它起名叫 “人”。 32 Human.prototype = o; // 这个东西就代表了所有 “人”的概念。 33 34 var h = new Human(); // 当我发现其他同它一样的东西, 35 alert(h.talk()) // 我就知道它也是 “人”了!

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档

相关文档