收藏 分享(赏)

JavaScript面向对象的支持.ppt

上传人:weiwoduzun 文档编号:5614726 上传时间:2019-03-10 格式:PPT 页数:54 大小:127.50KB
下载 相关 举报
JavaScript面向对象的支持.ppt_第1页
第1页 / 共54页
JavaScript面向对象的支持.ppt_第2页
第2页 / 共54页
JavaScript面向对象的支持.ppt_第3页
第3页 / 共54页
JavaScript面向对象的支持.ppt_第4页
第4页 / 共54页
JavaScript面向对象的支持.ppt_第5页
第5页 / 共54页
点击查看更多>>
资源描述

1、JavaScript面向对象的支持,很少有人对JavaScript的面向对象特性进行系统的分析。我希望接下来的文字让你了解到这个语言最少为人知的一面。,JavaScript面向对象的支持,Qomolangma OpenProject v0.9 类别 :Rich Web Client 关键词 :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component, DOM,DTHML,CSS,JavaScript,JScript 项目发起:aimingoo () 项目团队:aimingoo, leon() 有贡献者:JingYu(zjycnpack.org)

2、,JavaScript中的类型,虽然JavaScript是一个基于对象的语言,但对象(Object)在JavaScript中不是第一型的。JS是以函数(Function)为第一型的语言。这样说,不但是因为JS中的函数具有高级语言中的函数的各种特性,而且也因为在JS中,Object也是由函数来实现的。关于这一点,可以在后文中“构造与析构”部分看到更进一步的说明。,JavaScript中的类型,JS是弱类型的,内置类型简单且清晰 undefined : 未定义 number : 数字 boolean : 布尔值 string : 字符串 function : 函数 object : 对象,1.un

3、defined类型,在IE5及以下版本中,除了直接赋值和typeof()之外,其它任何对undefined的操作都将导致异常。如果需要知道一个变量是否是undefined,只能采用typeof()的方法:var v; if (typeof(v) = undefined) / . ,1.undefined类型,但是在IE5.5及以上版本中,undefined是一个已实现的系统保留字。因此可以用undefined来比较和运算。检测一个值是否是undefined的更简单方法可以是:var v; if (v = undefined) / . ,1.undefined类型,因此为了使得核心代码能(部分地

4、)兼容IE5及早期版本,Romo核心单元中有一行代码用来“声明”一个undefined值: /- / code from Qomolangma, in JSEnhance.js /- var undefined = void null; 这一行代码还有一点是需要说明的,就是void语句的应用。void表明“执行其后的语句,且忽略返回值”。因此在void之后可以出现能被执行的任何“单个”语句。而执行的结果就是undefined。当然,如果你愿意,你也可以用下面的代码之一定义”undefined”。,1.undefined类型,1. 较复杂的方法,利用一个匿名的空函数执行的返回 /- var un

5、defined = function()(); /-2. 代码更简洁,但不易懂的方法 /- var undefined = void 0; void也能像函数一样使用,因此void(0)也是合法的。有些时候,一些复杂的语句可能不能 使用void的关键字形式,而必须要使用void的函数形式。例如: /- / 必须使用void()形式的复杂表达式 /- void(i=1); / 或如下语句: void(i=1, i+);,2.number类型,JavaScript中总是处理浮点数,因此它没有象Delphi中的MaxInt这样的常量,反而是有这样两个常值定义:Number.MAX_VALUE : 返

6、回 JScript 能表达的最大的数。约等于 1.79E+308。Number.MIN_VALUE : 返回 JScript 最接近0的数。约等于 2.22E-308。 因为没有整型的缘故,因此在一些关于CSS和DOM属性的运算中,如 果你期望取值为整数2, 你可能会得到字符串“2.0”或者类似于此的一些情况。这种情况下,你可能需要用到全局对象(Gobal)的parseInt()方法。 全局对象(Gobal)中还有两个属性与number类型的运算有关:NaN : 算术表达式的运算结果不是数字,则返回NaN值。Infinity : 比MAX_VALUE更大的数。,2.number类型,如果一个值

7、是NaN,那么他可以通过全局对象(Gobal)的isNaN()方法来检测。然而两个NaN值之间不是互等的。如下例: / NaN的运算与检测 varv1 = 10 * a;v2 = 10 * a; document.writeln(isNaN(v1); document.writeln(isNaN(v2); document.writeln(v1 = v2); 全局对象(Gobal)的Infinity表示比最大的数 (Number.MAX_VALUE) 更大的值。在JS中,它在数学运算时的价值与正无穷是一样的。在一些实用技巧中,它也可以用来做一个数组序列的边界检测。,2.number类型,Inf

8、inity在Number对象中被定义为POSITIVE_INFINITY。此外, 负无穷也在Number中被定义:Number.POSITIVE_INFINITY : 比最大正数(Number.MAX_VALUE)更大的值。正无穷。Number.NEGATIVE_INFINITY : 比最小负数(-Number.MAX_VALUE)更小的值。负无穷。 与NaN不同的是,两个Infinity(或-Infinity)之间是互等的。如下例: / Infinity的运算与检测 varv1 = Number.MAX_VALUE * 2;v2 = Number.MAX_VALUE * 3; documen

9、t.writeln(v1); document.writeln(v2); document.writeln(v1 = v2); 在Global中其它与number类型相关的方法有:isFinite() : 如果值是NaN/正无穷/负无穷,返回false,否则返回true。parseFloat() : 从字符串(的前缀部分)取一个浮点数。不成功则返回NaN。,3. boolean类型,(略),4. string类型,JavaScript中的String类型原本没有什么特殊的,但是JavaScript为了适应“浏览器实现的超文本环境”,因此它具有一些奇怪的方法。例如:link() : 把一个有HR

10、EF属性的超链接标签放在String对象中的文本两端。big() : 把一对标签放在String对象中的文本两端。以下方法与此类同:anchor(), blink(), bold(), fixed(), fontcolor(), fontsize()italics(), small(), strike(), sub(), sup(),4. string类型,除此之外,string的主要复杂性来自于在JavaScript中无所不在的toString() 方法。这也是JavaScript为浏览器环境而提供的一个很重要的方法。例如我们 声明一个对象,但是要用document.writeln()来输出

11、它,在IE中会显示什么呢? 下例说明这个问题: /- / toString()的应用 /- vars = new Object(); s.v1 = hi,; s.v2 = test!; document.writeln(s); document.writeln(s.toString();s.toString = function() return s.v1 + s.v2; document.writeln(s);,4. string类型,在上个例子中,我们看到,当一个对象没有重新声明(覆盖)自己toString()方法的时候,那么它作为字符串型态使用时(例如被writeln),就会调用Java

12、 Script 环境缺省的toString()。反过来,你也可以重新定义JavaScript理解这个对象 的方法。很多JavaScript框架,在实现“模板”机制的时候,就利用了这个特性。例如:他们用这样定义一个FontElement对象: / 利用toString()实现模板机制的简单原理 function FontElement(innerHTML) this.face = 宋体;this.color = red;/ more.var ctx = innerHTML;this.toString = function() return + ctx+ ; var obj = new FontE

13、lement(这是一个测试。); / 留意下面这行代码的写法 document.writeln(obj);,5. function类型,javascript函数具有很多特性,除了面向对象的部分之外(这在后面讲述),它自已的一些独特特性应用也很广泛。 首先javascript中的每个函数,在调用过程中可以执有一个arguments对象。这个对象是由脚本解释环境创建的,你没有别的方法来自己创建一个arguments对象。arguments可以看成一个数组:它有length属性,并可以通过argumentsn的方式来访问每一个参数。然而它最重要的,却是可以通过 callee 属性来得到正在执行的函数

14、对象的引用。接下的问题变得很有趣:Function对象有一个 caller 属性,指向正在调用当前函数的父函数对象的引用。,5. function类型,我们已经看到,我们可以在JavaScript里面,通过callee/caller来遍历执行期的调用栈。由于arguments事实上也是Function的一个属性,因此我们事实上也能遍历执行期调用栈上的每一个函数的参数。下面的代码是一个简单的调用栈的遍历的示例: function foo1(v1, v2) foo2(v1 * 100); function foo2(v1) foo3(v1 * 200); function foo3(v1) var

15、 foo = arguments.callee;while (foo ,6.Object对象,在前面的例子中其实已经讲到了object类型的“类型声明”与“实例创建”。 在JavaScript中,我们需要通过一个函数来声明自己的object类型: /- / JavaScript中对象的类型声明的形式代码 / (以后的文档中,“对象名”通常用MyObject来替代) /- function 对象名(参数表) this.属性 = 初始值;this.方法 = function(方法参数表) / 方法实现代码 ,6.Object对象,然后,我们可以通过这样的代码来创建这个对象类型的一个实例: /- /

16、 创建实例的形式代码 / (以后的文档中,“实例变量名”通常用obj来替代) /- var 实例变量名 = new 对象名(参数表);,7.函数,函数在JavaScript的面向对象机制中的五重身份 “对象名”如MyObject()这个函数充当了以下语言角色:(1) 普通函数(2) 类型声明(3) 类型的实现(4) 类引用(5) 对象的构造函数 一些程序员(例如Delphi程序员)习惯于类型声明与实现分开。例如在delphi 中,Interface节用于声明类型或者变量,而implementation节用于书写类型 的实现代码,或者一些用于执行的函数、代码流程。 但在JavaScript中,类

17、型的声明与实现是混在一起的。一个对象的类型(类) 通过函数来声明,this.xxxx表明了该对象可具有的属性或者方法。,7.函数,这个函数的同时也是“类引用”。在JavaScript,如果你需要识别一个对象 的具体型别,你需要执有一个“类引用”。当然,也就是这个函数的名 字。instanceof 运算符就用于识别实例的类型,我们来看一下它的应用: /- / JavaScript中对象的类型识别 / 语法: 对象实例 instanceof 类引用 /- function MyObject() this.data = test data; / 这里MyObject()作为构造函数使用 var ob

18、j = new MyObject(); var arr = new Array(); / 这里MyObject作为类引用使用 document.writeln(obj instanceof MyObject); document.writeln(arr instanceof MyObject);,8.反射机制的实现,JavaScript中通过forin语法来实现了反射机制。但是JavaScript中并不明确区分“属性”与“方法”,以及“事件”。因此,对属性的类型考查在JS中是个问题。,8.反射机制的实现,下面的代码简单示例forin的使用与属性识别: var _r_event = _r_eve

19、nt = /Oon.*/; var colorSetting = method: red,event: blue,property: var obj2 = a_method : function() ,a_property: 1,onclick: undefined ,8.反射机制的实现,function propertyKind(obj, p) return (_r_event.test(p) ,8.反射机制的实现,一个常常被开发者忽略的事实是:JavaScript本身是没有事件(Event)系统的。通常我们在JavaScript用到的onclick等事件,其实是IE的DOM模型提供的。从更

20、内核的角度上讲:IE通过COM的接口属性公布了一组事件接口给DOM。 有两个原因,使得在JS中不能很好的识别“一个属性是不是事件”:- COM接口中本身只有方法,属性与事件,都是通过一组get/set方法来公布的。- JavaScript中,本身并没有独立的“事件”机制。,8.反射机制的实现,因此我们看到event的识别方法,是检测属性名是否是以on字符串开头(以On开头的是Qomo的约定)。接下来,由于DOM对象中的事件是可以不指定处理函数的,这种情况下事件句柄为null值(Qomo采用相同的约定);在另外的一些情况下,用户可能象obj2这样,定义一个值为 undefined的录虼“事件”的

21、判定条件被处理成一个复杂的表达式:(“属性以on/On开头“ & (“值为null/undefined“ | “类型为function“) 另外,从上面的这段代码的运行结果来看。对DOM对象使用for.in,是不能列举出对象方法来的。最后说明一点。事实上,在很多语言的实现中,“事件”都不是“面向对象”的语言特性,而是由具体的编程模型来提供的。例如Delphi中的事件驱动机制,是由Win32操作系统中的窗口消息机制来提供,或者由用户代码在Component/Class中主动调用事件处理函数来实现。 “事件”是一个“如何驱动编程模型”的机制问题,而不是语言本身的问题。然而以PME(property

22、/method/event)为框架的OOP概念,已经深入人心,所以当编程语言或系统表现出这些特性来的时候,就已经没人关心“event究竟是谁实现”的了。,9.this与with关键字的使用,在JavaScript的对象系统中,this关键字用在两种地方:- 在构造器函数中,指代新创建的对象实例- 在对象的方法被调用时,指代调用该方法的对象实例如果一个函数被作为普通函数(而不是对象方法)调用,那么在函数中的this关键字将指向window对象。与此相同的,如果this关键字不在任何函数中,那么他也指向window对象。,9.this与with关键字的使用,由于在JavaScript中不明确区分函

23、数与方法。因此有些代码看起来很奇怪: /- / 函数的几种可能调用形式 /- function foo() / 下面的this指代调用该方法的对象实例if (this=window) document.write(call a function., );else document.write(call a method, by object: , this.name, ); ,9.this与with关键字的使用,function MyObject(name) / 下面的this指代new关键字新创建实例this.name = name;this.foo = foo; var obj1 = ne

24、w MyObject(obj1); var obj2 = new MyObject(obj2); / 测试1: 作为函数调用 foo(); / 测试2: 作为对象方法的调用 obj1.foo(); obj2.foo(); / 测试3: 将函数作为“指定对象的”方法调用 foo.call(obj1); foo.apply(obj2);,9.this与with关键字的使用,在上面的代码里,obj1/obj2对foo()的调用是很普通的调用方法。也就是在构造器上,将一个函数指定为对象的方法。 而测试3中的call()与apply()就比较特殊。 在这个测试中,foo()仍然作为普通函数来调用,只是J

25、avaScript的语言特性允许在call()/apply()时,传入一个对象实例来指定foo()的上下文环境中所出现的this关键字的引用。需要注意的是,此时的foo()仍旧是一个普通函数调用,而不是对象方法调用。与this“指示调用该方法的对象实例”有些类同的,with()语法也用于限定“在一段代码片段中默认使用对象实例”。如果不使用with()语法,那么这段代码将受到更外层with()语句的影响;如果没有更外层的with(),那么这段代码的“默认使用的对象实例”将是window。,9.this与with关键字的使用,然而需要注意的是this与with关键字不是互为影响的。如下面的代码:

26、/ 测试: this与with关键字不是互为影响的 function test() with (obj2) this.value = 8; var obj2 = new Object(); obj2.value = 10; test(); document.writeln(obj2.value: , obj2.value, ); document.writeln(window.value: , window.value, ); 你不能指望这样的代码在调用结束后,会使obj2.value属性置值为8。这几行代码的结果是:window对象多了一个value属性,并且值为8。 with(obj).这

27、个语法,只能限定对obj的既有属性的读取,而不能主动的声明它。一旦with()里的对象没有指定的属性,或者with()限定了一个不是对象的数据,那么结果会产生一个异常。,10.使用in关键字的运算,除了用forin来反射对象的成员信息之外,JavaScript中也允许直接用in关键字去检测对象是否有指定名字的属性。in关键字经常被提及的原因并不是它检测属性是否存在的能力,因此在早期的代码中,很多可喜欢用“if (!obj.propName) ” 这样的方式来检测propName是否是有效的属性。很多时候,检测有效性比检测“是否存有该属性”更有实用性。因此这种情况下,in只是一个可选的、官方的方

28、案。,10.使用in关键字的运算,in关键字的重要应用是高速字符串检索。尤其是在只需要判定“字符串是否存在”的情况下。 例如10万个字符串,如果存储在数组中,那么检索效率将会极差。 function arrayToObject(arr) for (var obj=new Object(), i=0, imax=arr.length; iimax; i+) objarri=null;return obj; vararr = abc, def, ghi; / more and more.obj = arrayToObject(arr); function valueInArray(v) for (

29、var i=0, imax=arr.length; iimax; i+) if (arri=v) return true;return false; function valueInObject(v) return v in obj; ,10.使用in关键字的运算,这种使用关键字in的方法,也存在一些限制。例如只能查找字符串,而数组元可以是任意值。另外,arrayToObject()也存在一些开销,这使得它不适合于频繁变动的查找集。最后,(我想你可能已经注意到了)使用对象来查找的时候并不能准确定位到查找数据,而数组中可以指向结果的下标。,11.使用instanceof关键字,在JavaScri

30、pt中提供了instanceof关键字来检测实例的类型。这在前面讨论它的“五重身份”时已经讲过。但instanceof的问题是,它总是列举整个原型链以检测类型如: /- / instanceof使用中的问题 /- function MyObject() / . function MyObject2() / . MyObject2.prototype = new MyObject(); obj1 = new MyObject(); obj2 = new MyObject2(); document.writeln(obj1 instanceof MyObject, ); document.writ

31、eln(obj2 instanceof MyObject, );,11.使用instanceof关键字,我们看到,obj1与obj2都是MyObject的实例,但他们是不同的构造函数产生的。注意,这在面向对象理论中正确的:因为obj2是MyObject的子类实例,因此它具有与obj1相同的特性。在应用中这是obj2的多态性的体现之一。但是,即便如此,我们也必须面临这样的问题:如何知道obj2与obj1是否是相同类型的实例呢?也就是说,连构造器都相同?instanceof关键字不提供这样的机制。一个提供实现这种检测的能力的,是Object.constructor属性。但请先记住,它的使用远比你想

32、象的要难。问题先到这里。constructor属性已经涉及到“构造与析构”的问题,这个我们后面再讲。“原型继承”、“构造与析构”是JavaScript的OOP中的主要问题、核心问题,以及“致命问题”。,12.null与undefined,在JavaScript中,null与undefined曾一度使我迷惑。下面的文字,有利于你更清晰的认知它(或者让你更迷惑): - null是关键字;undefined是Global对象的一个属性。 - null是对象(空对象, 没有任何属性和方法);undefined是undefined类型的值。试试下面的代码:document.writeln(typeof

33、null);document.writeln(typeof undefined); -对象模型中,所有的对象都是Object或其子类的实例,但null对象例外:document.writeln(null instanceof Object); - null“等值(=)”于undefined,但不“全等值(=)”于undefined:document.writeln(null = undefined);document.writeln(null = undefined); - 运算时null与undefined都可以被类型转换为false,但不等值于false:document.writeln(

34、!null, !undefined);document.writeln(null=false);document.writeln(undefined=false);,13.构造、析构与原型问题,我们已经知道一个对象是需要通过构造器函数来产生的。我们先记住几点:- 构造器是一个普通的函数- 原型是一个对象实例- 构造器有原型属性,对象实例没有- (如果正常地实现继承模型,)对象实例的constructor属性指向构造器- 从三、四条推出:obj.constructor.prototype指向该对象的原型,13.构造、析构与原型问题,接下来分析一个例子,来说明JavaScript的“继承原型”声明

35、,以及构造过程。 /- / 理解原型、构造、继承的示例 /- function MyObject() this.v1 = abc; function MyObject2() this.v2 = def; MyObject2.prototype = new MyObject(); var obj1 = new MyObject(); var obj2 = new MyObject2();,13.构造、析构与原型问题,1). new()关键字的形式化代码 2). 用户代码维护的原型(prototype)链 3). 原型实例是如何被构造过程使用的,14.new()关键字形式化代码,我们先来看“obj

36、1 = new MyObject()”这行代码中的这个new关键字。new关键字用于产生一个实例(说到这里补充一下,我习惯于把保留字叫关键字。 另外,在JavaScript中new关键字同时也是一个运算符),但这个实例应当是从 一个“原型的模板”复制过来的。这个用来作模板的原型对象,就是用“构造器 函数的prototype属性”所指向的那个对象。对于JavaScript“内置对象的构造 器”来说,它指向内部的一个原型。 每一个函数,无论它是否用作构造器,都会有一个独一无二的原型对象。缺省时 JavaScript用它构造出一个“空的初始对象实例(不是null)”。然而如果你给函 数的这个prot

37、otype赋一个新的对象,那么构造过程将用这个新对象作为“模板”。 接下来,构造过程将调用MyObject()来完成初始化。注意,这里只是“初始 化”。,14.new()关键字形式化代码,为了清楚地解释这个过程,我用代码形式化地描述一下这个过程: /- / new()关键字的形式化代码 /- function new(aFunction) / 如果有参数argsvar _this = aFunction.prototype.clone(); / 从prototype中复制一个对象aFunction.call(_this); / 调用构造函数完成初始化, (如果有,)传入argsreturn _

38、this; / 返回对象 所以我们看到以下两点: - 构造函数(aFunction)本身只是对传入的this实例做“初始化”处理,而不是构造一个对象实例。 - 构造的过程实际发生在new()关键字/运算符的内部。 而且,构造函数(aFunction)本身并不需要操作prototype,也不需要回传this。,15.用户代码维护的原型(prototype)链,- 原型链是用户代码创建的,new()关键字并不协助维护原型链 以Delphi代码为例,我们在声明继承关系的时候,可以用这样的代码: /- / delphi中使用的“类”类型声明 /- typeTAnimal = class(TObject

39、); / 动物TMammal = class(TAnimal); / 哺乳动物TCanine = class(TMammal); / 犬科的哺乳动物TDog = class(TCanine); / 狗,15.用户代码维护的原型(prototype)链,这时,Delphi的编译器会通过编译技术来维护一个继承关系链表。我们可以通过类似以下的代码来查询这个链表: /- / delphi中使用继关系链表的关键代码 /- function isAnimal(obj: TObject): boolean; beginResult := obj is TAnimal; end; vardog := TDog

40、; / . dog := TDog.Create(); writeln(isAnimal(dog);,15.用户代码维护的原型(prototype)链,可以看到,在Delphi的用户代码中,不需要直接继护继承关系的链表。这是为Delphi是强类型语言,在处理用class()关键字声明类型时,delphi的编译器已经为用户构造了这个继承关系链。注意,这个过程是声明,而不是执行代码。 而在JavaScript中,如果需要获知对象“是否是某个基类的子类对象”,那么你需要手工的来维护(与delphi这个例子类似的)一个链表。当然,这个链有不叫类型继承树,而叫“(对象的)原型链表”。在JS中,没有“类”

41、类型。,15.用户代码维护的原型(prototype)链,参考前面的JS和Delphi代码,一个类同的例子是这样: / JS中“原型链表”的关键代码 / 1. 构造器 function Animal() ; function Mammal() ; function Canine() ; function Dog() ; / 2. 原型链表 Mammal.prototype = new Animal(); Canine.prototype = new Mammal(); Dog.prototype = new Canine(); / 3. 示例函数 function isAnimal(obj)

42、return obj instanceof Animal; vardog = new Dog(); document.writeln(isAnimal(dog);,15.用户代码维护的原型(prototype)链,可以看到,在JS的用户代码中,“原型链表”的构建方法是一行代码: “当前类的构造器函数“.prototype = “直接父类的实例“ 这与Delphi一类的语言不同:维护原型链的实质是在执行代码,而非声明。那么,“是执行而非声明”到底有什么意义呢? JavaScript是会有编译过程的。这个过程主要处理的是“语法检错”、“语 法声明”和“条件编译指令”。而这里的“语法声明”,主要处理

43、的就是函数声明。这也是我说“函数是第一类的,而对象不是”的一个原因。,15.用户代码维护的原型(prototype)链,如下例: testFoo(obj1); try testFoo(obj2); catch(e) document.writeln(Exception: , e.description, ); / 声明testFoo() function testFoo(v) document.writeln(v, ); / 声明object var obj1 = ; obj2 = toString: function() return hi, object. / 4. 输出obj1 / 5. 输出obj2 testFoo(obj1); testFoo(obj2);,15.用户代码维护的原型(prototype)链,这个示例代码在JS环境中执行的结果是: -1234undefinedException: obj2 未定义object Objecthi, obj - 问题是,testFoo()是在它被声明之前被执行的;而同样用“直接声明”的 形式定义的object变量,却不能在声明之前引用。例子中,第二、三 个输入是不正确的。 函数可以在声明之前引用,而其它类型的数值必须在声明之后才能被使用。 这说明“声明”与“执行期引用”在JavaScript中是两个过程。,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 网络科技 > Java

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报